feat: minilogin改造

This commit is contained in:
Blizzard
2026-05-24 23:04:09 +08:00
parent 076ed1509b
commit da02247794
36 changed files with 3523 additions and 11653 deletions
@@ -3,11 +3,15 @@ package complete
import (
"context"
"fmt"
"time"
"github.com/zeromicro/go-zero/core/logx"
filePb "sundynix-micro-go/app/file/rpc/file"
"sundynix-micro-go/app/plant/api/internal/svc"
"sundynix-micro-go/app/plant/api/internal/types"
plantModel "sundynix-micro-go/app/plant/model"
plantPb "sundynix-micro-go/app/plant/rpc/plant"
"github.com/zeromicro/go-zero/core/logx"
)
type CompleteTaskLogic struct {
@@ -20,9 +24,93 @@ func NewCompleteTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Comp
return &CompleteTaskLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *CompleteTaskLogic) CompleteTask(req *types.CompleteTaskApiReq) (*plantPb.TaskCompletionResult, error) {
func (l *CompleteTaskLogic) CompleteTask(req *types.CompleteTaskApiReq) (interface{}, error) {
userId := fmt.Sprintf("%v", l.ctx.Value("userId"))
return l.svcCtx.PlantRpc.CompleteTask(l.ctx, &plantPb.CompleteTaskReq{
resp, err := l.svcCtx.PlantRpc.CompleteTask(l.ctx, &plantPb.CompleteTaskReq{
UserId: userId, TaskId: req.TaskId, Remark: req.Remark,
})
if err != nil {
return nil, err
}
// 1. 拼装当前等级详情(在升级时包含 perks 等完整字段,完美渲染等级弹窗)
var currentLevelData map[string]interface{}
if resp.CurrentLevel != nil {
currentLevelData = map[string]interface{}{
"id": resp.CurrentLevel.Id,
"level": resp.CurrentLevel.Level,
"title": resp.CurrentLevel.Title,
"minSunlight": resp.CurrentLevel.MinSunlight,
"perks": "",
}
// 若发生升级,自动利用 GORM 提取等级的 Perks 描述
if resp.IsLevelUp {
var lvl plantModel.LevelConfig
if errLvl := l.svcCtx.DB.Where("id = ?", resp.CurrentLevel.Id).First(&lvl).Error; errLvl == nil {
currentLevelData["perks"] = lvl.Perks
}
}
}
// 2. 拼装新获得徽章详情(包含 description 和 icon.url,完美渲染新徽章弹窗)
var newBadgeData map[string]interface{}
if resp.IsGetBadge && resp.NewBadge != nil {
var bc plantModel.BadgeConfig
if errBc := l.svcCtx.DB.Where("id = ?", resp.NewBadge.Id).First(&bc).Error; errBc == nil {
newBadgeData = map[string]interface{}{
"id": bc.ID,
"name": bc.Name,
"description": bc.Description,
"iconId": bc.IconID,
"dimension": bc.Dimension,
"groupId": bc.GroupID,
"tier": bc.Tier,
"targetAction": bc.TargetAction,
"threshold": bc.Threshold,
"comparator": bc.Comparator,
"rewardSunlight": bc.RewardSunlight,
"sort": bc.Sort,
"icon": nil,
}
// 跨微服务请求获取 Icon 图片 URL 等元数据
if bc.IconID != "" {
fileResp, errFile := l.svcCtx.FileRpc.GetFilesByIds(l.ctx, &filePb.GetFilesByIdsReq{Ids: []string{bc.IconID}})
if errFile == nil && fileResp != nil && len(fileResp.Files) > 0 {
f := fileResp.Files[0]
newBadgeData["icon"] = map[string]interface{}{
"id": f.Id,
"name": f.Name,
"url": f.Url,
"tag": f.Tag,
"key": f.Key,
"suffix": f.Suffix,
"md5": f.Md5,
"createdAt": time.Unix(f.CreatedAt, 0).Format(time.RFC3339),
}
}
}
} else {
// 兜底映射
newBadgeData = map[string]interface{}{
"id": resp.NewBadge.Id,
"name": resp.NewBadge.Name,
"dimension": resp.NewBadge.Dimension,
"tier": resp.NewBadge.Tier,
"rewardSunlight": resp.NewBadge.RewardSunlight,
"icon": nil,
}
}
}
// 3. 构建高度兼容的 JSON 响应,同时适配大小写驼峰字段
res := map[string]interface{}{
"isLevelUp": resp.IsLevelUp,
"currentLevel": currentLevelData,
"isGetBadge": resp.IsGetBadge,
"IsGetBadge": resp.IsGetBadge, // 兼容前端多处命名差异
"newBadge": newBadgeData,
"rewardSunlight": resp.RewardSunlight,
}
return res, nil
}
@@ -2,10 +2,13 @@ package complete
import (
"context"
"time"
filePb "sundynix-micro-go/app/file/rpc/file"
"sundynix-micro-go/app/plant/api/internal/svc"
plantModel "sundynix-micro-go/app/plant/model"
"github.com/zeromicro/go-zero/core/logx"
"sundynix-micro-go/app/plant/api/internal/svc"
plantPb "sundynix-micro-go/app/plant/rpc/plant"
)
type GetBadgeConfigTreeLogic struct {
@@ -18,6 +21,142 @@ func NewGetBadgeConfigTreeLogic(ctx context.Context, svcCtx *svc.ServiceContext)
return &GetBadgeConfigTreeLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *GetBadgeConfigTreeLogic) GetBadgeConfigTree() (*plantPb.BadgeConfigTreeResp, error) {
return l.svcCtx.PlantRpc.GetBadgeConfigTree(l.ctx, &plantPb.IdReq{})
func (l *GetBadgeConfigTreeLogic) GetBadgeConfigTree() (interface{}, error) {
var allBadges []plantModel.BadgeConfig
err := l.svcCtx.DB.Order("dimension desc, group_id asc, tier asc, sort asc").Find(&allBadges).Error
if err != nil {
return nil, err
}
iconIds := make([]string, 0)
iconIdMap := make(map[string]bool)
for _, b := range allBadges {
if b.IconID != "" && !iconIdMap[b.IconID] {
iconIdMap[b.IconID] = true
iconIds = append(iconIds, b.IconID)
}
}
fileMap := make(map[string]map[string]interface{})
if len(iconIds) > 0 {
resp, err := l.svcCtx.FileRpc.GetFilesByIds(l.ctx, &filePb.GetFilesByIdsReq{Ids: iconIds})
if err == nil && resp != nil {
for _, f := range resp.Files {
fileMap[f.Id] = map[string]interface{}{
"id": f.Id,
"name": f.Name,
"url": f.Url,
"tag": f.Tag,
"key": f.Key,
"suffix": f.Suffix,
"md5": f.Md5,
"createdAt": time.Unix(f.CreatedAt, 0).Format(time.RFC3339),
}
}
}
}
dimLabelMap := map[string]string{
"PERSISTENCE": "勤勉成就",
"EXPERTISE": "专家成就",
"JOURNAL": "岁月记录",
"DISCOVERY": "探索发现",
}
groupLabelMap := map[string]string{
"water_master": "雨露均沾",
"alive_master": "长情陪伴",
"fert_master": "炼金术士",
"prune_master": "园艺理发师",
"repot_master": "乔迁之喜",
"doctor_master": "植物医生",
"photo_master": "光影捕手",
"night_owl": "守夜人",
}
dimOrder := []string{"PERSISTENCE", "EXPERTISE", "JOURNAL", "DISCOVERY"}
// Build dimension -> groupId -> badge list
tempMap := make(map[string]map[string][]map[string]interface{})
for _, b := range allBadges {
dim := b.Dimension
group := b.GroupID
badgeData := map[string]interface{}{
"id": b.ID,
"name": b.Name,
"description": b.Description,
"iconId": b.IconID,
"dimension": b.Dimension,
"groupId": b.GroupID,
"tier": b.Tier,
"targetAction": b.TargetAction,
"threshold": b.Threshold,
"comparator": b.Comparator,
"rewardSunlight": b.RewardSunlight,
"sort": b.Sort,
"createdAt": b.CreatedAt.Format("2006-01-02 15:04:05"),
}
if iconData, ok := fileMap[b.IconID]; ok {
badgeData["icon"] = iconData
} else {
badgeData["icon"] = nil
}
if tempMap[dim] == nil {
tempMap[dim] = make(map[string][]map[string]interface{})
}
tempMap[dim][group] = append(tempMap[dim][group], badgeData)
}
var tree []map[string]interface{}
for _, dimKey := range dimOrder {
if groupMap, exists := tempMap[dimKey]; exists {
var groupNodes []map[string]interface{}
for groupKey, badges := range groupMap {
gLabel := groupLabelMap[groupKey]
if gLabel == "" {
gLabel = groupKey
}
groupNodes = append(groupNodes, map[string]interface{}{
"groupId": groupKey,
"groupLabel": gLabel,
"badges": badges,
})
}
dLabel := dimLabelMap[dimKey]
if dLabel == "" {
dLabel = dimKey
}
tree = append(tree, map[string]interface{}{
"dimension": dimKey,
"label": dLabel,
"groups": groupNodes,
})
delete(tempMap, dimKey)
}
}
// Handle leftover dimensions
for dimKey, groupMap := range tempMap {
var groupNodes []map[string]interface{}
for groupKey, badges := range groupMap {
gLabel := groupLabelMap[groupKey]
if gLabel == "" {
gLabel = groupKey
}
groupNodes = append(groupNodes, map[string]interface{}{
"groupId": groupKey,
"groupLabel": gLabel,
"badges": badges,
})
}
tree = append(tree, map[string]interface{}{
"dimension": dimKey,
"label": dimKey,
"groups": groupNodes,
})
}
return tree, nil
}
@@ -36,7 +36,18 @@ func (l *GetLevelConfigListLogic) GetLevelConfigList(req *types.LevelConfigListR
if err != nil {
return nil, err
}
resList := make([]map[string]interface{}, 0, len(result.List))
for _, item := range result.List {
resList = append(resList, map[string]interface{}{
"id": item.Id,
"level": item.Level,
"title": item.Title,
"minSunlight": item.MinSunlight, // Explicitly keep 0 values in JSON
"perks": item.Perks,
})
}
return map[string]interface{}{
"list": result.List,
"list": resList,
}, nil
}
@@ -1,15 +1,16 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package userProfile
import (
"context"
"fmt"
"time"
filePb "sundynix-micro-go/app/file/rpc/file"
"sundynix-micro-go/app/plant/api/internal/svc"
plantModel "sundynix-micro-go/app/plant/model"
"sundynix-micro-go/app/plant/rpc/plant"
"github.com/zeromicro/go-zero/core/logx"
"sundynix-micro-go/app/plant/api/internal/svc"
"sundynix-micro-go/app/plant/rpc/plant"
)
type GetUserProfileLogic struct {
@@ -33,5 +34,98 @@ func (l *GetUserProfileLogic) GetUserProfile() (interface{}, error) {
if err != nil {
return nil, err
}
return result, nil
// 1. 获取 Avatar 详情
var avatarData map[string]interface{}
if result.AvatarId != "" {
avatarData = l.fetchAvatar(result.AvatarId)
}
// 2. 获取 Level 详情
var levelData map[string]interface{}
var lvl plantModel.LevelConfig
levelFound := false
if result.LevelId != "" {
if errLvl := l.svcCtx.DB.Where("id = ?", result.LevelId).First(&lvl).Error; errLvl == nil {
levelFound = true
}
}
// 如果没有在数据库中找到 level_id,或者 level_id 为空,根据用户 totalSunlight 动态查找最匹配的等级
if !levelFound {
var matchedLvl plantModel.LevelConfig
// 查找最匹配的等级:total_sunlight >= min_sunlight 中 level 最大的那一个
errLvl := l.svcCtx.DB.Order("level desc").Where("min_sunlight <= ?", result.TotalSunlight).First(&matchedLvl).Error
if errLvl == nil {
lvl = matchedLvl
levelFound = true
// 自动更新用户资料表的 level_id,保证后续一致性
l.svcCtx.DB.Model(&plantModel.UserProfile{}).Where("id = ?", result.Id).Update("level_id", matchedLvl.ID)
} else {
// 如果没有等级配置,使用 level = 1 的配置作为默认
var firstLvl plantModel.LevelConfig
if l.svcCtx.DB.Order("level asc").First(&firstLvl).Error == nil {
lvl = firstLvl
levelFound = true
l.svcCtx.DB.Model(&plantModel.UserProfile{}).Where("id = ?", result.Id).Update("level_id", firstLvl.ID)
}
}
}
if levelFound {
levelData = map[string]interface{}{
"id": lvl.ID,
"level": lvl.Level,
"title": lvl.Title,
"minSunlight": lvl.MinSunlight,
"perks": lvl.Perks,
"createdAt": lvl.CreatedAt.Format("2006-01-02 15:04:05"),
}
}
// 3. 构造兼容新旧版本的复合 JSON 数据
res := map[string]interface{}{
"id": result.Id,
"userId": result.UserId,
"nickName": result.NickName,
"nickname": result.NickName, // 兼容旧版 json tag
"avatarId": result.AvatarId,
"levelId": result.LevelId,
"currentSunlight": result.CurrentSunlight,
"totalSunlight": result.TotalSunlight,
"plantCount": result.PlantCount,
"careCount": result.CareCount,
"postCount": result.PostCount,
"waterCount": result.WaterCount,
"fertilizeCount": result.FertilizeCount,
"repotCount": result.RepotCount,
"pruneCount": result.PruneCount,
"photoCount": result.PhotoCount,
"avatar": avatarData,
"level": levelData,
}
return res, nil
}
func (l *GetUserProfileLogic) fetchAvatar(avatarId string) map[string]interface{} {
if avatarId == "" {
return nil
}
resp, err := l.svcCtx.FileRpc.GetFilesByIds(l.ctx, &filePb.GetFilesByIdsReq{Ids: []string{avatarId}})
if err != nil || resp == nil || len(resp.Files) == 0 {
return nil
}
f := resp.Files[0]
return map[string]interface{}{
"id": f.Id,
"name": f.Name,
"url": f.Url,
"tag": f.Tag,
"key": f.Key,
"suffix": f.Suffix,
"md5": f.Md5,
"createdAt": time.Unix(f.CreatedAt, 0).Format(time.RFC3339),
}
}