Files
slogan/styleagent/service/plan_effect_image_service.go
T
admin 775995f3e8 refactor: 五层对齐 22 表 + 工具并入 agent/ + 支付适配器内联(路由零变更)
- service/dto/controller 各拆 18 文件,一表一文件;跨表编排留在 outfit_generation_task_service
- controller 共享 struct(outfit/member)保 /outfit/*、/member/* 前缀,路由零漂移(28 路径 diff 为空)
- 修复 4 个缺失 g.Meta handler:/user/profile、/avatar/get、/body-measurement/get、/hairstyle/list 从 ALL 收敛为 GET
- 工具包并入 agent/ 单包(weather/imagegen/avatar/scoring),NewCache 泛化 NewTTLCache
- 虎皮椒支付适配器内联 service/payment_order_service.go,member 服务拆 4 表文件
- 冒烟 21 路径全绿 + 客户端 dart analyze 零 error
2026-07-31 15:15:04 +08:00

114 lines
3.5 KiB
Go
Raw 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 service
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"strings"
"slogan-agent/styleagent/agent"
"slogan-agent/styleagent/consts"
"slogan-agent/styleagent/dao"
"slogan-agent/styleagent/model/entity"
"github.com/gogf/gf/v2/frame/g"
)
type effectImageService struct{}
var EffectImageService = new(effectImageService)
var effectAngles = []string{"正面", "侧面", "背面"}
// GenerateForPlan 选定主方案后异步生成 3 视角效果图
func (s *effectImageService) GenerateForPlan(ctx context.Context, planId, userId int64) {
go s.run(ctx, planId, userId)
}
func (s *effectImageService) run(ctx context.Context, planId, userId int64) {
plan, err := dao.OutfitPlan.GetOne(ctx, planId, userId)
if err != nil || plan == nil {
g.Log().Errorf(ctx, "效果图任务: 方案不存在 planId=%d", planId)
return
}
// 每日限额:VIP 不限;普通用户 = 基础额度 + 广告激励额外次数
if !dao.UserMember.IsVip(ctx, userId) {
limit := ScoringRuleService.EffectLimit(ctx)
if limit > 0 {
used, _ := dao.PlanEffectImage.CountByUserToday(ctx, userId)
extra, _ := dao.AdRewardLog.CountTodayByType(ctx, userId, consts.AdTypeEffectExtra)
if used >= limit+extra {
g.Log().Warningf(ctx, "效果图任务: 用户 %d 当日次数已用尽(%d/%d", userId, used, limit+extra)
return
}
}
}
_ = dao.OutfitGenTask.UpdateStatus(ctx, plan.TaskId, consts.TaskStatusRendering, "")
defer dao.OutfitGenTask.UpdateStatus(ctx, plan.TaskId, consts.TaskStatusDone, "")
items, _ := dao.PlanOutfitItem.ListByPlan(ctx, planId)
planDesc := planTitleDesc(plan.Title, items)
// 用户全身正面照作 base image
baseImageURL := ""
if photos, err := dao.UserPhoto.ListByUser(ctx, userId, 0); err == nil {
for _, p := range photos {
if p.Type == consts.PhotoTypeFullFront {
baseImageURL = p.Url
break
}
}
}
client := agent.NewClient(g.Cfg().MustGet(ctx, "agent.supplier", "mock").String())
for i, angle := range effectAngles {
cacheKey := effectCacheKey(plan, angle)
if url, ok := agent.CacheGet(cacheKey); ok {
_, _ = dao.PlanEffectImage.Insert(ctx, &entity.PlanEffectImage{
PlanId: planId, Angle: angle, Url: url, Status: consts.EffectStatusDone,
PromptSnapshot: planDesc,
})
continue
}
recId, err := dao.PlanEffectImage.Insert(ctx, &entity.PlanEffectImage{
PlanId: planId, Angle: angle, Status: consts.EffectStatusRendering,
PromptSnapshot: planDesc,
})
if err != nil {
continue
}
url, err := client.Generate(ctx, &agent.GenerateReq{
BaseImageURL: baseImageURL, Prompt: planDesc, Angle: angle, Seed: plan.Id*100 + int64(i),
})
if err != nil {
g.Log().Warningf(ctx, "效果图生成失败 plan=%d angle=%s: %v", planId, angle, err)
_ = dao.PlanEffectImage.UpdateStatus(ctx, recId, consts.EffectStatusFailed, "")
continue
}
agent.CacheSet(cacheKey, url)
_ = dao.PlanEffectImage.UpdateStatus(ctx, recId, consts.EffectStatusDone, url)
}
g.Log().Infof(ctx, "方案 %d 效果图生成完成", planId)
}
func planTitleDesc(title string, items []*entity.PlanOutfitItem) string {
var sb strings.Builder
sb.WriteString("方案:")
sb.WriteString(title)
sb.WriteString("")
for _, it := range items {
sb.WriteString(it.Slot)
sb.WriteString("")
sb.WriteString(it.Name)
sb.WriteString("")
}
return strings.TrimSuffix(sb.String(), "")
}
func effectCacheKey(plan *entity.OutfitPlan, angle string) string {
sum := md5.Sum([]byte(fmt.Sprintf("%d:%s:%s", plan.Id, plan.Title, angle)))
return "plan:" + hex.EncodeToString(sum[:])
}