fix: 修复客户端实例记录问题并删除父应用功能

- 客户端实例唯一标识改为 app_config_id+appName+env+ip 四元组
- 新增 last_ping_at 字段,轮询即更新,拉取配置时更新 last_fetch_at
- FetchChangedConfigs 无变更时也记录客户端实例
- 清理逻辑改为基于 last_ping_at 超过5分钟删除
- 删除父应用(ParentId)相关功能
This commit is contained in:
2026-04-30 21:06:36 +08:00
parent ed94f53306
commit 8957286bb5
5 changed files with 54 additions and 59 deletions
-11
View File
@@ -103,7 +103,6 @@ func (h *AppHandler) GetById(c *gin.Context) {
type AddAppRequest struct {
Id int `json:"id"`
GroupId *int `json:"groupId"`
ParentId *int `json:"parentId"`
Name string `json:"name" binding:"required"`
Code string `json:"code" binding:"required"`
ServiceId string `json:"serviceId"`
@@ -141,7 +140,6 @@ func (h *AppHandler) Add(c *gin.Context) {
app := model.Application{
GroupId: req.GroupId,
ParentId: req.ParentId,
Name: req.Name,
Code: req.Code,
ServiceId: req.ServiceId,
@@ -153,15 +151,6 @@ func (h *AppHandler) Add(c *gin.Context) {
CreatedBy: userInfo.Name,
}
if req.ParentId != nil {
var parent model.Application
if has, _ := dao.Engine.ID(*req.ParentId).Get(&parent); has {
app.GroupId = parent.GroupId
app.IsModule = true
app.Code = parent.Code + "-" + req.Code
}
}
dao.Engine.Insert(&app)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
+1 -1
View File
@@ -94,7 +94,7 @@ func (h *ConfigHandler) List(c *gin.Context) {
}
var results []countResult
dao.Engine.Table("client_instance").
Select("app_config_id, COUNT(DISTINCT ip) as cnt").
Select("app_config_id, COUNT(*) as cnt").
In("app_config_id", configIds).
GroupBy("app_config_id").
Find(&results)
+46 -37
View File
@@ -86,20 +86,6 @@ func (h *ConfigApiHandler) FetchAllConfigs(c *gin.Context) {
}
}
// load parent app configs
if app.ParentId != nil {
var parentConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ?",
req.Env, version, *app.ParentId).Find(&parentConfigs)
for _, cfg := range parentConfigs {
configIds = append(configIds, cfg.Id)
kvMap := h.parseService.ParseConfigToKVMap(&cfg)
for k, v := range kvMap {
result[k] = v
}
}
}
// load app configs
var appConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ?",
@@ -176,30 +162,41 @@ func (h *ConfigApiHandler) FetchChangedConfigs(c *gin.Context) {
}
}
// load parent app changed configs
if app.ParentId != nil {
var parentConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ? AND updated_at > ?",
req.Env, version, *app.ParentId, lastUpdateTime).Find(&parentConfigs)
for _, cfg := range parentConfigs {
configIds = append(configIds, cfg.Id)
h.addChangedConfigs(result, &cfg, req.FetchAll)
}
}
// load app changed configs
var appConfigs []model.AppConfig
var appConfigs1 []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ? AND updated_at > ?",
req.Env, version, app.Id, lastUpdateTime).Find(&appConfigs)
for _, cfg := range appConfigs {
req.Env, version, app.Id, lastUpdateTime).Find(&appConfigs1)
for _, cfg := range appConfigs1 {
configIds = append(configIds, cfg.Id)
h.addChangedConfigs(result, &cfg, req.FetchAll)
}
result["latest.fetch.timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
// query all config ids for this app to record client instances
var allConfigIds []int
if !req.IgnoreGlobal && app.GroupId != nil {
var globalConfigs []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND group_id = ? AND (app_id is null OR app_id = 0)",
req.Env, version, *app.GroupId).Find(&globalConfigs)
for _, cfg := range globalConfigs {
allConfigIds = append(allConfigIds, cfg.Id)
}
}
var appConfigs2 []model.AppConfig
dao.Engine.Where("env = ? AND version = ? AND enabled = 1 AND app_id = ?",
req.Env, version, app.Id).Find(&appConfigs2)
for _, cfg := range appConfigs2 {
allConfigIds = append(allConfigIds, cfg.Id)
}
// record client instances asynchronously
go h.recordClientInstances(c.ClientIP(), configIds, req.AppName, req.Env, "fetchChanged")
// only update last_fetch_at if there are actual changes
fetchType := ""
if len(configIds) > 0 {
fetchType = "fetchChanged"
}
go h.recordClientInstances(c.ClientIP(), allConfigIds, req.AppName, req.Env, fetchType)
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: result})
}
@@ -218,29 +215,41 @@ func (h *ConfigApiHandler) recordClientInstances(ip string, configIds []int, app
now := time.Now()
for _, configId := range configIds {
var existing model.ClientInstance
has, _ := dao.Engine.Where("app_config_id = ? AND ip = ?", configId, ip).Get(&existing)
has, _ := dao.Engine.Where("app_config_id = ? AND app_name = ? AND env = ? AND ip = ?", configId, appName, env, ip).Get(&existing)
if has {
existing.LastPingAt = &now
existing.UpdatedAt = &now
if fetchType != "" {
existing.LastFetchAt = &now
existing.LastFetchType = fetchType
existing.UpdatedAt = &now
dao.Engine.ID(existing.Id).Cols("last_fetch_at", "last_fetch_type", "updated_at").Update(&existing)
dao.Engine.ID(existing.Id).Cols("last_ping_at", "last_fetch_at", "last_fetch_type", "updated_at").Update(&existing)
} else {
dao.Engine.ID(existing.Id).Cols("last_ping_at", "updated_at").Update(&existing)
}
} else {
instance := model.ClientInstance{
AppConfigId: configId,
AppName: appName,
Env: env,
IP: ip,
LastFetchAt: &now,
LastFetchType: fetchType,
Ip: ip,
LastPingAt: &now,
CreatedAt: &now,
UpdatedAt: &now,
}
dao.Engine.Insert(&instance)
if fetchType != "" {
instance.LastFetchAt = &now
instance.LastFetchType = fetchType
}
_, err := dao.Engine.Insert(&instance)
if err != nil {
fmt.Printf("[recordClientInstances] insert error: %v\n", err)
continue
}
}
}
// cleanup records older than 5 minutes
fiveMinutesAgo := now.Add(-5 * time.Minute)
dao.Engine.Where("last_fetch_at < ?", fiveMinutesAgo).Delete(&model.ClientInstance{})
dao.Engine.Where("last_ping_at < ?", fiveMinutesAgo).Delete(&model.ClientInstance{})
}
// func (h *ConfigApiHandler) Ping(c *gin.Context) {
-4
View File
@@ -5,12 +5,10 @@ import "time"
type Application struct {
Id int `xorm:"int autoincr pk" json:"id"`
GroupId *int `xorm:"int" json:"groupId"`
ParentId *int `xorm:"int" json:"parentId"`
Name string `xorm:"varchar(32)" json:"name"`
Code string `xorm:"varchar(32)" json:"code"`
ServiceId string `xorm:"varchar(64)" json:"serviceId"`
Token string `xorm:"varchar(64)" json:"token"`
IsModule bool `xorm:"tinyint(1) default 1" json:"isModule"`
OwnerId *int `xorm:"int" json:"ownerId"`
OwnerName string `xorm:"varchar(16)" json:"ownerName"`
Remarks string `xorm:"text" json:"remarks"`
@@ -19,7 +17,5 @@ type Application struct {
CreatedBy string `xorm:"varchar(32)" json:"createdBy"`
UpdatedAt *time.Time `xorm:"datetime" json:"updatedAt"`
UpdatedBy string `xorm:"varchar(32)" json:"updatedBy"`
ParentName string `xorm:"-" json:"parentName,omitempty"`
FullName string `xorm:"-" json:"fullName,omitempty"`
GroupName string `xorm:"-" json:"groupName,omitempty"`
}
+5 -4
View File
@@ -4,10 +4,11 @@ import "time"
type ClientInstance struct {
Id int `xorm:"int autoincr pk" json:"id"`
AppConfigId int `xorm:"int index" json:"appConfigId"`
AppName string `xorm:"varchar(64) index" json:"appName"`
Env string `xorm:"varchar(32)" json:"env"`
IP string `xorm:"varchar(64)" json:"ip"`
AppConfigId int `xorm:"int unique(udx_aaei)" json:"appConfigId"`
AppName string `xorm:"varchar(64) unique(udx_aaei)" json:"appName"`
Env string `xorm:"varchar(32) unique(udx_aaei)" json:"env"`
Ip string `xorm:"varchar(64) unique(udx_aaei)" json:"ip"`
LastPingAt *time.Time `xorm:"datetime" json:"lastPingAt"`
LastFetchAt *time.Time `xorm:"datetime" json:"lastFetchAt"`
LastFetchType string `xorm:"varchar(16)" json:"lastFetchType"`
CreatedAt *time.Time `xorm:"datetime" json:"createdAt"`