package opscenter import ( "context" "sort" "strings" "time" "hyapp-admin-server/internal/modules/appregistry" "hyapp-admin-server/internal/modules/luckygift" "hyapp-admin-server/internal/response" "github.com/gin-gonic/gin" ) type Handler struct { apps *appregistry.Service luckyGift *luckygift.Handler } type appDTO struct { AppID int64 `json:"app_id"` AppCode string `json:"app_code"` AppName string `json:"app_name"` PackageName string `json:"package_name"` Platform string `json:"platform"` Status string `json:"status"` Source string `json:"source"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` } func New(apps *appregistry.Service, luckyGift *luckygift.Handler) *Handler { return &Handler{apps: apps, luckyGift: luckyGift} } func (h *Handler) ListApps(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second) defer cancel() items := make([]appDTO, 0, 4) if h.apps != nil { registryApps, err := h.apps.ListApps(ctx) if err != nil { response.ServerError(c, "获取应用列表失败") return } for _, item := range registryApps { items = append(items, appDTO{ AppID: item.AppID, AppCode: strings.ToLower(strings.TrimSpace(item.AppCode)), AppName: strings.TrimSpace(item.AppName), PackageName: strings.TrimSpace(item.PackageName), Platform: strings.TrimSpace(item.Platform), Status: strings.TrimSpace(item.Status), Source: "registry", CreatedAtMS: item.CreatedAtMs, UpdatedAtMS: item.UpdatedAtMs, }) } } // ops-center 的外部幸运礼物接入只认 app_code。这里强制补齐 yumi/aslan,使运营首屏能配置外部 App, // 但不反向写入 admin app registry,避免把临时接入白名单伪装成完整 App 主数据。 nowMS := time.Now().UTC().UnixMilli() items = append(items, appDTO{AppCode: "yumi", AppName: "Yumi", Status: "active", Source: "fixed", CreatedAtMS: nowMS, UpdatedAtMS: nowMS}, appDTO{AppCode: "aslan", AppName: "Aslan", Status: "active", Source: "fixed", CreatedAtMS: nowMS, UpdatedAtMS: nowMS}, ) items = filterApps(dedupeApps(items), c.Query("app_code"), c.Query("status")) response.OK(c, gin.H{"items": items, "total": len(items)}) } func filterApps(items []appDTO, appCode string, status string) []appDTO { appCode = strings.ToLower(strings.TrimSpace(appCode)) status = strings.ToLower(strings.TrimSpace(status)) if appCode == "" && status == "" { return items } filtered := make([]appDTO, 0, len(items)) for _, item := range items { if appCode != "" && !strings.Contains(strings.ToLower(item.AppCode), appCode) { continue } if status != "" && strings.ToLower(item.Status) != status { continue } filtered = append(filtered, item) } return filtered } func dedupeApps(items []appDTO) []appDTO { seen := make(map[string]appDTO, len(items)) for _, item := range items { code := strings.ToLower(strings.TrimSpace(item.AppCode)) if code == "" { continue } item.AppCode = code if item.AppName == "" { item.AppName = code } if item.Status == "" { item.Status = "active" } // registry 里已存在同 app_code 时以真实主数据为准;固定 app 只补缺口,不覆盖包名、平台和时间戳。 if existing, ok := seen[code]; ok && existing.Source == "registry" { continue } seen[code] = item } result := make([]appDTO, 0, len(seen)) for _, item := range seen { result = append(result, item) } sort.Slice(result, func(i, j int) bool { if result[i].Source != result[j].Source { return result[i].Source == "registry" } return result[i].AppCode < result[j].AppCode }) return result }