Files
Mycontent/soul-api/internal/handler/admin_mbti_avatars.go
2026-04-13 14:32:32 +08:00

191 lines
5.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"encoding/json"
"errors"
"net/http"
"strings"
"soul-api/internal/database"
"soul-api/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
const mbtiAvatarsConfigKey = "mbti_avatars"
const mbtiAvatarsDescription = "MBTI 16型人格头像映射"
var allMbtiTypes = []string{
"INTJ", "INTP", "ENTJ", "ENTP",
"INFJ", "INFP", "ENFJ", "ENFP",
"ISTJ", "ISFJ", "ESTJ", "ESFJ",
"ISTP", "ISFP", "ESTP", "ESFP",
}
// buildDefaultMbtiAvatarMap returns the default PNG avatar mapping for all 16 types.
func buildDefaultMbtiAvatarMap() map[string]string {
m := make(map[string]string, 16)
for _, t := range allMbtiTypes {
m[t] = "/static/mbti-avatars-png-male/" + t + ".png"
}
return m
}
// absolutizeMbtiAvatarMap 将 /static、/uploads 等相对路径转为当前 API 公网可访问的绝对 URL
// 避免管理端页面 <img src="/static/..."> 请求到前端 dev 端口、以及小程序端拼接 baseUrl 异常时不显示。
func absolutizeMbtiAvatarMap(c *gin.Context, m map[string]string) map[string]string {
if m == nil {
return nil
}
out := make(map[string]string, len(m))
for k, v := range m {
out[k] = resolveAvatarURLWithRequest(c, strings.TrimSpace(v))
}
return out
}
// AdminMbtiAvatarsGet GET /api/admin/mbti-avatars 读取 MBTI 头像映射system_config.mbti_avatars
// 无配置时自动填充 PNG 默认路径并持久化。
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) {
defaults := buildDefaultMbtiAvatarMap()
valBytes, _ := json.Marshal(defaults)
desc := mbtiAvatarsDescription
newRow := model.SystemConfig{
ConfigKey: mbtiAvatarsConfigKey,
ConfigValue: valBytes,
Description: &desc,
}
db.Create(&newRow)
_mbtiAvatarCacheTs = 0
c.JSON(http.StatusOK, gin.H{"success": true, "avatars": absolutizeMbtiAvatarMap(c, defaults)})
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
}
}
if len(out) == 0 {
out = buildDefaultMbtiAvatarMap()
}
c.JSON(http.StatusOK, gin.H{"success": true, "avatars": absolutizeMbtiAvatarMap(c, 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 的随机分配。
// 无配置时自动使用 PNG 默认路径。
func AdminMbtiAvatarsBatchAssign(c *gin.Context) {
db := database.DB()
avatarMap := make(map[string]string)
var row model.SystemConfig
if err := db.Where("config_key = ?", mbtiAvatarsConfigKey).First(&row).Error; err == nil {
_ = json.Unmarshal(row.ConfigValue, &avatarMap)
}
if len(avatarMap) == 0 {
avatarMap = buildDefaultMbtiAvatarMap()
}
availableTypes := make([]string, 0, len(avatarMap))
for k, v := range avatarMap {
if v != "" {
availableTypes = append(availableTypes, k)
}
}
if len(availableTypes) == 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(availableTypes)
url = avatarMap[availableTypes[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": "批量分配完成"})
}