feat: 小程序阅读记录与资料链路、管理端用户规则、API/VIP/推荐与运营脚本

- miniprogram: reading-records、imageUrl/mpNavigate、多页资料与 VIP 展示调整
- soul-admin: Users/Settings/UserDetailModal、dist 构建产物更新
- soul-api: user/vip/referral/ckb/db、MBTI 头像管理、user_rule_completion、迁移 SQL
- .cursor: karuo-party 与飞书文档;.gitignore 忽略 .tmp_skill_bundle

Made-with: Cursor
This commit is contained in:
卡若
2026-03-23 18:38:23 +08:00
parent cb6e2bff56
commit fa3da12b16
82 changed files with 5621 additions and 2723 deletions

View File

@@ -0,0 +1,88 @@
package handler
import (
"encoding/json"
"errors"
"net/http"
"soul-api/internal/database"
"soul-api/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
const mbtiAvatarsConfigKey = "mbti_avatars"
const mbtiAvatarsDescription = "MBTI 16型人格头像映射"
// AdminMbtiAvatarsGet GET /api/admin/mbti-avatars 读取 MBTI 头像映射system_config.mbti_avatars
func AdminMbtiAvatarsGet(c *gin.Context) {
db := database.DB()
var row model.SystemConfig
err := db.Where("config_key = ?", mbtiAvatarsConfigKey).First(&row).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusOK, gin.H{"success": true, "avatars": map[string]string{}})
return
}
c.JSON(http.StatusOK, gin.H{"success": false, "error": "读取配置失败: " + err.Error()})
return
}
out := make(map[string]string)
if len(row.ConfigValue) > 0 {
if uerr := json.Unmarshal(row.ConfigValue, &out); uerr != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "配置 JSON 无效: " + uerr.Error()})
return
}
}
c.JSON(http.StatusOK, gin.H{"success": true, "avatars": out})
}
// AdminMbtiAvatarsPost POST /api/admin/mbti-avatars 保存 MBTI 头像映射upsert
func AdminMbtiAvatarsPost(c *gin.Context) {
var body struct {
Avatars map[string]string `json:"avatars"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "请求体无效"})
return
}
avatars := body.Avatars
if avatars == nil {
avatars = map[string]string{}
}
valBytes, err := json.Marshal(avatars)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "序列化失败: " + err.Error()})
return
}
db := database.DB()
desc := mbtiAvatarsDescription
var row model.SystemConfig
err = db.Where("config_key = ?", mbtiAvatarsConfigKey).First(&row).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
row = model.SystemConfig{
ConfigKey: mbtiAvatarsConfigKey,
ConfigValue: valBytes,
Description: &desc,
}
if cerr := db.Create(&row).Error; cerr != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "保存失败: " + cerr.Error()})
return
}
} else {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "查询配置失败: " + err.Error()})
return
}
} else {
row.ConfigValue = valBytes
row.Description = &desc
if serr := db.Save(&row).Error; serr != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "保存失败: " + serr.Error()})
return
}
}
_mbtiAvatarCacheTs = 0
c.JSON(http.StatusOK, gin.H{"success": true, "message": "MBTI 头像映射已保存"})
}