2026-05-08 18:55:47 +08:00

238 lines
6.3 KiB
Go

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)
}