238 lines
6.3 KiB
Go
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)
|
|
}
|