package apppopup import ( "context" "encoding/json" "errors" "net/http" "strings" "time" "chatapp3-golang/internal/model" "chatapp3-golang/internal/utils" "gorm.io/gorm" ) // PageAdminConfigs 返回后台首页弹窗配置分页。 func (s *Service) PageAdminConfigs(ctx context.Context, sysOrigin, scene, popupKey string, cursor, limit int) (*AdminConfigPageResponse, error) { if s == nil || s.db == nil { return nil, NewAppError(http.StatusServiceUnavailable, "app_popup_unavailable", "app popup service is unavailable") } sysOrigin = normalizeSysOrigin(sysOrigin) scene = normalizeScene(scene) cursor = normalizePageCursor(cursor) limit = normalizePageLimit(limit) query := s.db.WithContext(ctx). Model(&model.AppPopupConfig{}). Where("sys_origin = ? AND scene = ?", sysOrigin, scene) if key := normalizePopupKey(popupKey); key != "" { query = query.Where("popup_key LIKE ?", "%"+key+"%") } var total int64 if err := query.Count(&total).Error; err != nil { return nil, err } var rows []model.AppPopupConfig if err := query. Order("priority DESC"). Order("id ASC"). Limit(limit). Offset((cursor - 1) * limit). Find(&rows).Error; err != nil { return nil, err } items := make([]AdminConfigItem, 0, len(rows)) for _, row := range rows { items = append(items, adminItemFromModel(row)) } return &AdminConfigPageResponse{ Records: items, Total: total, Current: cursor, Size: limit, }, nil } // SaveAdminConfig 创建或更新后台首页弹窗配置。 func (s *Service) SaveAdminConfig(ctx context.Context, req SaveAdminConfigRequest) (*AdminConfigItem, error) { if s == nil || s.db == nil { return nil, NewAppError(http.StatusServiceUnavailable, "app_popup_unavailable", "app popup service is unavailable") } sysOrigin := normalizeSysOrigin(req.SysOrigin) scene := normalizeScene(req.Scene) popupKey := normalizePopupKey(req.PopupKey) if popupKey == "" { return nil, NewAppError(http.StatusBadRequest, "popup_key_required", "popupKey is required") } if req.LimitDays < 0 { return nil, NewAppError(http.StatusBadRequest, "invalid_limit_days", "limitDays must be greater than or equal to 0") } var savedID int64 if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var row model.AppPopupConfig requestID := req.ID.Int64() if requestID > 0 { err := tx.Where("id = ?", requestID).First(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { return NewAppError(http.StatusNotFound, "app_popup_config_not_found", "config not found") } if err != nil { return err } } else { id, err := utils.NextID() if err != nil { return err } row.ID = id row.CreateTime = time.Now() } var duplicate model.AppPopupConfig err := tx.Where("sys_origin = ? AND scene = ? AND popup_key = ? AND id <> ?", sysOrigin, scene, popupKey, row.ID). First(&duplicate).Error if err == nil { return NewAppError(http.StatusConflict, "duplicate_popup_key", "popupKey already exists") } if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } now := time.Now() row.SysOrigin = sysOrigin row.Scene = scene row.PopupKey = popupKey row.Name = defaultIfBlank(req.Name, popupKey) row.Enabled = req.Enabled row.LimitDays = req.LimitDays row.Priority = req.Priority row.Version = normalizeVersion(req.Version) row.ContentJSON = mustContentJSON(req.Title, req.Image) row.JumpType = strings.TrimSpace(req.JumpType) row.JumpURL = strings.TrimSpace(req.JumpURL) row.Remark = strings.TrimSpace(req.Description) row.PlatformsJSON = normalizeJSONField(req.PlatformsJSON) row.MinAppVersion = strings.TrimSpace(req.MinAppVersion) row.MaxAppVersion = strings.TrimSpace(req.MaxAppVersion) row.UserScopeType = "ALL" if strings.TrimSpace(row.UserScopeJSON) == "" { row.UserScopeJSON = "{}" } row.UpdateTime = now if row.CreateTime.IsZero() { row.CreateTime = now } if err := tx.Save(&row).Error; err != nil { return err } savedID = row.ID return nil }); err != nil { return nil, err } s.ClearConfigCache(ctx, sysOrigin, scene) var saved model.AppPopupConfig if err := s.db.WithContext(ctx).Where("id = ?", savedID).First(&saved).Error; err != nil { return nil, err } item := adminItemFromModel(saved) return &item, nil } // DeleteAdminConfig 删除后台首页弹窗配置。 func (s *Service) DeleteAdminConfig(ctx context.Context, id int64) error { if s == nil || s.db == nil { return NewAppError(http.StatusServiceUnavailable, "app_popup_unavailable", "app popup service is unavailable") } if id <= 0 { return NewAppError(http.StatusBadRequest, "invalid_id", "id is required") } var row model.AppPopupConfig if err := s.db.WithContext(ctx).Where("id = ?", id).First(&row).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return NewAppError(http.StatusNotFound, "app_popup_config_not_found", "config not found") } return err } if err := s.db.WithContext(ctx).Delete(&row).Error; err != nil { return err } s.ClearConfigCache(ctx, row.SysOrigin, row.Scene) return nil } // ClearConfigCache 清理指定场景的配置缓存,供后台配置保存后调用。 func (s *Service) ClearConfigCache(ctx context.Context, sysOrigin, scene string) { if s == nil || s.cache == nil { return } _ = s.cache.Del(ctx, appPopupConfigCacheKey(sysOrigin, scene)).Err() } func normalizePageCursor(cursor int) int { if cursor <= 0 { return 1 } return cursor } func normalizePageLimit(limit int) int { if limit <= 0 { return 20 } if limit > 100 { return 100 } return limit } func defaultIfBlank(value, fallback string) string { value = strings.TrimSpace(value) if value != "" { return value } return strings.TrimSpace(fallback) } func mustContentJSON(title, image string) string { payload := map[string]any{} if title = strings.TrimSpace(title); title != "" { payload["title"] = title } if image = strings.TrimSpace(image); image != "" { payload["image"] = image } if len(payload) == 0 { return "{}" } raw, err := json.Marshal(payload) if err != nil { return "" } return string(raw) } func normalizeJSONField(raw string) string { raw = strings.TrimSpace(raw) if raw == "" { return "null" } var value any if err := json.Unmarshal([]byte(raw), &value); err != nil { return "null" } encoded, err := json.Marshal(value) if err != nil { return "null" } return string(encoded) }