整合小程序、管理端与后端的最新本地改动,补齐用户管理与首页入口相关能力;提交前已完成敏感信息扫描,并移除本地 gitea 远程 URL 中的明文凭证,避免隐私信息进入远程仓库。 Made-with: Cursor
146 lines
4.4 KiB
Go
146 lines
4.4 KiB
Go
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 头像映射已保存"})
|
||
}
|
||
|
||
// AdminMbtiAvatarsBatchAssign POST /api/admin/mbti-avatars/batch-assign
|
||
// 为无头像(avatar IS NULL 或空)的用户批量分配 MBTI 默认头像。
|
||
// 有 MBTI 的按 MBTI 匹配;无 MBTI 的随机分配。
|
||
func AdminMbtiAvatarsBatchAssign(c *gin.Context) {
|
||
db := database.DB()
|
||
var row model.SystemConfig
|
||
if err := db.Where("config_key = ?", mbtiAvatarsConfigKey).First(&row).Error; err != nil {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "请先配置 MBTI 头像映射"})
|
||
return
|
||
}
|
||
avatarMap := make(map[string]string)
|
||
if err := json.Unmarshal(row.ConfigValue, &avatarMap); err != nil || len(avatarMap) == 0 {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "MBTI 头像映射为空"})
|
||
return
|
||
}
|
||
allTypes := make([]string, 0, len(avatarMap))
|
||
for k, v := range avatarMap {
|
||
if v != "" {
|
||
allTypes = append(allTypes, k)
|
||
}
|
||
}
|
||
if len(allTypes) == 0 {
|
||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "无可用头像"})
|
||
return
|
||
}
|
||
|
||
var users []model.User
|
||
db.Where("(avatar IS NULL OR avatar = '')").Limit(500).Find(&users)
|
||
if len(users) == 0 {
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "assigned": 0, "message": "无需分配"})
|
||
return
|
||
}
|
||
|
||
assigned := 0
|
||
for i := range users {
|
||
mbti := ""
|
||
if users[i].Mbti != nil {
|
||
mbti = *users[i].Mbti
|
||
}
|
||
url := ""
|
||
if mbti != "" {
|
||
url = avatarMap[mbti]
|
||
}
|
||
if url == "" {
|
||
idx := (int(users[i].CreatedAt.UnixNano()) + i) % len(allTypes)
|
||
url = avatarMap[allTypes[idx]]
|
||
}
|
||
if url == "" {
|
||
continue
|
||
}
|
||
if err := db.Model(&model.User{}).Where("id = ?", users[i].ID).Update("avatar", url).Error; err == nil {
|
||
assigned++
|
||
}
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"success": true, "assigned": assigned, "total": len(users), "message": "批量分配完成"})
|
||
}
|