feat(server): 添加服务器地理位置信息字段
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m3s

为服务器模型添加经度、纬度及中心点坐标字段,并在相关逻辑中处理这些字段
同时修复服务器用户列表缓存功能
This commit is contained in:
shanshanzhong 2025-11-03 23:50:23 -08:00
parent 071bb1940d
commit 15f4e69dc3
12 changed files with 2689 additions and 58 deletions

View File

@ -47,6 +47,10 @@ type (
Tags []string `json:"tags"` Tags []string `json:"tags"`
Country string `json:"country"` Country string `json:"country"`
City string `json:"city"` City string `json:"city"`
Longitude string `json:"longitude"`
Latitude string `json:"latitude"`
LatitudeCenter string `json:"latitude_center"`
LongitudeCenter string `json:"longitude_center"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
} }
) )

View File

@ -0,0 +1,67 @@
-- Add longitude if not exists
SET @col_exists := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'servers'
AND COLUMN_NAME = 'longitude'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `servers` ADD COLUMN `longitude` VARCHAR(255) DEFAULT '''' COMMENT ''longitude''',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Add latitude if not exists
SET @col_exists := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'servers'
AND COLUMN_NAME = 'latitude'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `servers` ADD COLUMN `latitude` VARCHAR(255) DEFAULT '''' COMMENT ''latitude''',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Add longitude_center if not exists
SET @col_exists := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'servers'
AND COLUMN_NAME = 'longitude_center'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `servers` ADD COLUMN `longitude_center` VARCHAR(255) DEFAULT '''' COMMENT ''longitude center''',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Add latitude_center if not exists
SET @col_exists := (
SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'servers'
AND COLUMN_NAME = 'latitude_center'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `servers` ADD COLUMN `latitude_center` VARCHAR(255) DEFAULT '''' COMMENT ''latitude center''',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -99,6 +99,10 @@ func (l *CreateServerLogic) CreateServer(req *types.CreateServerRequest) error {
} else { } else {
data.City = result.City data.City = result.City
data.Country = result.Country data.Country = result.Country
data.Latitude = result.Latitude
data.Longitude = result.Longitude
data.LatitudeCenter = result.LatitudeCenter
data.LongitudeCenter = result.LongitudeCenter
} }
} }
err = l.svcCtx.NodeModel.InsertServer(l.ctx, &data) err = l.svcCtx.NodeModel.InsertServer(l.ctx, &data)

View File

@ -47,6 +47,10 @@ func (l *UpdateServerLogic) UpdateServer(req *types.UpdateServerRequest) error {
} else { } else {
data.City = result.City data.City = result.City
data.Country = result.Country data.Country = result.Country
data.Latitude = result.Latitude
data.Longitude = result.Longitude
data.LatitudeCenter = result.LatitudeCenter
data.LongitudeCenter = result.LongitudeCenter
} }
// update address // update address
data.Address = req.Address data.Address = req.Address

View File

@ -137,17 +137,21 @@ func (l *QueryUserSubscribeNodeListLogic) getServers(userSub *user.Subscribe) (u
continue continue
} }
userSubscribeNode := &types.UserSubscribeNodeInfo{ userSubscribeNode := &types.UserSubscribeNodeInfo{
Id: n.Id, Id: n.Id,
Name: n.Name, Name: n.Name,
Uuid: userSub.UUID, Uuid: userSub.UUID,
Protocol: n.Protocol, Protocol: n.Protocol,
Protocols: server.Protocols, Protocols: server.Protocols,
Port: n.Port, Port: n.Port,
Address: n.Address, Address: n.Address,
Tags: strings.Split(n.Tags, ","), Tags: strings.Split(n.Tags, ","),
Country: server.Country, Country: server.Country,
City: server.City, City: server.City,
CreatedAt: n.CreatedAt.Unix(), Latitude: server.Latitude,
Longitude: server.Longitude,
LongitudeCenter: server.LongitudeCenter,
LatitudeCenter: server.LatitudeCenter,
CreatedAt: n.CreatedAt.Unix(),
} }
userSubscribeNodes = append(userSubscribeNodes, userSubscribeNode) userSubscribeNodes = append(userSubscribeNodes, userSubscribeNode)
} }

View File

@ -69,6 +69,15 @@ func (l *UnbindDeviceLogic) UnbindDevice(req *types.UnbindDeviceRequest) error {
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete device online record err: %v", err) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete device online record err: %v", err)
} }
var count int64
err = tx.Model(user.AuthMethods{}).Where("user_id = ?", deleteDevice.UserId).Count(&count).Error
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count user auth methods err: %v", err)
}
if count < 1 {
_ = tx.Where("id = ?", deleteDevice.UserId).Delete(&user.User{}).Error
}
//remove device cache //remove device cache
deviceCacheKey := fmt.Sprintf("%v:%v", config.DeviceCacheKeyKey, deleteDevice.Identifier) deviceCacheKey := fmt.Sprintf("%v:%v", config.DeviceCacheKeyKey, deleteDevice.Identifier)

View File

@ -2,6 +2,7 @@ package server
import ( import (
"encoding/json" "encoding/json"
"fmt"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -32,24 +33,23 @@ func NewGetServerUserListLogic(ctx *gin.Context, svcCtx *svc.ServiceContext) *Ge
} }
func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListRequest) (resp *types.GetServerUserListResponse, err error) { func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListRequest) (resp *types.GetServerUserListResponse, err error) {
//TODO Cache bug, temporarily disable the use of cache cacheKey := fmt.Sprintf("%s%d", node.ServerUserListCacheKey, req.ServerId)
//cacheKey := fmt.Sprintf("%s%d", node.ServerUserListCacheKey, req.ServerId) cache, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
//cache, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() if cache != "" {
//if cache != "" { etag := tool.GenerateETag([]byte(cache))
// etag := tool.GenerateETag([]byte(cache)) resp = &types.GetServerUserListResponse{}
// resp = &types.GetServerUserListResponse{} // Check If-None-Match header
// // Check If-None-Match header if match := l.ctx.GetHeader("If-None-Match"); match == etag {
// if match := l.ctx.GetHeader("If-None-Match"); match == etag { return nil, xerr.StatusNotModified
// return nil, xerr.StatusNotModified }
// } l.ctx.Header("ETag", etag)
// l.ctx.Header("ETag", etag) err = json.Unmarshal([]byte(cache), resp)
// err = json.Unmarshal([]byte(cache), resp) if err != nil {
// if err != nil { l.Errorw("[ServerUserListCacheKey] json unmarshal error", logger.Field("error", err.Error()))
// l.Errorw("[ServerUserListCacheKey] json unmarshal error", logger.Field("error", err.Error())) return nil, err
// return nil, err }
// } return resp, nil
// return resp, nil }
//}
server, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId) server, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.ServerId)
if err != nil { if err != nil {
return nil, err return nil, err
@ -121,11 +121,10 @@ func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListR
val, _ := json.Marshal(resp) val, _ := json.Marshal(resp)
etag := tool.GenerateETag(val) etag := tool.GenerateETag(val)
l.ctx.Header("ETag", etag) l.ctx.Header("ETag", etag)
//TODO Cache bug, temporarily disable the use of cache err = l.svcCtx.Redis.Set(l.ctx, cacheKey, string(val), -1).Err()
//err = l.svcCtx.Redis.Set(l.ctx, cacheKey, string(val), -1).Err() if err != nil {
//if err != nil { l.Errorw("[ServerUserListCacheKey] redis set error", logger.Field("error", err.Error()))
// l.Errorw("[ServerUserListCacheKey] redis set error", logger.Field("error", err.Error())) }
//}
// Check If-None-Match header // Check If-None-Match header
if match := l.ctx.GetHeader("If-None-Match"); match == etag { if match := l.ctx.GetHeader("If-None-Match"); match == etag {
return nil, xerr.StatusNotModified return nil, xerr.StatusNotModified

View File

@ -15,12 +15,16 @@ type Server struct {
Country string `gorm:"type:varchar(128);not null;default:'';comment:Country"` Country string `gorm:"type:varchar(128);not null;default:'';comment:Country"`
City string `gorm:"type:varchar(128);not null;default:'';comment:City"` City string `gorm:"type:varchar(128);not null;default:'';comment:City"`
//Ratio float32 `gorm:"type:DECIMAL(4,2);not null;default:0;comment:Traffic Ratio"` //Ratio float32 `gorm:"type:DECIMAL(4,2);not null;default:0;comment:Traffic Ratio"`
Address string `gorm:"type:varchar(100);not null;default:'';comment:Server Address"` Address string `gorm:"type:varchar(100);not null;default:'';comment:Server Address"`
Sort int `gorm:"type:int;not null;default:0;comment:Sort"` Sort int `gorm:"type:int;not null;default:0;comment:Sort"`
Protocols string `gorm:"type:text;default:null;comment:Protocol"` Protocols string `gorm:"type:text;default:null;comment:Protocol"`
LastReportedAt *time.Time `gorm:"comment:Last Reported Time"` LastReportedAt *time.Time `gorm:"comment:Last Reported Time"`
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` Longitude string `gorm:"type:varchar(50);not null;default:'0.0';comment:Longitude"`
UpdatedAt time.Time `gorm:"comment:Update Time"` Latitude string `gorm:"type:varchar(50);not null;default:'0.0';comment:Latitude"`
LongitudeCenter string `gorm:"type:varchar(50);not null;default:'0.0';comment:Center Longitude"`
LatitudeCenter string `gorm:"type:varchar(50);not null;default:'0.0';comment:Center Latitude"`
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
UpdatedAt time.Time `gorm:"comment:Update Time"`
} }
func (*Server) TableName() string { func (*Server) TableName() string {

View File

@ -2664,17 +2664,21 @@ type UserSubscribeLog struct {
} }
type UserSubscribeNodeInfo struct { type UserSubscribeNodeInfo struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
Protocols string `json:"protocols"` Protocols string `json:"protocols"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
Address string `json:"address"` Address string `json:"address"`
Tags []string `json:"tags"` Tags []string `json:"tags"`
Country string `json:"country"` Country string `json:"country"`
City string `json:"city"` City string `json:"city"`
CreatedAt int64 `json:"created_at"` Longitude string `json:"longitude"`
Latitude string `json:"latitude"`
LatitudeCenter string `json:"latitude_center"`
LongitudeCenter string `json:"longitude_center"`
CreatedAt int64 `json:"created_at"`
} }
type UserSubscribeTrafficLog struct { type UserSubscribeTrafficLog struct {

2524
pkg/ip/center.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -74,6 +74,12 @@ func GetRegionByIp(ip string) (*GeoLocationResponse, error) {
if response.Country == "" { if response.Country == "" {
continue continue
} }
response.LatitudeCenter, response.LongitudeCenter, _ = GetCapitalCoordinates(fmt.Sprintf("%s,%s", response.Country, response.City))
if response.LatitudeCenter == "" || response.LongitudeCenter == "" {
response.LatitudeCenter = response.Latitude
response.LongitudeCenter = response.Longitude
}
return response, nil return response, nil
} }
} }
@ -181,11 +187,13 @@ func decompressResponse(resp *http.Response) ([]byte, error) {
// GeoLocationResponse represents the geolocation data returned by the API. // GeoLocationResponse represents the geolocation data returned by the API.
type GeoLocationResponse struct { type GeoLocationResponse struct {
Country string `json:"country"` Country string `json:"country"`
CountryName string `json:"country_name"` CountryName string `json:"country_name"`
Region string `json:"region"` Region string `json:"region"`
City string `json:"city"` City string `json:"city"`
Latitude string `json:"latitude"` Latitude string `json:"latitude"`
Longitude string `json:"longitude"` Longitude string `json:"longitude"`
Loc string `json:"loc"` LatitudeCenter string `json:"latitude_center"`
LongitudeCenter string `json:"longitude_center"`
Loc string `json:"loc"`
} }