新版IP库管理阶段性提交(未完成)

This commit is contained in:
GoEdgeLab
2022-08-13 23:55:48 +08:00
parent 0388d6c557
commit 0e411ff59f
42 changed files with 2628 additions and 172 deletions

View File

@@ -14,23 +14,23 @@ type Admin struct {
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 状态
Modules dbs.JSON `field:"modules"` // 允许的模块
CanLogin uint8 `field:"canLogin"` // 是否可以登录
CanLogin bool `field:"canLogin"` // 是否可以登录
Theme string `field:"theme"` // 模板设置
}
type AdminOperator struct {
Id interface{} // ID
IsOn interface{} // 是否启用
Username interface{} // 用户名
Password interface{} // 密码
Fullname interface{} // 全名
IsSuper interface{} // 是否为超级管理员
CreatedAt interface{} // 创建时间
UpdatedAt interface{} // 修改时间
State interface{} // 状态
Modules interface{} // 允许的模块
CanLogin interface{} // 是否可以登录
Theme interface{} // 模板设置
Id any // ID
IsOn any // 是否启用
Username any // 用户名
Password any // 密码
Fullname any // 全名
IsSuper any // 是否为超级管理员
CreatedAt any // 创建时间
UpdatedAt any // 修改时间
State any // 状态
Modules any // 允许的模块
CanLogin any // 是否可以登录
Theme any // 模板设置
}
func NewAdminOperator() *AdminOperator {

View File

@@ -12,23 +12,23 @@ type HTTPCacheTask struct {
Day string `field:"day"` // 创建日期YYYYMMDD
IsDone bool `field:"isDone"` // 是否已完成
IsOk bool `field:"isOk"` // 是否完全成功
IsReady uint8 `field:"isReady"` // 是否已准备好
IsReady bool `field:"isReady"` // 是否已准备好
Description string `field:"description"` // 描述
}
type HTTPCacheTaskOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
Type interface{} // 任务类型purge|fetch
KeyType interface{} // Key类型
State interface{} // 状态
CreatedAt interface{} // 创建时间
DoneAt interface{} // 完成时间
Day interface{} // 创建日期YYYYMMDD
IsDone interface{} // 是否已完成
IsOk interface{} // 是否完全成功
IsReady interface{} // 是否已准备好
Description interface{} // 描述
Id any // ID
UserId any // 用户ID
Type any // 任务类型purge|fetch
KeyType any // Key类型
State any // 状态
CreatedAt any // 创建时间
DoneAt any // 完成时间
Day any // 创建日期YYYYMMDD
IsDone any // 是否已完成
IsOk any // 是否完全成功
IsReady any // 是否已准备好
Description any // 描述
}
func NewHTTPCacheTaskOperator() *HTTPCacheTaskOperator {

View File

@@ -33,7 +33,7 @@ func init() {
})
}
// 启用条目
// EnableIPLibrary 启用条目
func (this *IPLibraryDAO) EnableIPLibrary(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -42,7 +42,7 @@ func (this *IPLibraryDAO) EnableIPLibrary(tx *dbs.Tx, id int64) error {
return err
}
// 禁用条目
// DisableIPLibrary 禁用条目
func (this *IPLibraryDAO) DisableIPLibrary(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -51,7 +51,7 @@ func (this *IPLibraryDAO) DisableIPLibrary(tx *dbs.Tx, id int64) error {
return err
}
// 查找启用中的条目
// FindEnabledIPLibrary 查找启用中的条目
func (this *IPLibraryDAO) FindEnabledIPLibrary(tx *dbs.Tx, id int64) (*IPLibrary, error) {
result, err := this.Query(tx).
Pk(id).
@@ -63,7 +63,7 @@ func (this *IPLibraryDAO) FindEnabledIPLibrary(tx *dbs.Tx, id int64) (*IPLibrary
return result.(*IPLibrary), err
}
// 查找某个类型的IP库列表
// FindAllEnabledIPLibrariesWithType 查找某个类型的IP库列表
func (this *IPLibraryDAO) FindAllEnabledIPLibrariesWithType(tx *dbs.Tx, libraryType string) (result []*IPLibrary, err error) {
_, err = this.Query(tx).
State(IPLibraryStateEnabled).
@@ -74,7 +74,7 @@ func (this *IPLibraryDAO) FindAllEnabledIPLibrariesWithType(tx *dbs.Tx, libraryT
return
}
// 查找某个类型的最新的IP库
// FindLatestIPLibraryWithType 查找某个类型的最新的IP库
func (this *IPLibraryDAO) FindLatestIPLibraryWithType(tx *dbs.Tx, libraryType string) (*IPLibrary, error) {
one, err := this.Query(tx).
State(IPLibraryStateEnabled).
@@ -90,7 +90,7 @@ func (this *IPLibraryDAO) FindLatestIPLibraryWithType(tx *dbs.Tx, libraryType st
return one.(*IPLibrary), nil
}
// 创建新的IP库
// CreateIPLibrary 创建新的IP库
func (this *IPLibraryDAO) CreateIPLibrary(tx *dbs.Tx, libraryType string, fileId int64) (int64, error) {
var op = NewIPLibraryOperator()
op.Type = libraryType

View File

@@ -0,0 +1,469 @@
package models
import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/regions"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"os"
"time"
)
const (
IPLibraryFileStateEnabled = 1 // 已启用
IPLibraryFileStateDisabled = 0 // 已禁用
)
type IPLibraryFileDAO dbs.DAO
func NewIPLibraryFileDAO() *IPLibraryFileDAO {
return dbs.NewDAO(&IPLibraryFileDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeIPLibraryFiles",
Model: new(IPLibraryFile),
PkName: "id",
},
}).(*IPLibraryFileDAO)
}
var SharedIPLibraryFileDAO *IPLibraryFileDAO
func init() {
dbs.OnReady(func() {
SharedIPLibraryFileDAO = NewIPLibraryFileDAO()
})
}
// EnableIPLibraryFile 启用条目
func (this *IPLibraryFileDAO) EnableIPLibraryFile(tx *dbs.Tx, id uint64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", IPLibraryFileStateEnabled).
Update()
return err
}
// DisableIPLibraryFile 禁用条目
func (this *IPLibraryFileDAO) DisableIPLibraryFile(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", IPLibraryFileStateDisabled).
Update()
return err
}
// FindEnabledIPLibraryFile 查找启用中的条目
func (this *IPLibraryFileDAO) FindEnabledIPLibraryFile(tx *dbs.Tx, id int64) (*IPLibraryFile, error) {
result, err := this.Query(tx).
Pk(id).
State(IPLibraryFileStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*IPLibraryFile), err
}
// CreateLibraryFile 创建文件
func (this *IPLibraryFileDAO) CreateLibraryFile(tx *dbs.Tx, template string, emptyValues []string, fileId int64, countries []string, provinces [][2]string, cities [][3]string, towns [][4]string, providers []string) (int64, error) {
var op = NewIPLibraryFileOperator()
op.Template = template
if emptyValues == nil {
emptyValues = []string{}
}
emptyValuesJSON, err := json.Marshal(emptyValues)
if err != nil {
return 0, err
}
op.EmptyValues = emptyValuesJSON
op.FileId = fileId
if countries == nil {
countries = []string{}
}
countriesJSON, err := json.Marshal(countries)
if err != nil {
return 0, err
}
op.Countries = countriesJSON
if provinces == nil {
provinces = [][2]string{}
}
provincesJSON, err := json.Marshal(provinces)
if err != nil {
return 0, err
}
op.Provinces = provincesJSON
if cities == nil {
cities = [][3]string{}
}
citiesJSON, err := json.Marshal(cities)
if err != nil {
return 0, err
}
op.Cities = citiesJSON
if towns == nil {
towns = [][4]string{}
}
townsJSON, err := json.Marshal(towns)
if err != nil {
return 0, err
}
op.Towns = townsJSON
if providers == nil {
providers = []string{}
}
providersJSON, err := json.Marshal(providers)
if err != nil {
return 0, err
}
op.Providers = providersJSON
op.IsFinished = false
op.State = IPLibraryFileStateEnabled
return this.SaveInt64(tx, op)
}
// FindAllUnfinishedLibraryFiles 查找所有未完成的文件
func (this *IPLibraryFileDAO) FindAllUnfinishedLibraryFiles(tx *dbs.Tx) (result []*IPLibraryFile, err error) {
_, err = this.Query(tx).
State(IPLibraryFileStateEnabled).
Result("id", "fileId", "createdAt"). // 这里不需要其他信息
Attr("isFinished", false).
DescPk().
Slice(&result).
FindAll()
return
}
// UpdateLibraryFileIsFinished 设置文件为已完成
func (this *IPLibraryFileDAO) UpdateLibraryFileIsFinished(tx *dbs.Tx, fileId int64) error {
return this.Query(tx).
Pk(fileId).
Set("isFinished", true).
UpdateQuickly()
}
// FindLibraryFileCountries 获取IP库中的国家/地区
func (this *IPLibraryFileDAO) FindLibraryFileCountries(tx *dbs.Tx, fileId int64) ([]string, error) {
countriesJSON, err := this.Query(tx).
Result("countries").
Pk(fileId).
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(countriesJSON) {
return nil, nil
}
var result = []string{}
err = json.Unmarshal(countriesJSON, &result)
if err != nil {
return nil, err
}
return result, nil
}
// FindLibraryFileProvinces 获取IP库中的省份
func (this *IPLibraryFileDAO) FindLibraryFileProvinces(tx *dbs.Tx, fileId int64) ([][2]string, error) {
provincesJSON, err := this.Query(tx).
Result("provinces").
Pk(fileId).
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(provincesJSON) {
return nil, nil
}
var result = [][2]string{}
err = json.Unmarshal(provincesJSON, &result)
if err != nil {
return nil, err
}
return result, nil
}
// FindLibraryFileCities 获取IP库中的城市
func (this *IPLibraryFileDAO) FindLibraryFileCities(tx *dbs.Tx, fileId int64) ([][3]string, error) {
citiesJSON, err := this.Query(tx).
Result("cities").
Pk(fileId).
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(citiesJSON) {
return nil, nil
}
var result = [][3]string{}
err = json.Unmarshal(citiesJSON, &result)
if err != nil {
return nil, err
}
return result, nil
}
// FindLibraryFileProviders 获取IP库中的ISP运营商
func (this *IPLibraryFileDAO) FindLibraryFileProviders(tx *dbs.Tx, fileId int64) ([]string, error) {
providersJSON, err := this.Query(tx).
Result("providers").
Pk(fileId).
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(providersJSON) {
return nil, nil
}
var result = []string{}
err = json.Unmarshal(providersJSON, &result)
if err != nil {
return nil, err
}
return result, nil
}
func (this *IPLibraryFileDAO) GenerateIPLibrary(tx *dbs.Tx, libraryFileId int64) error {
one, err := this.Query(tx).Pk(libraryFileId).Find()
if err != nil {
return err
}
if one == nil {
return errors.New("the library file not found")
}
var libraryFile = one.(*IPLibraryFile)
template, err := iplibrary.NewTemplate(libraryFile.Template)
if err != nil {
return errors.New("create template from '" + libraryFile.Template + "' failed: " + err.Error())
}
var fileId = int64(libraryFile.FileId)
if fileId == 0 {
return errors.New("the library file has not been uploaded yet")
}
var dir = Tea.Root + "/data"
stat, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
err = os.Mkdir(dir, 0777)
if err != nil {
return errors.New("can not open dir '" + dir + "' to write: " + err.Error())
}
} else {
return errors.New("can not open dir '" + dir + "' to write: " + err.Error())
}
} else if !stat.IsDir() {
_ = os.Remove(dir)
err = os.Mkdir(dir, 0777)
if err != nil {
return errors.New("can not open dir '" + dir + "' to write: " + err.Error())
}
}
// TODO 删除以往生成的文件,但要考虑到文件正在被别的任务所使用
// 国家
var countries = []*iplibrary.Country{}
// TODO
// 省份
var provinces = []*iplibrary.Province{}
// TODO
// 城市
var cities = []*iplibrary.City{}
// TODO
// 区县
var towns = []*iplibrary.Town{}
// TODO
// ISP运营商
var providers = []*iplibrary.Provider{}
// TODO
var libraryCode = utils.Sha1RandomString() // 每次都生成新的code
var filePath = dir + "/" + this.composeFilename(libraryFileId, libraryCode)
writer, err := iplibrary.NewFileWriter(filePath, &iplibrary.Meta{
Author: "", // 将来用户可以自行填写
CreatedAt: time.Now().Unix(),
Countries: countries,
Provinces: provinces,
Cities: cities,
Towns: towns,
Providers: providers,
})
if err != nil {
return err
}
defer func() {
_ = writer.Close()
}()
err = writer.WriteMeta()
if err != nil {
return errors.New("write meta failed: " + err.Error())
}
chunkIds, err := SharedFileChunkDAO.FindAllFileChunkIds(tx, fileId)
if err != nil {
return err
}
// countries etc ...
var countryMap = map[string]int64{} // countryName => countryId
dbCountries, err := regions.SharedRegionCountryDAO.FindAllCountries(tx)
if err != nil {
return err
}
for _, country := range dbCountries {
for _, code := range country.AllCodes() {
countryMap[code] = int64(country.Id)
}
}
var provinceMap = map[string]int64{} // countryId_provinceName => provinceId
dbProvinces, err := regions.SharedRegionProvinceDAO.FindAllEnabledProvinces(tx)
if err != nil {
return err
}
for _, province := range dbProvinces {
for _, code := range province.AllCodes() {
provinceMap[types.String(province.CountryId)+"_"+code] = int64(province.Id)
}
}
var cityMap = map[string]int64{} // provinceId_cityName => cityId
dbCities, err := regions.SharedRegionCityDAO.FindAllEnabledCities(tx)
if err != nil {
return err
}
for _, city := range dbCities {
for _, code := range city.AllCodes() {
cityMap[types.String(city.ProvinceId)+"_"+code] = int64(city.Id)
}
}
var townMap = map[string]int64{} // cityId_townName => townId
// TODO 将来实现区县
var providerMap = map[string]int64{} // providerName => providerId
dbProviders, err := regions.SharedRegionProviderDAO.FindAllEnabledProviders(tx)
if err != nil {
return err
}
for _, provider := range dbProviders {
for _, code := range provider.AllCodes() {
providerMap[code] = int64(provider.Id)
}
}
dataParser, err := iplibrary.NewParser(&iplibrary.ParserConfig{
Template: template,
EmptyValues: libraryFile.DecodeEmptyValues(),
Iterator: func(values map[string]string) error {
var ipFrom = values["ipFrom"]
var ipTo = values["ipTo"]
var countryName = values["country"]
var provinceName = values["province"]
var cityName = values["city"]
var townName = values["town"]
var providerName = values["provider"]
var countryId = countryMap[countryName]
var provinceId int64
var cityId int64 = 0
var townId int64 = 0
var providerId = providerMap[providerName]
if countryId > 0 {
provinceId = provinceMap[types.String(countryId)+"_"+provinceName]
if provinceId > 0 {
cityId = cityMap[types.String(provinceId)+"_"+cityName]
if cityId > 0 {
townId = townMap[types.String(cityId)+"_"+townName]
}
}
}
err = writer.Write(ipFrom, ipTo, countryId, provinceId, cityId, townId, providerId)
if err != nil {
return errors.New("write failed: " + err.Error())
}
return nil
},
})
if err != nil {
return err
}
for _, chunkId := range chunkIds {
chunk, err := SharedFileChunkDAO.FindFileChunk(tx, chunkId)
if err != nil {
return err
}
if chunk == nil {
return errors.New("invalid chunk file, please upload again")
}
dataParser.Write(chunk.Data)
err = dataParser.Parse()
if err != nil {
return err
}
}
err = writer.Close()
if err != nil {
return err
}
// 设置code
err = this.Query(tx).
Pk(libraryFileId).
Set("code", libraryCode).
Set("isFinished", true).
Set("generatedAt", time.Now().Unix()).
UpdateQuickly()
if err != nil {
return err
}
return nil
}
// 组合IP库文件名
func (this *IPLibraryFileDAO) composeFilename(libraryFileId int64, code string) string {
return "ip-library-" + types.String(libraryFileId) + "-" + code + ".db"
}

View File

@@ -0,0 +1,19 @@
package models_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestIPLibraryFileDAO_GenerateIPLibrary(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
err := models.SharedIPLibraryFileDAO.GenerateIPLibrary(tx, 3)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,44 @@
package models
import "github.com/iwind/TeaGo/dbs"
// IPLibraryFile IP库上传的文件
type IPLibraryFile struct {
Id uint64 `field:"id"` // ID
FileId uint64 `field:"fileId"` // 原始文件ID
Template string `field:"template"` // 模板
EmptyValues dbs.JSON `field:"emptyValues"` // 空值列表
GeneratedFileId uint64 `field:"generatedFileId"` // 生成的文件ID
GeneratedAt uint64 `field:"generatedAt"` // 生成时间
IsFinished bool `field:"isFinished"` // 是否已经完成
Countries dbs.JSON `field:"countries"` // 国家/地区
Provinces dbs.JSON `field:"provinces"` // 省份
Cities dbs.JSON `field:"cities"` // 城市
Towns dbs.JSON `field:"towns"` // 区县
Providers dbs.JSON `field:"providers"` // ISP服务商
Code string `field:"code"` // 文件代号
CreatedAt uint64 `field:"createdAt"` // 上传时间
State uint8 `field:"state"` // 状态
}
type IPLibraryFileOperator struct {
Id any // ID
FileId any // 原始文件ID
Template any // 模板
EmptyValues any // 空值列表
GeneratedFileId any // 生成的文件ID
GeneratedAt any // 生成时间
IsFinished any // 是否已经完成
Countries any // 国家/地区
Provinces any // 省份
Cities any // 城市
Towns any // 区县
Providers any // ISP服务商
Code any // 文件代号
CreatedAt any // 上传时间
State any // 状态
}
func NewIPLibraryFileOperator() *IPLibraryFileOperator {
return &IPLibraryFileOperator{}
}

View File

@@ -0,0 +1,69 @@
package models
import "encoding/json"
func (this *IPLibraryFile) DecodeCountries() []string {
var countries = []string{}
if IsNotNull(this.Countries) {
err := json.Unmarshal(this.Countries, &countries)
if err != nil {
// ignore error
}
}
return countries
}
func (this *IPLibraryFile) DecodeProvinces() [][2]string {
var provinces = [][2]string{}
if IsNotNull(this.Provinces) {
err := json.Unmarshal(this.Provinces, &provinces)
if err != nil {
// ignore error
}
}
return provinces
}
func (this *IPLibraryFile) DecodeCities() [][3]string {
var cities = [][3]string{}
if IsNotNull(this.Cities) {
err := json.Unmarshal(this.Cities, &cities)
if err != nil {
// ignore error
}
}
return cities
}
func (this *IPLibraryFile) DecodeTowns() [][4]string {
var towns = [][4]string{}
if IsNotNull(this.Towns) {
err := json.Unmarshal(this.Towns, &towns)
if err != nil {
// ignore error
}
}
return towns
}
func (this *IPLibraryFile) DecodeProviders() []string {
var providers = []string{}
if IsNotNull(this.Providers) {
err := json.Unmarshal(this.Providers, &providers)
if err != nil {
// ignore error
}
}
return providers
}
func (this *IPLibraryFile) DecodeEmptyValues() []string {
var result = []string{}
if IsNotNull(this.EmptyValues) {
err := json.Unmarshal(this.EmptyValues, &result)
if err != nil {
// ignore error
}
}
return result
}

View File

@@ -1,22 +1,26 @@
package models
// IP库
// IPLibrary IP库
type IPLibrary struct {
Id uint32 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
FileId uint32 `field:"fileId"` // 文件ID
Type string `field:"type"` // 类型
Name string `field:"name"` // 名称
IsPublic bool `field:"isPublic"` // 是否公用
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
}
type IPLibraryOperator struct {
Id interface{} // ID
AdminId interface{} // 管理员ID
FileId interface{} // 文件ID
Type interface{} // 类型
State interface{} // 状态
CreatedAt interface{} // 创建时间
Id any // ID
AdminId any // 管理员ID
FileId any // 文件ID
Type any // 类型
Name any // 名称
IsPublic any // 是否公用
State any // 状态
CreatedAt any // 创建时间
}
func NewIPLibraryOperator() *IPLibraryOperator {

View File

@@ -2,11 +2,14 @@ package regions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"sort"
"strconv"
)
@@ -105,7 +108,18 @@ func (this *RegionCityDAO) CreateCity(tx *dbs.Tx, provinceId int64, name string,
return types.Int64(op.Id), nil
}
// FindCityIdWithNameCacheable 根据城市名查找城市ID
// FindCityIdWithName 根据城市名查找城市ID
func (this *RegionCityDAO) FindCityIdWithName(tx *dbs.Tx, provinceId int64, cityName string) (int64, error) {
return this.Query(tx).
Attr("provinceId", provinceId).
Where("(name=:cityName OR customName=:cityName OR JSON_CONTAINS(codes, :cityNameJSON) OR JSON_CONTAINS(customCodes, :cityNameJSON))").
Param("cityName", cityName).
Param("cityNameJSON", strconv.Quote(cityName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
}
// FindCityIdWithNameCacheable 根据城市名查找城市ID并加入缓存
func (this *RegionCityDAO) FindCityIdWithNameCacheable(tx *dbs.Tx, provinceId int64, cityName string) (int64, error) {
key := cityName + "@" + numberutils.FormatInt64(provinceId)
@@ -119,16 +133,19 @@ func (this *RegionCityDAO) FindCityIdWithNameCacheable(tx *dbs.Tx, provinceId in
cityId, err := this.Query(tx).
Attr("provinceId", provinceId).
Where("JSON_CONTAINS(codes, :cityName)").
Param("cityName", strconv.Quote(cityName)). // 查询的需要是个JSON字符串所以这里加双引号
Where("(name=:cityName OR customName=:cityName OR JSON_CONTAINS(codes, :cityNameJSON) OR JSON_CONTAINS(customCodes, :cityNameJSON))").
Param("cityName", cityName).
Param("cityNameJSON", strconv.Quote(cityName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
SharedCacheLocker.Lock()
regionCityNameAndIdCacheMap[key] = cityId
SharedCacheLocker.Unlock()
if cityId > 0 {
SharedCacheLocker.Lock()
regionCityNameAndIdCacheMap[key] = cityId
SharedCacheLocker.Unlock()
}
return cityId, nil
}
@@ -141,3 +158,75 @@ func (this *RegionCityDAO) FindAllEnabledCities(tx *dbs.Tx) (result []*RegionCit
FindAll()
return
}
// FindAllEnabledCitiesWithProvinceId 获取某个省份下的所有城市
func (this *RegionCityDAO) FindAllEnabledCitiesWithProvinceId(tx *dbs.Tx, provinceId int64) (result []*RegionCity, err error) {
_, err = this.Query(tx).
Attr("provinceId", provinceId).
State(RegionCityStateEnabled).
Slice(&result).
FindAll()
return
}
// UpdateCityCustom 自定义城市信息
func (this *RegionCityDAO) UpdateCityCustom(tx *dbs.Tx, cityId int64, customName string, customCodes []string) error {
if customCodes == nil {
customCodes = []string{}
}
customCodesJSON, err := json.Marshal(customCodes)
if err != nil {
return err
}
defer func() {
SharedCacheLocker.Lock()
regionCityNameAndIdCacheMap = map[string]int64{}
SharedCacheLocker.Unlock()
}()
return this.Query(tx).
Pk(cityId).
Set("customName", customName).
Set("customCodes", customCodesJSON).
UpdateQuickly()
}
// FindSimilarCities 查找类似城市名
func (this *RegionCityDAO) FindSimilarCities(cities []*RegionCity, cityName string, size int) (result []*RegionCity) {
if len(cities) == 0 {
return
}
var similarResult = []maps.Map{}
for _, city := range cities {
var similarityList = []float32{}
for _, code := range city.AllCodes() {
var similarity = utils.Similar(cityName, code)
if similarity > 0 {
similarityList = append(similarityList, similarity)
}
}
if len(similarityList) > 0 {
similarResult = append(similarResult, maps.Map{
"similarity": numberutils.Max(similarityList...),
"city": city,
})
}
}
sort.Slice(similarResult, func(i, j int) bool {
return similarResult[i].GetFloat32("similarity") > similarResult[j].GetFloat32("similarity")
})
if len(similarResult) > size {
similarResult = similarResult[:size]
}
for _, r := range similarResult {
result = append(result, r.Get("city").(*RegionCity))
}
return
}

View File

@@ -2,23 +2,27 @@ package regions
import "github.com/iwind/TeaGo/dbs"
// RegionCity 区域城市
// RegionCity 区域-城市
type RegionCity struct {
Id uint32 `field:"id"` // ID
ProvinceId uint32 `field:"provinceId"` // 省份ID
Name string `field:"name"` // 名称
Codes dbs.JSON `field:"codes"` // 代号
State uint8 `field:"state"` // 状态
DataId string `field:"dataId"` // 原始数据ID
Id uint32 `field:"id"` // ID
ProvinceId uint32 `field:"provinceId"` // 省份ID
Name string `field:"name"` // 名称
Codes dbs.JSON `field:"codes"` // 代号
CustomName string `field:"customName"` // 自定义名称
CustomCodes dbs.JSON `field:"customCodes"` // 自定义代号
State uint8 `field:"state"` // 状态
DataId string `field:"dataId"` // 原始数据ID
}
type RegionCityOperator struct {
Id interface{} // ID
ProvinceId interface{} // 省份ID
Name interface{} // 名称
Codes interface{} // 代号
State interface{} // 状态
DataId interface{} // 原始数据ID
Id interface{} // ID
ProvinceId interface{} // 省份ID
Name interface{} // 名称
Codes interface{} // 代号
CustomName interface{} // 自定义名称
CustomCodes interface{} // 自定义代号
State interface{} // 状态
DataId interface{} // 原始数据ID
}
func NewRegionCityOperator() *RegionCityOperator {

View File

@@ -2,17 +2,56 @@ package regions
import (
"encoding/json"
"github.com/iwind/TeaGo/logs"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/iwind/TeaGo/lists"
)
func (this *RegionCity) DecodeCodes() []string {
if len(this.Codes) == 0 {
return []string{}
}
result := []string{}
var result = []string{}
err := json.Unmarshal(this.Codes, &result)
if err != nil {
logs.Error(err)
remotelogs.Error("RegionCity.DecodeCodes", err.Error())
}
return result
}
func (this *RegionCity) DecodeCustomCodes() []string {
if len(this.CustomCodes) == 0 {
return []string{}
}
var result = []string{}
err := json.Unmarshal(this.CustomCodes, &result)
if err != nil {
remotelogs.Error("RegionCity.DecodeCustomCodes", err.Error())
}
return result
}
func (this *RegionCity) DisplayName() string {
if len(this.CustomName) > 0 {
return this.CustomName
}
return this.Name
}
func (this *RegionCity) AllCodes() []string {
var codes = this.DecodeCodes()
if len(this.Name) > 0 && !lists.ContainsString(codes, this.Name) {
codes = append(codes, this.Name)
}
if len(this.CustomName) > 0 && !lists.ContainsString(codes, this.CustomName) {
codes = append(codes, this.CustomName)
}
for _, code := range this.DecodeCustomCodes() {
if !lists.ContainsString(codes, code) {
codes = append(codes, code)
}
}
return codes
}

View File

@@ -2,11 +2,15 @@ package regions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/mozillazg/go-pinyin"
"sort"
"strconv"
"strings"
)
@@ -16,6 +20,10 @@ const (
RegionCountryStateDisabled = 0 // 已禁用
)
const (
CountryChinaId = 1
)
var regionCountryNameAndIdCacheMap = map[string]int64{} // country name => id
var regionCountryIdAndNameCacheMap = map[int64]string{} // country id => name
@@ -88,7 +96,9 @@ func (this *RegionCountryDAO) FindRegionCountryName(tx *dbs.Tx, id int64) (strin
return "", err
}
regionCountryIdAndNameCacheMap[id] = name
if len(name) > 0 {
regionCountryIdAndNameCacheMap[id] = name
}
return name, nil
}
@@ -103,8 +113,9 @@ func (this *RegionCountryDAO) FindCountryIdWithDataId(tx *dbs.Tx, dataId string)
// FindCountryIdWithName 根据国家名查找国家ID
func (this *RegionCountryDAO) FindCountryIdWithName(tx *dbs.Tx, countryName string) (int64, error) {
return this.Query(tx).
Where("JSON_CONTAINS(codes, :countryName)").
Param("countryName", strconv.Quote(countryName)). // 查询的需要是个JSON字符串所以这里加双引号
Where("(name=:countryName OR JSON_CONTAINS(codes, :countryNameJSON) OR customName=:countryName OR JSON_CONTAINS(customCodes, :countryNameJSON))").
Param("countryName", countryName).
Param("countryNameJSON", strconv.Quote(countryName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
}
@@ -124,9 +135,11 @@ func (this *RegionCountryDAO) FindCountryIdWithNameCacheable(tx *dbs.Tx, country
return 0, err
}
SharedCacheLocker.Lock()
regionCountryNameAndIdCacheMap[countryName] = countryId
SharedCacheLocker.Unlock()
if countryId > 0 {
SharedCacheLocker.Lock()
regionCountryNameAndIdCacheMap[countryName] = countryId
SharedCacheLocker.Unlock()
}
return countryId, nil
}
@@ -160,7 +173,7 @@ func (this *RegionCountryDAO) CreateCountry(tx *dbs.Tx, name string, dataId stri
return types.Int64(op.Id), nil
}
// FindAllEnabledCountriesOrderByPinyin 查找所有可用的国家
// FindAllEnabledCountriesOrderByPinyin 查找所有可用的国家并按拼音排序
func (this *RegionCountryDAO) FindAllEnabledCountriesOrderByPinyin(tx *dbs.Tx) (result []*RegionCountry, err error) {
_, err = this.Query(tx).
State(RegionCountryStateEnabled).
@@ -169,3 +182,76 @@ func (this *RegionCountryDAO) FindAllEnabledCountriesOrderByPinyin(tx *dbs.Tx) (
FindAll()
return
}
// FindAllCountries 查找所有可用的国家
func (this *RegionCountryDAO) FindAllCountries(tx *dbs.Tx) (result []*RegionCountry, err error) {
_, err = this.Query(tx).
State(RegionCountryStateEnabled).
Slice(&result).
AscPk().
FindAll()
return
}
// UpdateCountryCustom 修改国家/地区自定义
func (this *RegionCountryDAO) UpdateCountryCustom(tx *dbs.Tx, countryId int64, customName string, customCodes []string) error {
if customCodes == nil {
customCodes = []string{}
}
customCodesJSON, err := json.Marshal(customCodes)
if err != nil {
return err
}
defer func() {
SharedCacheLocker.Lock()
regionCountryNameAndIdCacheMap = map[string]int64{}
regionCountryIdAndNameCacheMap = map[int64]string{}
SharedCacheLocker.Unlock()
}()
return this.Query(tx).
Pk(countryId).
Set("customName", customName).
Set("customCodes", customCodesJSON).
UpdateQuickly()
}
// FindSimilarCountries 查找类似国家/地区名
func (this *RegionCountryDAO) FindSimilarCountries(countries []*RegionCountry, countryName string, size int) (result []*RegionCountry) {
if len(countries) == 0 {
return
}
var similarResult = []maps.Map{}
for _, country := range countries {
var similarityList = []float32{}
for _, code := range country.AllCodes() {
var similarity = utils.Similar(countryName, code)
if similarity > 0 {
similarityList = append(similarityList, similarity)
}
}
if len(similarityList) > 0 {
similarResult = append(similarResult, maps.Map{
"similarity": numberutils.Max(similarityList...),
"country": country,
})
}
}
sort.Slice(similarResult, func(i, j int) bool {
return similarResult[i].GetFloat32("similarity") > similarResult[j].GetFloat32("similarity")
})
if len(similarResult) > size {
similarResult = similarResult[:size]
}
for _, r := range similarResult {
result = append(result, r.Get("country").(*RegionCountry))
}
return
}

View File

@@ -8,11 +8,29 @@ import (
"time"
)
func TestRegionCountryDAO_FindCountryIdWithName(t *testing.T) {
dbs.NotifyReady()
for _, name := range []string{
"中国",
"中华人民共和国",
"美国",
"美利坚合众国",
"美利坚",
} {
countryId, err := SharedRegionCountryDAO.FindCountryIdWithName(nil, name)
if err != nil {
t.Fatal(err)
}
t.Log(name, ":", countryId)
}
}
func TestRegionCountryDAO_FindCountryIdWithCountryNameCacheable(t *testing.T) {
dbs.NotifyReady()
for i := 0; i < 5; i++ {
now := time.Now()
var now = time.Now()
countryId, err := SharedRegionCountryDAO.FindCountryIdWithNameCacheable(nil, "中国")
if err != nil {
t.Fatal(err)
@@ -20,3 +38,24 @@ func TestRegionCountryDAO_FindCountryIdWithCountryNameCacheable(t *testing.T) {
t.Log("countryId", countryId, time.Since(now).Seconds()*1000, "ms")
}
}
func TestRegionCountryDAO_FindSimilarCountries(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
countries, err := SharedRegionCountryDAO.FindAllCountries(tx)
if err != nil {
t.Fatal(err)
}
for _, countryName := range []string{"中国", "布基纳法索", "哥伦比亚", "德意志共和国", "美利坚", "刚果金"} {
t.Log("====" + countryName + "====")
var countries = SharedRegionCountryDAO.FindSimilarCountries(countries, countryName, 5)
if err != nil {
t.Fatal(err)
}
for _, country := range countries {
t.Log(country.Name, country.AllCodes())
}
}
}

View File

@@ -2,23 +2,27 @@ package regions
import "github.com/iwind/TeaGo/dbs"
// RegionCountry 区域国家|地区
// RegionCountry 区域-国家/地区
type RegionCountry struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 名称
Codes dbs.JSON `field:"codes"` // 代号
State uint8 `field:"state"` // 状态
DataId string `field:"dataId"` // 原始数据ID
Pinyin dbs.JSON `field:"pinyin"` // 拼音
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 名称
Codes dbs.JSON `field:"codes"` // 代号
CustomName string `field:"customName"` // 自定义名称
CustomCodes dbs.JSON `field:"customCodes"` // 自定义代号
State uint8 `field:"state"` // 状态
DataId string `field:"dataId"` // 原始数据ID
Pinyin dbs.JSON `field:"pinyin"` // 拼音
}
type RegionCountryOperator struct {
Id interface{} // ID
Name interface{} // 名称
Codes interface{} // 代号
State interface{} // 状态
DataId interface{} // 原始数据ID
Pinyin interface{} // 拼音
Id interface{} // ID
Name interface{} // 名称
Codes interface{} // 代号
CustomName interface{} // 自定义名称
CustomCodes interface{} // 自定义代号
State interface{} // 状态
DataId interface{} // 原始数据ID
Pinyin interface{} // 拼音
}
func NewRegionCountryOperator() *RegionCountryOperator {

View File

@@ -2,17 +2,56 @@ package regions
import (
"encoding/json"
"github.com/iwind/TeaGo/logs"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/iwind/TeaGo/lists"
)
func (this *RegionCountry) DecodeCodes() []string {
if len(this.Codes) == 0 {
return []string{}
}
result := []string{}
var result = []string{}
err := json.Unmarshal(this.Codes, &result)
if err != nil {
logs.Error(err)
remotelogs.Error("RegionCountry.DecodeCodes", err.Error())
}
return result
}
func (this *RegionCountry) DecodeCustomCodes() []string {
if len(this.CustomCodes) == 0 {
return []string{}
}
var result = []string{}
err := json.Unmarshal(this.CustomCodes, &result)
if err != nil {
remotelogs.Error("RegionCountry.DecodeCustomCodes", err.Error())
}
return result
}
func (this *RegionCountry) DisplayName() string {
if len(this.CustomName) > 0 {
return this.CustomName
}
return this.Name
}
func (this *RegionCountry) AllCodes() []string {
var codes = this.DecodeCodes()
if len(this.Name) > 0 && !lists.ContainsString(codes, this.Name) {
codes = append(codes, this.Name)
}
if len(this.CustomName) > 0 && !lists.ContainsString(codes, this.CustomName) {
codes = append(codes, this.CustomName)
}
for _, code := range this.DecodeCustomCodes() {
if !lists.ContainsString(codes, code) {
codes = append(codes, code)
}
}
return codes
}

View File

@@ -2,9 +2,13 @@ package regions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"sort"
"strconv"
)
@@ -74,7 +78,17 @@ func (this *RegionProviderDAO) FindRegionProviderName(tx *dbs.Tx, id uint32) (st
FindStringCol("")
}
// FindProviderIdWithNameCacheable 根据服务商名称查找服务商ID
// FindProviderIdWithName 根据服务商名称查找服务商ID
func (this *RegionProviderDAO) FindProviderIdWithName(tx *dbs.Tx, providerName string) (int64, error) {
return this.Query(tx).
Where("(name=:providerName OR customName=:providerName OR JSON_CONTAINS(codes, :providerNameJSON) OR JSON_CONTAINS(customCodes, :providerNameJSON))").
Param("providerName", providerName).
Param("providerNameJSON", strconv.Quote(providerName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
}
// FindProviderIdWithNameCacheable 根据服务商名称查找服务商ID并保存进缓存
func (this *RegionProviderDAO) FindProviderIdWithNameCacheable(tx *dbs.Tx, providerName string) (int64, error) {
SharedCacheLocker.RLock()
providerId, ok := regionProviderNameAndIdCacheMap[providerName]
@@ -85,17 +99,20 @@ func (this *RegionProviderDAO) FindProviderIdWithNameCacheable(tx *dbs.Tx, provi
SharedCacheLocker.RUnlock()
providerId, err := this.Query(tx).
Where("JSON_CONTAINS(codes, :providerName)").
Param("providerName", strconv.Quote(providerName)). // 查询的需要是个JSON字符串所以这里加双引号
Where("(name=:providerName OR customName=:providerName OR JSON_CONTAINS(codes, :providerNameJSON) OR JSON_CONTAINS(customCodes, :providerNameJSON))").
Param("providerName", providerName).
Param("providerNameJSON", strconv.Quote(providerName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
SharedCacheLocker.Lock()
regionProviderNameAndIdCacheMap[providerName] = providerId
SharedCacheLocker.Unlock()
if providerId > 0 {
SharedCacheLocker.Lock()
regionProviderNameAndIdCacheMap[providerName] = providerId
SharedCacheLocker.Unlock()
}
return providerId, nil
}
@@ -121,3 +138,65 @@ func (this *RegionProviderDAO) FindAllEnabledProviders(tx *dbs.Tx) (result []*Re
FindAll()
return
}
// UpdateProviderCustom 修改ISP自定义信息
func (this *RegionProviderDAO) UpdateProviderCustom(tx *dbs.Tx, providerId int64, customName string, customCodes []string) error {
if customCodes == nil {
customCodes = []string{}
}
customCodesJSON, err := json.Marshal(customCodes)
if err != nil {
return err
}
defer func() {
SharedCacheLocker.Lock()
regionProviderNameAndIdCacheMap = map[string]int64{}
SharedCacheLocker.Unlock()
}()
return this.Query(tx).
Pk(providerId).
Set("customName", customName).
Set("customCodes", customCodesJSON).
UpdateQuickly()
}
// FindSimilarProviders 查找类似ISP运营商
func (this *RegionProviderDAO) FindSimilarProviders(providers []*RegionProvider, providerName string, size int) (result []*RegionProvider) {
if len(providers) == 0 {
return
}
var similarResult = []maps.Map{}
for _, provider := range providers {
var similarityList = []float32{}
for _, code := range provider.AllCodes() {
var similarity = utils.Similar(providerName, code)
if similarity > 0 {
similarityList = append(similarityList, similarity)
}
}
if len(similarityList) > 0 {
similarResult = append(similarResult, maps.Map{
"similarity": numberutils.Max(similarityList...),
"provider": provider,
})
}
}
sort.Slice(similarResult, func(i, j int) bool {
return similarResult[i].GetFloat32("similarity") > similarResult[j].GetFloat32("similarity")
})
if len(similarResult) > size {
similarResult = similarResult[:size]
}
for _, r := range similarResult {
result = append(result, r.Get("provider").(*RegionProvider))
}
return
}

View File

@@ -2,19 +2,23 @@ package regions
import "github.com/iwind/TeaGo/dbs"
// RegionProvider 区域ISP
// RegionProvider 区域-运营商
type RegionProvider struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 名称
Codes dbs.JSON `field:"codes"` // 代号
State uint8 `field:"state"` // 状态
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 名称
Codes dbs.JSON `field:"codes"` // 代号
CustomName string `field:"customName"` // 自定义名称
CustomCodes dbs.JSON `field:"customCodes"` // 自定义代号
State uint8 `field:"state"` // 状态
}
type RegionProviderOperator struct {
Id interface{} // ID
Name interface{} // 名称
Codes interface{} // 代号
State interface{} // 状态
Id interface{} // ID
Name interface{} // 名称
Codes interface{} // 代号
CustomName interface{} // 自定义名称
CustomCodes interface{} // 自定义代号
State interface{} // 状态
}
func NewRegionProviderOperator() *RegionProviderOperator {

View File

@@ -2,17 +2,56 @@ package regions
import (
"encoding/json"
"github.com/iwind/TeaGo/logs"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/iwind/TeaGo/lists"
)
func (this *RegionProvider) DecodeCodes() []string {
if len(this.Codes) == 0 {
return []string{}
}
result := []string{}
var result = []string{}
err := json.Unmarshal(this.Codes, &result)
if err != nil {
logs.Error(err)
remotelogs.Error("RegionProvider.DecodeCodes", err.Error())
}
return result
}
func (this *RegionProvider) DecodeCustomCodes() []string {
if len(this.CustomCodes) == 0 {
return []string{}
}
var result = []string{}
err := json.Unmarshal(this.CustomCodes, &result)
if err != nil {
remotelogs.Error("RegionProvider.DecodeCustomCodes", err.Error())
}
return result
}
func (this *RegionProvider) DisplayName() string {
if len(this.CustomName) > 0 {
return this.CustomName
}
return this.Name
}
func (this *RegionProvider) AllCodes() []string {
var codes = this.DecodeCodes()
if len(this.Name) > 0 && !lists.ContainsString(codes, this.Name) {
codes = append(codes, this.Name)
}
if len(this.CustomName) > 0 && !lists.ContainsString(codes, this.CustomName) {
codes = append(codes, this.CustomName)
}
for _, code := range this.DecodeCustomCodes() {
if !lists.ContainsString(codes, code) {
codes = append(codes, code)
}
}
return codes
}

View File

@@ -2,11 +2,14 @@ package regions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"sort"
"strconv"
)
@@ -38,7 +41,7 @@ func init() {
})
}
// 启用条目
// EnableRegionProvince 启用条目
func (this *RegionProvinceDAO) EnableRegionProvince(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -47,7 +50,7 @@ func (this *RegionProvinceDAO) EnableRegionProvince(tx *dbs.Tx, id int64) error
return err
}
// 禁用条目
// DisableRegionProvince 禁用条目
func (this *RegionProvinceDAO) DisableRegionProvince(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -56,7 +59,7 @@ func (this *RegionProvinceDAO) DisableRegionProvince(tx *dbs.Tx, id int64) error
return err
}
// 查找启用中的条目
// FindEnabledRegionProvince 查找启用中的条目
func (this *RegionProvinceDAO) FindEnabledRegionProvince(tx *dbs.Tx, id int64) (*RegionProvince, error) {
result, err := this.Query(tx).
Pk(id).
@@ -68,7 +71,7 @@ func (this *RegionProvinceDAO) FindEnabledRegionProvince(tx *dbs.Tx, id int64) (
return result.(*RegionProvince), err
}
// 根据主键查找名称
// FindRegionProvinceName 根据主键查找名称
func (this *RegionProvinceDAO) FindRegionProvinceName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
@@ -76,7 +79,7 @@ func (this *RegionProvinceDAO) FindRegionProvinceName(tx *dbs.Tx, id int64) (str
FindStringCol("")
}
// 根据数据ID查找省份
// FindProvinceIdWithDataId 根据数据ID查找省份
func (this *RegionProvinceDAO) FindProvinceIdWithDataId(tx *dbs.Tx, dataId string) (int64, error) {
return this.Query(tx).
Attr("dataId", dataId).
@@ -84,17 +87,18 @@ func (this *RegionProvinceDAO) FindProvinceIdWithDataId(tx *dbs.Tx, dataId strin
FindInt64Col(0)
}
// 根据省份名查找省份ID
// FindProvinceIdWithName 根据省份名查找省份ID
func (this *RegionProvinceDAO) FindProvinceIdWithName(tx *dbs.Tx, countryId int64, provinceName string) (int64, error) {
return this.Query(tx).
Attr("countryId", countryId).
Where("JSON_CONTAINS(codes, :provinceName)").
Param("provinceName", strconv.Quote(provinceName)). // 查询的需要是个JSON字符串所以这里加双引号
Where("(name=:provinceName OR customName=:provinceName OR JSON_CONTAINS(codes, :provinceNameJSON) OR JSON_CONTAINS(customCodes, :provinceNameJSON))").
Param("provinceName", provinceName).
Param("provinceNameJSON", strconv.Quote(provinceName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
}
// 根据省份名查找省份ID并可使用缓存
// FindProvinceIdWithNameCacheable 根据省份名查找省份ID并可使用缓存
func (this *RegionProvinceDAO) FindProvinceIdWithNameCacheable(tx *dbs.Tx, countryId int64, provinceName string) (int64, error) {
var key = provinceName + "@" + numberutils.FormatInt64(countryId)
@@ -110,14 +114,16 @@ func (this *RegionProvinceDAO) FindProvinceIdWithNameCacheable(tx *dbs.Tx, count
if err != nil {
return 0, err
}
SharedCacheLocker.Lock()
regionProvinceNameAndIdCacheMap[key] = provinceId
SharedCacheLocker.Unlock()
if provinceId > 0 {
SharedCacheLocker.Lock()
regionProvinceNameAndIdCacheMap[key] = provinceId
SharedCacheLocker.Unlock()
}
return provinceId, nil
}
// 创建省份
// CreateProvince 创建省份
func (this *RegionProvinceDAO) CreateProvince(tx *dbs.Tx, countryId int64, name string, dataId string) (int64, error) {
var op = NewRegionProvinceOperator()
op.CountryId = countryId
@@ -138,7 +144,7 @@ func (this *RegionProvinceDAO) CreateProvince(tx *dbs.Tx, countryId int64, name
return types.Int64(op.Id), nil
}
// 查找所有省份
// FindAllEnabledProvincesWithCountryId 查找某个国家/地区的所有省份
func (this *RegionProvinceDAO) FindAllEnabledProvincesWithCountryId(tx *dbs.Tx, countryId int64) (result []*RegionProvince, err error) {
_, err = this.Query(tx).
State(RegionProvinceStateEnabled).
@@ -148,3 +154,76 @@ func (this *RegionProvinceDAO) FindAllEnabledProvincesWithCountryId(tx *dbs.Tx,
FindAll()
return
}
// FindAllEnabledProvinces 查找所有省份
func (this *RegionProvinceDAO) FindAllEnabledProvinces(tx *dbs.Tx) (result []*RegionProvince, err error) {
_, err = this.Query(tx).
State(RegionProvinceStateEnabled).
Asc().
Slice(&result).
FindAll()
return
}
// UpdateProvinceCustom 修改自定义省份信息
func (this *RegionProvinceDAO) UpdateProvinceCustom(tx *dbs.Tx, provinceId int64, customName string, customCodes []string) error {
if customCodes == nil {
customCodes = []string{}
}
customCodesJSON, err := json.Marshal(customCodes)
if err != nil {
return err
}
// 清空缓存
defer func() {
SharedCacheLocker.Lock()
regionProvinceNameAndIdCacheMap = map[string]int64{}
SharedCacheLocker.Unlock()
}()
return this.Query(tx).
Pk(provinceId).
Set("customName", customName).
Set("customCodes", customCodesJSON).
UpdateQuickly()
}
// FindSimilarProvinces 查找类似省份名
func (this *RegionProvinceDAO) FindSimilarProvinces(provinces []*RegionProvince, provinceName string, size int) (result []*RegionProvince) {
if len(provinces) == 0 {
return
}
var similarResult = []maps.Map{}
for _, province := range provinces {
var similarityList = []float32{}
for _, code := range province.AllCodes() {
var similarity = utils.Similar(provinceName, code)
if similarity > 0 {
similarityList = append(similarityList, similarity)
}
}
if len(similarityList) > 0 {
similarResult = append(similarResult, maps.Map{
"similarity": numberutils.Max(similarityList...),
"province": province,
})
}
}
sort.Slice(similarResult, func(i, j int) bool {
return similarResult[i].GetFloat32("similarity") > similarResult[j].GetFloat32("similarity")
})
if len(similarResult) > size {
similarResult = similarResult[:size]
}
for _, r := range similarResult {
result = append(result, r.Get("province").(*RegionProvince))
}
return
}

View File

@@ -7,7 +7,7 @@ import (
"time"
)
func TestRegionProvinceDAO_FindProvinceIdWithProvinceName(t *testing.T) {
func TestRegionProvinceDAO_FindProvinceIdWithNameCacheable(t *testing.T) {
dbs.NotifyReady()
for i := 0; i < 5; i++ {
@@ -29,3 +29,51 @@ func TestRegionProvinceDAO_FindProvinceIdWithProvinceName(t *testing.T) {
t.Log(provinceId, time.Since(now).Seconds()*1000, "ms")
}
}
func TestRegionProvinceDAO_FindProvinceIdWithName(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
for _, name := range []string{
"安徽",
"安徽省",
"广西",
"广西省",
"广西壮族自治区",
"皖",
} {
provinceId, err := SharedRegionProvinceDAO.FindProvinceIdWithName(tx, 1, name)
if err != nil {
t.Fatal(err)
}
t.Log(name, "=>", provinceId)
}
}
func TestRegionProvinceDAO_FindSimilarProvinces(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
var countryId int64 = 1
provinces, err := SharedRegionProvinceDAO.FindAllEnabledProvincesWithCountryId(tx, countryId)
if err != nil {
t.Fatal(err)
}
for _, provinceName := range []string{
"北京",
"北京市",
"安徽",
"安徽省",
"大北京",
} {
t.Log("====" + provinceName + "====")
var provinces = SharedRegionProvinceDAO.FindSimilarProvinces(provinces, provinceName, 5)
if err != nil {
t.Fatal(err)
}
for _, province := range provinces {
t.Log(province.Name, province.AllCodes())
}
}
}

View File

@@ -2,23 +2,27 @@ package regions
import "github.com/iwind/TeaGo/dbs"
// RegionProvince 区域省份
// RegionProvince 区域-省份
type RegionProvince struct {
Id uint32 `field:"id"` // ID
CountryId uint32 `field:"countryId"` // 国家ID
Name string `field:"name"` // 名称
Codes dbs.JSON `field:"codes"` // 代号
State uint8 `field:"state"` // 状态
DataId string `field:"dataId"` // 原始数据ID
Id uint32 `field:"id"` // ID
CountryId uint32 `field:"countryId"` // 国家ID
Name string `field:"name"` // 名称
Codes dbs.JSON `field:"codes"` // 代号
CustomName string `field:"customName"` // 自定义名称
CustomCodes dbs.JSON `field:"customCodes"` // 自定义代号
State uint8 `field:"state"` // 状态
DataId string `field:"dataId"` // 原始数据ID
}
type RegionProvinceOperator struct {
Id interface{} // ID
CountryId interface{} // 国家ID
Name interface{} // 名称
Codes interface{} // 代号
State interface{} // 状态
DataId interface{} // 原始数据ID
Id interface{} // ID
CountryId interface{} // 国家ID
Name interface{} // 名称
Codes interface{} // 代号
CustomName interface{} // 自定义名称
CustomCodes interface{} // 自定义代号
State interface{} // 状态
DataId interface{} // 原始数据ID
}
func NewRegionProvinceOperator() *RegionProvinceOperator {

View File

@@ -2,17 +2,56 @@ package regions
import (
"encoding/json"
"github.com/iwind/TeaGo/logs"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/iwind/TeaGo/lists"
)
func (this *RegionProvince) DecodeCodes() []string {
if len(this.Codes) == 0 {
return []string{}
}
result := []string{}
var result = []string{}
err := json.Unmarshal(this.Codes, &result)
if err != nil {
logs.Error(err)
remotelogs.Error("RegionProvince.DecodeCodes", err.Error())
}
return result
}
func (this *RegionProvince) DecodeCustomCodes() []string {
if len(this.CustomCodes) == 0 {
return []string{}
}
var result = []string{}
err := json.Unmarshal(this.CustomCodes, &result)
if err != nil {
remotelogs.Error("RegionProvince.DecodeCustomCodes", err.Error())
}
return result
}
func (this *RegionProvince) DisplayName() string {
if len(this.CustomName) > 0 {
return this.CustomName
}
return this.Name
}
func (this *RegionProvince) AllCodes() []string {
var codes = this.DecodeCodes()
if len(this.Name) > 0 && !lists.ContainsString(codes, this.Name) {
codes = append(codes, this.Name)
}
if len(this.CustomName) > 0 && !lists.ContainsString(codes, this.CustomName) {
codes = append(codes, this.CustomName)
}
for _, code := range this.DecodeCustomCodes() {
if !lists.ContainsString(codes, code) {
codes = append(codes, code)
}
}
return codes
}

View File

@@ -0,0 +1,131 @@
package regions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"sort"
)
const (
RegionTownStateEnabled = 1 // 已启用
RegionTownStateDisabled = 0 // 已禁用
)
type RegionTownDAO dbs.DAO
func NewRegionTownDAO() *RegionTownDAO {
return dbs.NewDAO(&RegionTownDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeRegionTowns",
Model: new(RegionTown),
PkName: "id",
},
}).(*RegionTownDAO)
}
var SharedRegionTownDAO *RegionTownDAO
func init() {
dbs.OnReady(func() {
SharedRegionTownDAO = NewRegionTownDAO()
})
}
// EnableRegionTown 启用条目
func (this *RegionTownDAO) EnableRegionTown(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", RegionTownStateEnabled).
Update()
return err
}
// DisableRegionTown 禁用条目
func (this *RegionTownDAO) DisableRegionTown(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", RegionTownStateDisabled).
Update()
return err
}
// FindEnabledRegionTown 查找启用中的条目
func (this *RegionTownDAO) FindEnabledRegionTown(tx *dbs.Tx, id uint32) (*RegionTown, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", RegionTownStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*RegionTown), err
}
// FindRegionTownName 根据主键查找名称
func (this *RegionTownDAO) FindRegionTownName(tx *dbs.Tx, id uint32) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// UpdateTownCustom 修改自定义县级信息
func (this *RegionTownDAO) UpdateTownCustom(tx *dbs.Tx, townId int64, customName string, customCodes []string) error {
if customCodes == nil {
customCodes = []string{}
}
customCodesJSON, err := json.Marshal(customCodes)
if err != nil {
return err
}
return this.Query(tx).
Pk(townId).
Set("customName", customName).
Set("customCodes", customCodesJSON).
UpdateQuickly()
}
// FindSimilarTowns 查找类似区县
func (this *RegionTownDAO) FindSimilarTowns(towns []*RegionTown, townName string, size int) (result []*RegionTown) {
if len(towns) == 0 {
return
}
var similarResult = []maps.Map{}
for _, town := range towns {
var similarityList = []float32{}
for _, code := range town.AllCodes() {
var similarity = utils.Similar(townName, code)
if similarity > 0 {
similarityList = append(similarityList, similarity)
}
}
if len(similarityList) > 0 {
similarResult = append(similarResult, maps.Map{
"similarity": numberutils.Max(similarityList...),
"town": town,
})
}
}
sort.Slice(similarResult, func(i, j int) bool {
return similarResult[i].GetFloat32("similarity") > similarResult[j].GetFloat32("similarity")
})
if len(similarResult) > size {
similarResult = similarResult[:size]
}
for _, r := range similarResult {
result = append(result, r.Get("town").(*RegionTown))
}
return
}

View File

@@ -0,0 +1,6 @@
package regions_test
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,30 @@
package regions
import "github.com/iwind/TeaGo/dbs"
// RegionTown 区域-省份
type RegionTown struct {
Id uint32 `field:"id"` // ID
CityId uint32 `field:"cityId"` // 城市ID
Name string `field:"name"` // 名称
Codes dbs.JSON `field:"codes"` // 代号
CustomName string `field:"customName"` // 自定义名称
CustomCodes dbs.JSON `field:"customCodes"` // 自定义代号
State uint8 `field:"state"` // 状态
DataId string `field:"dataId"` // 原始数据ID
}
type RegionTownOperator struct {
Id interface{} // ID
CityId interface{} // 城市ID
Name interface{} // 名称
Codes interface{} // 代号
CustomName interface{} // 自定义名称
CustomCodes interface{} // 自定义代号
State interface{} // 状态
DataId interface{} // 原始数据ID
}
func NewRegionTownOperator() *RegionTownOperator {
return &RegionTownOperator{}
}

View File

@@ -0,0 +1,57 @@
package regions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/iwind/TeaGo/lists"
)
func (this *RegionTown) DecodeCodes() []string {
if len(this.Codes) == 0 {
return []string{}
}
var result = []string{}
err := json.Unmarshal(this.Codes, &result)
if err != nil {
remotelogs.Error("RegionTown.DecodeCodes", err.Error())
}
return result
}
func (this *RegionTown) DecodeCustomCodes() []string {
if len(this.CustomCodes) == 0 {
return []string{}
}
var result = []string{}
err := json.Unmarshal(this.CustomCodes, &result)
if err != nil {
remotelogs.Error("RegionTown.DecodeCustomCodes", err.Error())
}
return result
}
func (this *RegionTown) DisplayName() string {
if len(this.CustomName) > 0 {
return this.CustomName
}
return this.Name
}
func (this *RegionTown) AllCodes() []string {
var codes = this.DecodeCodes()
if len(this.Name) > 0 && !lists.ContainsString(codes, this.Name) {
codes = append(codes, this.Name)
}
if len(this.CustomName) > 0 && !lists.ContainsString(codes, this.CustomName) {
codes = append(codes, this.CustomName)
}
for _, code := range this.DecodeCustomCodes() {
if !lists.ContainsString(codes, code) {
codes = append(codes, code)
}
}
return codes
}