From 775995f3e8833790ce26c86ee3f0cbe3b2ea00df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Fri, 31 Jul 2026 15:15:04 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BA=94=E5=B1=82=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=2022=20=E8=A1=A8=20+=20=E5=B7=A5=E5=85=B7=E5=B9=B6?= =?UTF-8?q?=E5=85=A5=20agent/=20+=20=E6=94=AF=E4=BB=98=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=99=A8=E5=86=85=E8=81=94=EF=BC=88=E8=B7=AF=E7=94=B1=E9=9B=B6?= =?UTF-8?q?=E5=8F=98=E6=9B=B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- styleagent/controller/avatar_controller.go | 2 +- .../controller/body_measurement_controller.go | 2 +- styleagent/controller/hairstyle_controller.go | 2 +- .../controller/member_plan_controller.go | 37 +++ styleagent/controller/outfit_controller.go | 63 ----- .../outfit_generation_task_controller.go | 35 +++ .../controller/outfit_plan_controller.go | 33 +++ .../controller/pay_notify_log_controller.go | 3 + ...troller.go => payment_order_controller.go} | 26 +- .../plan_effect_image_controller.go | 4 + .../controller/plan_outfit_item_controller.go | 4 + .../controller/plan_review_controller.go | 19 ++ .../controller/scoring_rule_controller.go | 3 + styleagent/controller/user_controller.go | 2 +- .../controller/user_member_controller.go | 4 + styleagent/model/dto/ad_reward_log_dto.go | 19 ++ styleagent/model/dto/avatar_model_dto.go | 27 ++ styleagent/model/dto/body_measurement_dto.go | 24 ++ styleagent/model/dto/dto.go | 255 ------------------ styleagent/model/dto/hairstyle_asset_dto.go | 15 ++ styleagent/model/dto/member_plan_dto.go | 26 ++ .../model/dto/outfit_generation_task_dto.go | 26 ++ styleagent/model/dto/outfit_plan_dto.go | 32 +++ styleagent/model/dto/partner_store_dto.go | 16 ++ styleagent/model/dto/pay_notify_log_dto.go | 3 + styleagent/model/dto/payment_order_dto.go | 26 ++ styleagent/model/dto/plan_effect_image_dto.go | 3 + styleagent/model/dto/plan_outfit_item_dto.go | 3 + styleagent/model/dto/plan_review_dto.go | 12 + styleagent/model/dto/scoring_rule_dto.go | 3 + styleagent/model/dto/user_dto.go | 47 ++++ styleagent/model/dto/user_member_dto.go | 3 + styleagent/model/dto/user_photo_dto.go | 30 +++ styleagent/model/dto/wardrobe_item_dto.go | 41 +++ ...ad_service.go => ad_reward_log_service.go} | 0 ..._test.go => ad_reward_log_service_test.go} | 0 ...tar_service.go => avatar_model_service.go} | 0 ..._service.go => hairstyle_asset_service.go} | 0 styleagent/service/outfit_combiner.go | 69 ----- ...e.go => outfit_generation_task_service.go} | 178 ++++++------ styleagent/service/outfit_plan_service.go | 95 +++++++ ...ervice.go => plan_effect_image_service.go} | 22 +- .../service/plan_outfit_item_service.go | 16 ++ styleagent/service/plan_review_service.go | 22 ++ styleagent/service/scoring_rule_service.go | 53 ++++ ...be_service.go => wardrobe_item_service.go} | 0 styleagent/service/weather_service.go | 31 --- 47 files changed, 771 insertions(+), 565 deletions(-) create mode 100644 styleagent/controller/member_plan_controller.go delete mode 100644 styleagent/controller/outfit_controller.go create mode 100644 styleagent/controller/outfit_generation_task_controller.go create mode 100644 styleagent/controller/outfit_plan_controller.go create mode 100644 styleagent/controller/pay_notify_log_controller.go rename styleagent/controller/{member_controller.go => payment_order_controller.go} (75%) create mode 100644 styleagent/controller/plan_effect_image_controller.go create mode 100644 styleagent/controller/plan_outfit_item_controller.go create mode 100644 styleagent/controller/plan_review_controller.go create mode 100644 styleagent/controller/scoring_rule_controller.go create mode 100644 styleagent/controller/user_member_controller.go create mode 100644 styleagent/model/dto/ad_reward_log_dto.go create mode 100644 styleagent/model/dto/avatar_model_dto.go create mode 100644 styleagent/model/dto/body_measurement_dto.go delete mode 100644 styleagent/model/dto/dto.go create mode 100644 styleagent/model/dto/hairstyle_asset_dto.go create mode 100644 styleagent/model/dto/member_plan_dto.go create mode 100644 styleagent/model/dto/outfit_generation_task_dto.go create mode 100644 styleagent/model/dto/outfit_plan_dto.go create mode 100644 styleagent/model/dto/partner_store_dto.go create mode 100644 styleagent/model/dto/pay_notify_log_dto.go create mode 100644 styleagent/model/dto/payment_order_dto.go create mode 100644 styleagent/model/dto/plan_effect_image_dto.go create mode 100644 styleagent/model/dto/plan_outfit_item_dto.go create mode 100644 styleagent/model/dto/plan_review_dto.go create mode 100644 styleagent/model/dto/scoring_rule_dto.go create mode 100644 styleagent/model/dto/user_dto.go create mode 100644 styleagent/model/dto/user_member_dto.go create mode 100644 styleagent/model/dto/user_photo_dto.go create mode 100644 styleagent/model/dto/wardrobe_item_dto.go rename styleagent/service/{ad_service.go => ad_reward_log_service.go} (100%) rename styleagent/service/{ad_service_test.go => ad_reward_log_service_test.go} (100%) rename styleagent/service/{avatar_service.go => avatar_model_service.go} (100%) rename styleagent/service/{hairstyle_service.go => hairstyle_asset_service.go} (100%) delete mode 100644 styleagent/service/outfit_combiner.go rename styleagent/service/{outfit_service.go => outfit_generation_task_service.go} (73%) create mode 100644 styleagent/service/outfit_plan_service.go rename styleagent/service/{effect_image_service.go => plan_effect_image_service.go} (88%) create mode 100644 styleagent/service/plan_outfit_item_service.go create mode 100644 styleagent/service/plan_review_service.go create mode 100644 styleagent/service/scoring_rule_service.go rename styleagent/service/{wardrobe_service.go => wardrobe_item_service.go} (100%) delete mode 100644 styleagent/service/weather_service.go diff --git a/styleagent/controller/avatar_controller.go b/styleagent/controller/avatar_controller.go index 5e6d13e..7de7cdf 100644 --- a/styleagent/controller/avatar_controller.go +++ b/styleagent/controller/avatar_controller.go @@ -22,7 +22,7 @@ func (c *avatar) Build(ctx context.Context, req *dto.AvatarBuildReq) (res *dto.A return &dto.AvatarBuildRes{AvatarId: a.Id, Status: a.BuildStatus}, nil } -func (c *avatar) Get(ctx context.Context, req *struct{}) (res *dto.AvatarGetRes, err error) { +func (c *avatar) Get(ctx context.Context, req *dto.AvatarGetReq) (res *dto.AvatarGetRes, err error) { a, err := service.AvatarService.Get(ctx, common.GetUserId(g.RequestFromCtx(ctx))) if err != nil || a == nil { return &dto.AvatarGetRes{}, nil diff --git a/styleagent/controller/body_measurement_controller.go b/styleagent/controller/body_measurement_controller.go index b6d31ab..7cd2c07 100644 --- a/styleagent/controller/body_measurement_controller.go +++ b/styleagent/controller/body_measurement_controller.go @@ -27,7 +27,7 @@ func (c *body_measurement) Save(ctx context.Context, req *dto.BodyMeasurementSav return &struct{}{}, nil } -func (c *body_measurement) Get(ctx context.Context, req *struct{}) (res *dto.BodyMeasurementGetRes, err error) { +func (c *body_measurement) Get(ctx context.Context, req *dto.BodyMeasurementGetReq) (res *dto.BodyMeasurementGetRes, err error) { b, err := service.BodyMeasurementService.Get(ctx, common.GetUserId(g.RequestFromCtx(ctx))) if err != nil || b == nil { return &dto.BodyMeasurementGetRes{}, nil diff --git a/styleagent/controller/hairstyle_controller.go b/styleagent/controller/hairstyle_controller.go index 136d326..d0f3dfe 100644 --- a/styleagent/controller/hairstyle_controller.go +++ b/styleagent/controller/hairstyle_controller.go @@ -11,7 +11,7 @@ type hairstyle struct{} var Hairstyle = new(hairstyle) -func (c *hairstyle) List(ctx context.Context, req *struct{}) (res *dto.HairstyleListRes, err error) { +func (c *hairstyle) List(ctx context.Context, req *dto.HairstyleListReq) (res *dto.HairstyleListRes, err error) { list, err := service.HairstyleService.List(ctx) if err != nil { return nil, err diff --git a/styleagent/controller/member_plan_controller.go b/styleagent/controller/member_plan_controller.go new file mode 100644 index 0000000..c114091 --- /dev/null +++ b/styleagent/controller/member_plan_controller.go @@ -0,0 +1,37 @@ +package controller + +import ( + "context" + + commonHttp "slogan-agent/common" + "slogan-agent/styleagent/model/dto" + "slogan-agent/styleagent/service" + + "github.com/gogf/gf/v2/frame/g" +) + +// member 为 /member/* 簇共享控制器类型(struct 名 kebab-case 决定路由前缀, +// 多表拆分文件但保持同一类型,避免路径漂移) +type member struct{} + +var Member = new(member) + +// PlanList 会员套餐列表 +func (c *member) PlanList(ctx context.Context, req *dto.MemberPlanListReq) (res *dto.MemberPlanListRes, err error) { + list, err := service.MemberPlanService.PlanList(ctx) + if err != nil { + return nil, err + } + return &dto.MemberPlanListRes{List: list}, nil +} + +// Status 我的会员状态 +func (c *member) Status(ctx context.Context, req *dto.MemberStatusReq) (res *dto.MemberStatusRes, err error) { + st, err := service.MemberPlanService.Status(ctx, commonHttp.GetUserId(g.RequestFromCtx(ctx))) + if err != nil { + return nil, err + } + return &dto.MemberStatusRes{ + IsVip: st.IsVip, ExpireAt: st.ExpireAt, PlanName: st.PlanName, Benefits: st.Benefits, + }, nil +} diff --git a/styleagent/controller/outfit_controller.go b/styleagent/controller/outfit_controller.go deleted file mode 100644 index e3cb553..0000000 --- a/styleagent/controller/outfit_controller.go +++ /dev/null @@ -1,63 +0,0 @@ -package controller - -import ( - "context" - - "slogan-agent/common" - "slogan-agent/styleagent/model/dto" - "slogan-agent/styleagent/service" - - "github.com/gogf/gf/v2/frame/g" -) - -type outfit struct{} - -var Outfit = new(outfit) - -// Generate 生成穿搭方案(异步任务) -func (c *outfit) Generate(ctx context.Context, req *dto.OutfitGenerateReq) (res *dto.OutfitGenerateRes, err error) { - taskId, err := service.OutfitService.Generate(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req) - if err != nil { - return nil, err - } - return &dto.OutfitGenerateRes{TaskId: taskId}, nil -} - -// TaskStatus 查询生成任务状态 -func (c *outfit) TaskStatus(ctx context.Context, req *dto.OutfitTaskStatusReq) (res *dto.OutfitTaskStatusRes, err error) { - status, msg, err := service.OutfitService.GetTaskStatus(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req.TaskId) - if err != nil { - return nil, err - } - return &dto.OutfitTaskStatusRes{Status: status, Error: msg}, nil -} - -// PlanList 方案列表 -func (c *outfit) PlanList(ctx context.Context, req *dto.OutfitPlanListReq) (res *dto.OutfitPlanListRes, err error) { - list, err := service.OutfitService.ListPlans(ctx, common.GetUserId(g.RequestFromCtx(ctx))) - if err != nil { - return nil, err - } - return &dto.OutfitPlanListRes{List: list}, nil -} - -// PlanDetail 方案详情(items + images + hairstyle) -func (c *outfit) PlanDetail(ctx context.Context, req *dto.OutfitPlanDetailReq) (res *dto.OutfitPlanDetailRes, err error) { - return service.OutfitService.GetPlanDetail(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req.PlanId) -} - -// SelectMain 选定主方案(触发效果图生成) -func (c *outfit) SelectMain(ctx context.Context, req *dto.OutfitSelectMainReq) (res *struct{}, err error) { - if err = service.OutfitService.SelectMain(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req.PlanId); err != nil { - return nil, err - } - return &struct{}{}, nil -} - -// Review 方案反馈 -func (c *outfit) Review(ctx context.Context, req *dto.OutfitReviewReq) (res *struct{}, err error) { - if err = service.OutfitService.Review(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req.PlanId, req.Action, req.Note); err != nil { - return nil, err - } - return &struct{}{}, nil -} diff --git a/styleagent/controller/outfit_generation_task_controller.go b/styleagent/controller/outfit_generation_task_controller.go new file mode 100644 index 0000000..63fe6e9 --- /dev/null +++ b/styleagent/controller/outfit_generation_task_controller.go @@ -0,0 +1,35 @@ +package controller + +import ( + "context" + + "slogan-agent/common" + "slogan-agent/styleagent/model/dto" + "slogan-agent/styleagent/service" + + "github.com/gogf/gf/v2/frame/g" +) + +// outfit 为 /outfit/* 簇共享控制器类型(struct 名 kebab-case 决定路由前缀, +// 多表拆分文件但保持同一类型,避免路径漂移) +type outfit struct{} + +var Outfit = new(outfit) + +// Generate 生成穿搭方案(异步任务) +func (c *outfit) Generate(ctx context.Context, req *dto.OutfitGenerateReq) (res *dto.OutfitGenerateRes, err error) { + taskId, err := service.OutfitService.Generate(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req) + if err != nil { + return nil, err + } + return &dto.OutfitGenerateRes{TaskId: taskId}, nil +} + +// TaskStatus 查询生成任务状态 +func (c *outfit) TaskStatus(ctx context.Context, req *dto.OutfitTaskStatusReq) (res *dto.OutfitTaskStatusRes, err error) { + status, msg, err := service.OutfitService.GetTaskStatus(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req.TaskId) + if err != nil { + return nil, err + } + return &dto.OutfitTaskStatusRes{Status: status, Error: msg}, nil +} diff --git a/styleagent/controller/outfit_plan_controller.go b/styleagent/controller/outfit_plan_controller.go new file mode 100644 index 0000000..4efba71 --- /dev/null +++ b/styleagent/controller/outfit_plan_controller.go @@ -0,0 +1,33 @@ +package controller + +import ( + "context" + + "slogan-agent/common" + "slogan-agent/styleagent/model/dto" + "slogan-agent/styleagent/service" + + "github.com/gogf/gf/v2/frame/g" +) + +// PlanList 方案列表 +func (c *outfit) PlanList(ctx context.Context, req *dto.OutfitPlanListReq) (res *dto.OutfitPlanListRes, err error) { + list, err := service.OutfitPlanService.ListPlans(ctx, common.GetUserId(g.RequestFromCtx(ctx))) + if err != nil { + return nil, err + } + return &dto.OutfitPlanListRes{List: list}, nil +} + +// PlanDetail 方案详情(items + images + hairstyle) +func (c *outfit) PlanDetail(ctx context.Context, req *dto.OutfitPlanDetailReq) (res *dto.OutfitPlanDetailRes, err error) { + return service.OutfitPlanService.GetPlanDetail(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req.PlanId) +} + +// SelectMain 选定主方案(触发效果图生成) +func (c *outfit) SelectMain(ctx context.Context, req *dto.OutfitSelectMainReq) (res *struct{}, err error) { + if err = service.OutfitPlanService.SelectMain(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req.PlanId); err != nil { + return nil, err + } + return &struct{}{}, nil +} diff --git a/styleagent/controller/pay_notify_log_controller.go b/styleagent/controller/pay_notify_log_controller.go new file mode 100644 index 0000000..8bc6444 --- /dev/null +++ b/styleagent/controller/pay_notify_log_controller.go @@ -0,0 +1,3 @@ +package controller + +// pay_notify_log 表控制器:无独立路由 handler(回调日志由 /member/order/notify 审计写入) diff --git a/styleagent/controller/member_controller.go b/styleagent/controller/payment_order_controller.go similarity index 75% rename from styleagent/controller/member_controller.go rename to styleagent/controller/payment_order_controller.go index c060f04..f6447db 100644 --- a/styleagent/controller/member_controller.go +++ b/styleagent/controller/payment_order_controller.go @@ -13,30 +13,6 @@ import ( "github.com/gogf/gf/v2/net/ghttp" ) -type member struct{} - -var Member = new(member) - -// PlanList 会员套餐列表 -func (c *member) PlanList(ctx context.Context, req *dto.MemberPlanListReq) (res *dto.MemberPlanListRes, err error) { - list, err := service.MemberPlanService.PlanList(ctx) - if err != nil { - return nil, err - } - return &dto.MemberPlanListRes{List: list}, nil -} - -// Status 我的会员状态 -func (c *member) Status(ctx context.Context, req *dto.MemberStatusReq) (res *dto.MemberStatusRes, err error) { - st, err := service.MemberPlanService.Status(ctx, commonHttp.GetUserId(g.RequestFromCtx(ctx))) - if err != nil { - return nil, err - } - return &dto.MemberStatusRes{ - IsVip: st.IsVip, ExpireAt: st.ExpireAt, PlanName: st.PlanName, Benefits: st.Benefits, - }, nil -} - // OrderCreate 下单 → 返回支付 URL func (c *member) OrderCreate(ctx context.Context, req *dto.MemberOrderCreateReq) (res *dto.MemberOrderCreateRes, err error) { order, payURL, err := service.PaymentOrderService.CreateMemberOrder(ctx, commonHttp.GetUserId(g.RequestFromCtx(ctx)), req.PlanId) @@ -60,7 +36,7 @@ func (c *member) OrderStatus(ctx context.Context, req *dto.MemberOrderStatusReq) } // MemberNotify 虎皮棋支付回调:验签 → 幂等开通 → 返回裸文本 "success" -// 虎皮棋要求回调响应体为字面 "success",故不走统一 JSON 包装 +// 虎皮棋要求回调响应体为字面 "success",故不走统一 JSON 包装(main.go 手动绑定) func MemberNotify(r *ghttp.Request) { ctx := r.Context() body := r.GetBodyString() diff --git a/styleagent/controller/plan_effect_image_controller.go b/styleagent/controller/plan_effect_image_controller.go new file mode 100644 index 0000000..343e2fe --- /dev/null +++ b/styleagent/controller/plan_effect_image_controller.go @@ -0,0 +1,4 @@ +package controller + +// plan_effect_image 表控制器:无独立路由 handler,逻辑归属 outfit/* 簇 +// (效果图由选定主方案后异步生成,经 /outfit/plan/detail 返回) diff --git a/styleagent/controller/plan_outfit_item_controller.go b/styleagent/controller/plan_outfit_item_controller.go new file mode 100644 index 0000000..5de0273 --- /dev/null +++ b/styleagent/controller/plan_outfit_item_controller.go @@ -0,0 +1,4 @@ +package controller + +// plan_outfit_item 表控制器:无独立路由 handler,逻辑归属 outfit/* 簇 +// (方案条目数据由 /outfit/plan/detail 承载) diff --git a/styleagent/controller/plan_review_controller.go b/styleagent/controller/plan_review_controller.go new file mode 100644 index 0000000..bac9985 --- /dev/null +++ b/styleagent/controller/plan_review_controller.go @@ -0,0 +1,19 @@ +package controller + +import ( + "context" + + "slogan-agent/common" + "slogan-agent/styleagent/model/dto" + "slogan-agent/styleagent/service" + + "github.com/gogf/gf/v2/frame/g" +) + +// Review 方案反馈 +func (c *outfit) Review(ctx context.Context, req *dto.OutfitReviewReq) (res *struct{}, err error) { + if err = service.PlanReviewService.Review(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req.PlanId, req.Action, req.Note); err != nil { + return nil, err + } + return &struct{}{}, nil +} diff --git a/styleagent/controller/scoring_rule_controller.go b/styleagent/controller/scoring_rule_controller.go new file mode 100644 index 0000000..07219b9 --- /dev/null +++ b/styleagent/controller/scoring_rule_controller.go @@ -0,0 +1,3 @@ +package controller + +// scoring_rule 表控制器:无独立路由 handler,规则在服务端读取(评分阈值/效果图额度) diff --git a/styleagent/controller/user_controller.go b/styleagent/controller/user_controller.go index 12784a5..4875868 100644 --- a/styleagent/controller/user_controller.go +++ b/styleagent/controller/user_controller.go @@ -38,7 +38,7 @@ func (c *user) ChangePassword(ctx context.Context, req *dto.ChangePasswordReq) ( return nil, service.UserService.ChangePassword(ctx, common.GetUserId(g.RequestFromCtx(ctx)), req.OldPassword, req.NewPassword) } -func (c *user) Profile(ctx context.Context, req *struct{}) (res *dto.ProfileRes, err error) { +func (c *user) Profile(ctx context.Context, req *dto.ProfileReq) (res *dto.ProfileRes, err error) { user, err := dao.User.GetOne(ctx, common.GetUserId(g.RequestFromCtx(ctx))) if err != nil || user == nil { return nil, err diff --git a/styleagent/controller/user_member_controller.go b/styleagent/controller/user_member_controller.go new file mode 100644 index 0000000..43bf417 --- /dev/null +++ b/styleagent/controller/user_member_controller.go @@ -0,0 +1,4 @@ +package controller + +// user_member 表控制器:无独立路由 handler(会员状态经 /member/status 返回, +// 开通由支付回调与广告激励写入) diff --git a/styleagent/model/dto/ad_reward_log_dto.go b/styleagent/model/dto/ad_reward_log_dto.go new file mode 100644 index 0000000..b57867b --- /dev/null +++ b/styleagent/model/dto/ad_reward_log_dto.go @@ -0,0 +1,19 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type AdRewardClaimReq struct { + g.Meta `path:"/reward/claim" method:"post" tags:"广告" summary:"领取广告激励"` + AdType string `v:"required|in:effect_extra,vip_trial" json:"ad_type"` +} + +type AdRewardInfo struct { + AdType string `json:"ad_type"` + RemainingToday int `json:"remaining_today"` +} + +type AdRewardClaimRes struct { + Reward *AdRewardInfo `json:"reward"` +} diff --git a/styleagent/model/dto/avatar_model_dto.go b/styleagent/model/dto/avatar_model_dto.go new file mode 100644 index 0000000..150a7aa --- /dev/null +++ b/styleagent/model/dto/avatar_model_dto.go @@ -0,0 +1,27 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type AvatarBuildReq struct { + g.Meta `path:"/build" method:"post" tags:"化身" summary:"构建化身"` +} + +type AvatarBuildRes struct { + AvatarId int64 `json:"avatar_id"` + Status string `json:"status"` +} + +type AvatarGetReq struct { + g.Meta `path:"/get" method:"get" tags:"化身" summary:"我的化身"` +} + +type AvatarGetRes struct { + FaceTemplateId int `json:"face_template_id"` + BodyTemplateId int `json:"body_template_id"` + SkinToneIndex int `json:"skin_tone_index"` + GlbUrl string `json:"glb_url"` + BuildStatus string `json:"build_status"` + Error string `json:"error"` +} diff --git a/styleagent/model/dto/body_measurement_dto.go b/styleagent/model/dto/body_measurement_dto.go new file mode 100644 index 0000000..3fcfd21 --- /dev/null +++ b/styleagent/model/dto/body_measurement_dto.go @@ -0,0 +1,24 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type BodyMeasurementSaveReq struct { + g.Meta `path:"/save" method:"post" tags:"身形" summary:"保存身形参数"` + Height int `json:"height"` + Weight int `json:"weight"` + SkinTone int `v:"in:1,2,3,4,5" json:"skin_tone"` + FitParams string `json:"fit_params"` +} + +type BodyMeasurementGetReq struct { + g.Meta `path:"/get" method:"get" tags:"身形" summary:"我的身形参数"` +} + +type BodyMeasurementGetRes struct { + Height int `json:"height"` + Weight int `json:"weight"` + SkinTone int `json:"skin_tone"` + FitParams string `json:"fit_params"` +} diff --git a/styleagent/model/dto/dto.go b/styleagent/model/dto/dto.go deleted file mode 100644 index ae44a0a..0000000 --- a/styleagent/model/dto/dto.go +++ /dev/null @@ -1,255 +0,0 @@ -package dto - -import ( - "slogan-agent/styleagent/model/entity" - - "github.com/gogf/gf/v2/frame/g" -) - -type LoginReq struct { - g.Meta `path:"/login" method:"post" tags:"用户" summary:"登录"` - Account string `v:"required" json:"account"` - Password string `v:"required" json:"password"` -} - -type LoginRes struct { - Token string `json:"token"` - User *LoginUser `json:"user"` -} - -type LoginUser struct { - Id int64 `json:"id"` - Role string `json:"role"` - Name string `json:"name"` -} - -type ChangePasswordReq struct { - g.Meta `path:"/change-password" method:"post" tags:"用户" summary:"修改密码"` - OldPassword string `v:"required" json:"old_password"` - NewPassword string `v:"required|min-length:6" json:"new_password"` -} - -type RegisterReq struct { - g.Meta `path:"/register" method:"post" tags:"用户" summary:"注册"` - Account string `v:"required" json:"account"` - Password string `v:"required|min-length:6" json:"password"` - Name string `json:"name"` -} - -type ProfileRes struct { - Id int64 `json:"id"` - Role string `json:"role"` - Name string `json:"name"` - Username string `json:"username"` - Phone string `json:"phone"` -} - -type UserPhotoUploadReq struct { - g.Meta `path:"/upload" method:"post" tags:"照片" summary:"上传照片"` - Type int `v:"required|in:1,2,3,4" json:"type"` -} - -type UserPhotoUploadRes struct { - Id int64 `json:"id"` -} - -type UserPhotoListReq struct { - g.Meta `path:"/list" method:"get" tags:"照片" summary:"照片列表"` - Type int `json:"type"` -} - -type UserPhotoListRes struct { - List []*entity.UserPhoto `json:"list"` -} - -type UserPhotoDeleteReq struct { - g.Meta `path:"/delete" method:"post" tags:"照片" summary:"删除照片"` - Id int64 `v:"required" json:"id"` -} - -type WardrobeUploadReq struct { - g.Meta `path:"/upload" method:"post" tags:"衣橱" summary:"上传服装"` - Category string `v:"required|in:上衣,下装,鞋,配饰" json:"category"` - Season string `json:"season"` - StyleTags string `json:"style_tags"` - ColorInfo string `json:"color_info"` -} - -type WardrobeUploadRes struct { - Id int64 `json:"id"` -} - -type WardrobeListReq struct { - g.Meta `path:"/list" method:"get" tags:"衣橱" summary:"衣橱列表"` - Category string `json:"category"` -} - -type WardrobeListRes struct { - List []*entity.WardrobeItem `json:"list"` -} - -type WardrobeUpdateReq struct { - g.Meta `path:"/update" method:"post" tags:"衣橱" summary:"更新服装"` - Id int64 `v:"required" json:"id"` - Category string `json:"category"` - Season string `json:"season"` - StyleTags string `json:"style_tags"` -} - -type WardrobeDeleteReq struct { - g.Meta `path:"/delete" method:"post" tags:"衣橱" summary:"删除服装"` - Id int64 `v:"required" json:"id"` -} - -type BodyMeasurementSaveReq struct { - g.Meta `path:"/save" method:"post" tags:"身形" summary:"保存身形参数"` - Height int `json:"height"` - Weight int `json:"weight"` - SkinTone int `v:"in:1,2,3,4,5" json:"skin_tone"` - FitParams string `json:"fit_params"` -} - -type BodyMeasurementGetRes struct { - Height int `json:"height"` - Weight int `json:"weight"` - SkinTone int `json:"skin_tone"` - FitParams string `json:"fit_params"` -} - -type AvatarBuildReq struct { - g.Meta `path:"/build" method:"post" tags:"化身" summary:"构建化身"` -} - -type AvatarBuildRes struct { - AvatarId int64 `json:"avatar_id"` - Status string `json:"status"` -} - -type AvatarGetRes struct { - FaceTemplateId int `json:"face_template_id"` - BodyTemplateId int `json:"body_template_id"` - SkinToneIndex int `json:"skin_tone_index"` - GlbUrl string `json:"glb_url"` - BuildStatus string `json:"build_status"` - Error string `json:"error"` -} - -type HairstyleListRes struct { - List []*entity.HairstyleAsset `json:"list"` -} - -type OutfitGenerateReq struct { - g.Meta `path:"/generate" method:"post" tags:"穿搭" summary:"生成穿搭方案"` - StartDate string `v:"required|date" json:"start_date"` - EndDate string `v:"required|date" json:"end_date"` - Location string `v:"required" json:"location"` -} - -type OutfitGenerateRes struct { - TaskId int64 `json:"task_id"` -} - -type OutfitTaskStatusReq struct { - g.Meta `path:"/task/status" method:"get" tags:"穿搭" summary:"任务状态"` - TaskId int64 `v:"required" json:"task_id"` -} - -type OutfitTaskStatusRes struct { - Status string `json:"status"` - Error string `json:"error"` -} - -type OutfitPlanListReq struct { - g.Meta `path:"/plan/list" method:"get" tags:"穿搭" summary:"方案列表"` -} - -type OutfitPlanListRes struct { - List []*entity.OutfitPlan `json:"list"` -} - -type OutfitPlanDetailReq struct { - g.Meta `path:"/plan/detail" method:"get" tags:"穿搭" summary:"方案详情"` - PlanId int64 `v:"required" json:"plan_id"` -} - -type OutfitPlanDetailRes struct { - Plan *entity.OutfitPlan `json:"plan"` - Items []*entity.PlanOutfitItem `json:"items"` - Images []*entity.PlanEffectImage `json:"images"` - Hairstyle *entity.HairstyleAsset `json:"hairstyle,omitempty"` -} - -type OutfitSelectMainReq struct { - g.Meta `path:"/plan/select-main" method:"post" tags:"穿搭" summary:"选定主方案"` - PlanId int64 `v:"required" json:"plan_id"` -} - -type OutfitReviewReq struct { - g.Meta `path:"/plan/review" method:"post" tags:"穿搭" summary:"方案反馈"` - PlanId int64 `v:"required" json:"plan_id"` - Action string `v:"required|in:fav,unfav" json:"action"` - Note string `json:"note"` -} - -type StoreListReq struct { - g.Meta `path:"/list" method:"get" tags:"门店" summary:"合作门店列表"` - Type int `json:"type"` -} - -type StoreListRes struct { - List []*entity.PartnerStore `json:"list"` -} - -type MemberPlanListReq struct { - g.Meta `path:"/plan/list" method:"get" tags:"会员" summary:"会员套餐列表"` -} - -type MemberPlanListRes struct { - List []*entity.MemberPlan `json:"list"` -} - -type MemberStatusReq struct { - g.Meta `path:"/status" method:"get" tags:"会员" summary:"我的会员状态"` -} - -type MemberStatusRes struct { - IsVip bool `json:"is_vip"` - ExpireAt string `json:"expire_at"` - PlanName string `json:"plan_name"` - Benefits []string `json:"benefits"` -} - -type MemberOrderCreateReq struct { - g.Meta `path:"/order/create" method:"post" tags:"会员" summary:"创建支付订单"` - PlanId int64 `v:"required" json:"plan_id"` -} - -type MemberOrderCreateRes struct { - OrderNo string `json:"order_no"` - PayUrl string `json:"pay_url"` -} - -type MemberOrderStatusReq struct { - g.Meta `path:"/order/status" method:"get" tags:"会员" summary:"订单状态"` - OrderNo string `v:"required" json:"order_no"` -} - -type MemberOrderStatusRes struct { - Status string `json:"status"` - TradeNo string `json:"trade_no"` - PaidAt string `json:"paid_at"` -} - -type AdRewardClaimReq struct { - g.Meta `path:"/reward/claim" method:"post" tags:"广告" summary:"领取广告激励"` - AdType string `v:"required|in:effect_extra,vip_trial" json:"ad_type"` -} - -type AdRewardInfo struct { - AdType string `json:"ad_type"` - RemainingToday int `json:"remaining_today"` -} - -type AdRewardClaimRes struct { - Reward *AdRewardInfo `json:"reward"` -} diff --git a/styleagent/model/dto/hairstyle_asset_dto.go b/styleagent/model/dto/hairstyle_asset_dto.go new file mode 100644 index 0000000..c611f4b --- /dev/null +++ b/styleagent/model/dto/hairstyle_asset_dto.go @@ -0,0 +1,15 @@ +package dto + +import ( + "slogan-agent/styleagent/model/entity" + + "github.com/gogf/gf/v2/frame/g" +) + +type HairstyleListReq struct { + g.Meta `path:"/list" method:"get" tags:"发型" summary:"发型资产列表"` +} + +type HairstyleListRes struct { + List []*entity.HairstyleAsset `json:"list"` +} diff --git a/styleagent/model/dto/member_plan_dto.go b/styleagent/model/dto/member_plan_dto.go new file mode 100644 index 0000000..28496df --- /dev/null +++ b/styleagent/model/dto/member_plan_dto.go @@ -0,0 +1,26 @@ +package dto + +import ( + "slogan-agent/styleagent/model/entity" + + "github.com/gogf/gf/v2/frame/g" +) + +type MemberPlanListReq struct { + g.Meta `path:"/plan/list" method:"get" tags:"会员" summary:"会员套餐列表"` +} + +type MemberPlanListRes struct { + List []*entity.MemberPlan `json:"list"` +} + +type MemberStatusReq struct { + g.Meta `path:"/status" method:"get" tags:"会员" summary:"我的会员状态"` +} + +type MemberStatusRes struct { + IsVip bool `json:"is_vip"` + ExpireAt string `json:"expire_at"` + PlanName string `json:"plan_name"` + Benefits []string `json:"benefits"` +} diff --git a/styleagent/model/dto/outfit_generation_task_dto.go b/styleagent/model/dto/outfit_generation_task_dto.go new file mode 100644 index 0000000..86445c5 --- /dev/null +++ b/styleagent/model/dto/outfit_generation_task_dto.go @@ -0,0 +1,26 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type OutfitGenerateReq struct { + g.Meta `path:"/generate" method:"post" tags:"穿搭" summary:"生成穿搭方案"` + StartDate string `v:"required|date" json:"start_date"` + EndDate string `v:"required|date" json:"end_date"` + Location string `v:"required" json:"location"` +} + +type OutfitGenerateRes struct { + TaskId int64 `json:"task_id"` +} + +type OutfitTaskStatusReq struct { + g.Meta `path:"/task/status" method:"get" tags:"穿搭" summary:"任务状态"` + TaskId int64 `v:"required" json:"task_id"` +} + +type OutfitTaskStatusRes struct { + Status string `json:"status"` + Error string `json:"error"` +} diff --git a/styleagent/model/dto/outfit_plan_dto.go b/styleagent/model/dto/outfit_plan_dto.go new file mode 100644 index 0000000..4c4af89 --- /dev/null +++ b/styleagent/model/dto/outfit_plan_dto.go @@ -0,0 +1,32 @@ +package dto + +import ( + "slogan-agent/styleagent/model/entity" + + "github.com/gogf/gf/v2/frame/g" +) + +type OutfitPlanListReq struct { + g.Meta `path:"/plan/list" method:"get" tags:"穿搭" summary:"方案列表"` +} + +type OutfitPlanListRes struct { + List []*entity.OutfitPlan `json:"list"` +} + +type OutfitPlanDetailReq struct { + g.Meta `path:"/plan/detail" method:"get" tags:"穿搭" summary:"方案详情"` + PlanId int64 `v:"required" json:"plan_id"` +} + +type OutfitPlanDetailRes struct { + Plan *entity.OutfitPlan `json:"plan"` + Items []*entity.PlanOutfitItem `json:"items"` + Images []*entity.PlanEffectImage `json:"images"` + Hairstyle *entity.HairstyleAsset `json:"hairstyle,omitempty"` +} + +type OutfitSelectMainReq struct { + g.Meta `path:"/plan/select-main" method:"post" tags:"穿搭" summary:"选定主方案"` + PlanId int64 `v:"required" json:"plan_id"` +} diff --git a/styleagent/model/dto/partner_store_dto.go b/styleagent/model/dto/partner_store_dto.go new file mode 100644 index 0000000..967de48 --- /dev/null +++ b/styleagent/model/dto/partner_store_dto.go @@ -0,0 +1,16 @@ +package dto + +import ( + "slogan-agent/styleagent/model/entity" + + "github.com/gogf/gf/v2/frame/g" +) + +type StoreListReq struct { + g.Meta `path:"/list" method:"get" tags:"门店" summary:"合作门店列表"` + Type int `json:"type"` +} + +type StoreListRes struct { + List []*entity.PartnerStore `json:"list"` +} diff --git a/styleagent/model/dto/pay_notify_log_dto.go b/styleagent/model/dto/pay_notify_log_dto.go new file mode 100644 index 0000000..3868774 --- /dev/null +++ b/styleagent/model/dto/pay_notify_log_dto.go @@ -0,0 +1,3 @@ +package dto + +// 支付回调日志(pay_notify_log)为服务端记录,无 HTTP 接口。 diff --git a/styleagent/model/dto/payment_order_dto.go b/styleagent/model/dto/payment_order_dto.go new file mode 100644 index 0000000..7303d04 --- /dev/null +++ b/styleagent/model/dto/payment_order_dto.go @@ -0,0 +1,26 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type MemberOrderCreateReq struct { + g.Meta `path:"/order/create" method:"post" tags:"会员" summary:"创建支付订单"` + PlanId int64 `v:"required" json:"plan_id"` +} + +type MemberOrderCreateRes struct { + OrderNo string `json:"order_no"` + PayUrl string `json:"pay_url"` +} + +type MemberOrderStatusReq struct { + g.Meta `path:"/order/status" method:"get" tags:"会员" summary:"订单状态"` + OrderNo string `v:"required" json:"order_no"` +} + +type MemberOrderStatusRes struct { + Status string `json:"status"` + TradeNo string `json:"trade_no"` + PaidAt string `json:"paid_at"` +} diff --git a/styleagent/model/dto/plan_effect_image_dto.go b/styleagent/model/dto/plan_effect_image_dto.go new file mode 100644 index 0000000..e4f8d1e --- /dev/null +++ b/styleagent/model/dto/plan_effect_image_dto.go @@ -0,0 +1,3 @@ +package dto + +// 方案效果图(plan_effect_image)无独立 HTTP 接口,由方案选定流程在服务端生成,客户端经方案详情获取。 diff --git a/styleagent/model/dto/plan_outfit_item_dto.go b/styleagent/model/dto/plan_outfit_item_dto.go new file mode 100644 index 0000000..fba399e --- /dev/null +++ b/styleagent/model/dto/plan_outfit_item_dto.go @@ -0,0 +1,3 @@ +package dto + +// 方案穿搭项(plan_outfit_item)无独立 HTTP 接口,请求/响应经 outfit_plan_dto.go 透传。 diff --git a/styleagent/model/dto/plan_review_dto.go b/styleagent/model/dto/plan_review_dto.go new file mode 100644 index 0000000..7493c00 --- /dev/null +++ b/styleagent/model/dto/plan_review_dto.go @@ -0,0 +1,12 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type OutfitReviewReq struct { + g.Meta `path:"/plan/review" method:"post" tags:"穿搭" summary:"方案反馈"` + PlanId int64 `v:"required" json:"plan_id"` + Action string `v:"required|in:fav,unfav" json:"action"` + Note string `json:"note"` +} diff --git a/styleagent/model/dto/scoring_rule_dto.go b/styleagent/model/dto/scoring_rule_dto.go new file mode 100644 index 0000000..598681b --- /dev/null +++ b/styleagent/model/dto/scoring_rule_dto.go @@ -0,0 +1,3 @@ +package dto + +// 评分规则(scoring_rule)为服务端内部配置,无 HTTP 接口。 diff --git a/styleagent/model/dto/user_dto.go b/styleagent/model/dto/user_dto.go new file mode 100644 index 0000000..f23f863 --- /dev/null +++ b/styleagent/model/dto/user_dto.go @@ -0,0 +1,47 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type LoginReq struct { + g.Meta `path:"/login" method:"post" tags:"用户" summary:"登录"` + Account string `v:"required" json:"account"` + Password string `v:"required" json:"password"` +} + +type LoginRes struct { + Token string `json:"token"` + User *LoginUser `json:"user"` +} + +type LoginUser struct { + Id int64 `json:"id"` + Role string `json:"role"` + Name string `json:"name"` +} + +type ChangePasswordReq struct { + g.Meta `path:"/change-password" method:"post" tags:"用户" summary:"修改密码"` + OldPassword string `v:"required" json:"old_password"` + NewPassword string `v:"required|min-length:6" json:"new_password"` +} + +type RegisterReq struct { + g.Meta `path:"/register" method:"post" tags:"用户" summary:"注册"` + Account string `v:"required" json:"account"` + Password string `v:"required|min-length:6" json:"password"` + Name string `json:"name"` +} + +type ProfileReq struct { + g.Meta `path:"/profile" method:"get" tags:"用户" summary:"我的资料"` +} + +type ProfileRes struct { + Id int64 `json:"id"` + Role string `json:"role"` + Name string `json:"name"` + Username string `json:"username"` + Phone string `json:"phone"` +} diff --git a/styleagent/model/dto/user_member_dto.go b/styleagent/model/dto/user_member_dto.go new file mode 100644 index 0000000..a59c18b --- /dev/null +++ b/styleagent/model/dto/user_member_dto.go @@ -0,0 +1,3 @@ +package dto + +// 用户会员关系(user_member)由支付回调/下单流程维护,状态经 member_plan_dto.go 返回。 diff --git a/styleagent/model/dto/user_photo_dto.go b/styleagent/model/dto/user_photo_dto.go new file mode 100644 index 0000000..20a61fb --- /dev/null +++ b/styleagent/model/dto/user_photo_dto.go @@ -0,0 +1,30 @@ +package dto + +import ( + "slogan-agent/styleagent/model/entity" + + "github.com/gogf/gf/v2/frame/g" +) + +type UserPhotoUploadReq struct { + g.Meta `path:"/upload" method:"post" tags:"照片" summary:"上传照片"` + Type int `v:"required|in:1,2,3,4" json:"type"` +} + +type UserPhotoUploadRes struct { + Id int64 `json:"id"` +} + +type UserPhotoListReq struct { + g.Meta `path:"/list" method:"get" tags:"照片" summary:"照片列表"` + Type int `json:"type"` +} + +type UserPhotoListRes struct { + List []*entity.UserPhoto `json:"list"` +} + +type UserPhotoDeleteReq struct { + g.Meta `path:"/delete" method:"post" tags:"照片" summary:"删除照片"` + Id int64 `v:"required" json:"id"` +} diff --git a/styleagent/model/dto/wardrobe_item_dto.go b/styleagent/model/dto/wardrobe_item_dto.go new file mode 100644 index 0000000..e3c70bd --- /dev/null +++ b/styleagent/model/dto/wardrobe_item_dto.go @@ -0,0 +1,41 @@ +package dto + +import ( + "slogan-agent/styleagent/model/entity" + + "github.com/gogf/gf/v2/frame/g" +) + +type WardrobeUploadReq struct { + g.Meta `path:"/upload" method:"post" tags:"衣橱" summary:"上传服装"` + Category string `v:"required|in:上衣,下装,鞋,配饰" json:"category"` + Season string `json:"season"` + StyleTags string `json:"style_tags"` + ColorInfo string `json:"color_info"` +} + +type WardrobeUploadRes struct { + Id int64 `json:"id"` +} + +type WardrobeListReq struct { + g.Meta `path:"/list" method:"get" tags:"衣橱" summary:"衣橱列表"` + Category string `json:"category"` +} + +type WardrobeListRes struct { + List []*entity.WardrobeItem `json:"list"` +} + +type WardrobeUpdateReq struct { + g.Meta `path:"/update" method:"post" tags:"衣橱" summary:"更新服装"` + Id int64 `v:"required" json:"id"` + Category string `json:"category"` + Season string `json:"season"` + StyleTags string `json:"style_tags"` +} + +type WardrobeDeleteReq struct { + g.Meta `path:"/delete" method:"post" tags:"衣橱" summary:"删除服装"` + Id int64 `v:"required" json:"id"` +} diff --git a/styleagent/service/ad_service.go b/styleagent/service/ad_reward_log_service.go similarity index 100% rename from styleagent/service/ad_service.go rename to styleagent/service/ad_reward_log_service.go diff --git a/styleagent/service/ad_service_test.go b/styleagent/service/ad_reward_log_service_test.go similarity index 100% rename from styleagent/service/ad_service_test.go rename to styleagent/service/ad_reward_log_service_test.go diff --git a/styleagent/service/avatar_service.go b/styleagent/service/avatar_model_service.go similarity index 100% rename from styleagent/service/avatar_service.go rename to styleagent/service/avatar_model_service.go diff --git a/styleagent/service/hairstyle_service.go b/styleagent/service/hairstyle_asset_service.go similarity index 100% rename from styleagent/service/hairstyle_service.go rename to styleagent/service/hairstyle_asset_service.go diff --git a/styleagent/service/outfit_combiner.go b/styleagent/service/outfit_combiner.go deleted file mode 100644 index 2e42be5..0000000 --- a/styleagent/service/outfit_combiner.go +++ /dev/null @@ -1,69 +0,0 @@ -package service - -import ( - "slogan-agent/styleagent/agent" - "slogan-agent/styleagent/model/entity" -) - -// candidateSet 一套预筛组合 -type candidateSet struct { - Items []*entity.WardrobeItem - HasOuterwear bool -} - -// combineCandidates 预筛组合算法: -// 按 category 分组 → 按季节过滤 → 确定性轮询组合,最多 3 套互不相同(含外套标记) -func combineCandidates(items []*entity.WardrobeItem, season string, maxSets int) []candidateSet { - groups := map[string][]*entity.WardrobeItem{} - for _, it := range items { - if season != "" && it.Season != "" && it.Season != "四季" && it.Season != season { - continue - } - groups[it.Category] = append(groups[it.Category], it) - } - if len(groups) == 0 || maxSets <= 0 { - return nil - } - - var sets []candidateSet - cats := []string{"上衣", "下装", "鞋", "配饰"} - for i := 0; i < maxSets; i++ { - set := candidateSet{} - hasOuterwear := false - for _, cat := range cats { - g := groups[cat] - if len(g) == 0 { - continue - } - it := g[i%len(g)] - set.Items = append(set.Items, it) - if isOuterwear(it) { - hasOuterwear = true - } - } - if len(set.Items) == 0 { - break - } - set.HasOuterwear = hasOuterwear - sets = append(sets, set) - } - return sets -} - -func isOuterwear(it *entity.WardrobeItem) bool { - return it.Category == "上衣" && (it.StyleTags == "" || it.StyleTags == "外套") -} - -// toScoringOutfit 转评分用候选 -func toScoringOutfit(set candidateSet) agent.CandidateOutfit { - o := agent.CandidateOutfit{HasOuterwear: set.HasOuterwear} - for _, it := range set.Items { - o.Items = append(o.Items, agent.WardrobeItem{ - Category: it.Category, - Season: it.Season, - ColorInfo: it.ColorInfo, - StyleTags: it.StyleTags, - }) - } - return o -} diff --git a/styleagent/service/outfit_service.go b/styleagent/service/outfit_generation_task_service.go similarity index 73% rename from styleagent/service/outfit_service.go rename to styleagent/service/outfit_generation_task_service.go index abbfea5..087e79f 100644 --- a/styleagent/service/outfit_service.go +++ b/styleagent/service/outfit_generation_task_service.go @@ -128,7 +128,7 @@ func runGenerateTask(ctx context.Context, taskId, userId int64) { // 5. 规则评分 setTask(consts.TaskStatusScoring, "") - threshold := scoringThreshold(ctx) + threshold := ScoringRuleService.Threshold(ctx) plans := out.Plans ctxScore := agent.ScoreContext{ TempAvg: weatherResult.AvgTemp, Season: weatherResult.Season, @@ -211,7 +211,7 @@ func scorePlan(p agent.PlanCandidate, items []*entity.WardrobeItem, ctxScore age return agent.Score(&out, &ctxScore) } -// ==================== 查询/操作 ==================== +// ==================== 查询 ==================== func (s *outfitService) GetTaskStatus(ctx context.Context, userId, taskId int64) (string, string, error) { t, err := dao.OutfitGenTask.GetOne(ctx, taskId, userId) @@ -221,94 +221,101 @@ func (s *outfitService) GetTaskStatus(ctx context.Context, userId, taskId int64) return t.Status, t.Error, nil } -func (s *outfitService) ListPlans(ctx context.Context, userId int64) ([]*entity.OutfitPlan, error) { - return dao.OutfitPlan.ListByUser(ctx, userId) -} +// ==================== 天气(原 weather_service 内联) ==================== -func (s *outfitService) GetPlanDetail(ctx context.Context, userId, planId int64) (*dto.OutfitPlanDetailRes, error) { - plan, err := dao.OutfitPlan.GetOne(ctx, planId, userId) - if err != nil || plan == nil { - return nil, errors.New("方案不存在") - } - res := &dto.OutfitPlanDetailRes{Plan: plan} - res.Items, err = dao.PlanOutfitItem.ListByPlan(ctx, planId) +var weatherCache = agent.NewTTLCache(6 * time.Hour) + +// GetWeather 地点 + 日期范围 → 天气结果(高德地理编码 + 和风 7 天预报,缓存 6 小时) +func GetWeather(ctx context.Context, location, startDate, endDate string) (*agent.WeatherResult, error) { + cityCode, err := agent.GetCityCode(ctx, location) if err != nil { return nil, err } - res.Images, err = dao.PlanEffectImage.ListByPlan(ctx, planId) + cacheKey := fmt.Sprintf("%s:%s:%s", cityCode, startDate, endDate) + if v, ok := weatherCache.Get(cacheKey); ok { + if result, ok := v.(*agent.WeatherResult); ok { + return result, nil + } + } + result, err := agent.GetDaily(ctx, cityCode, startDate, endDate) if err != nil { return nil, err } - if plan.HairstyleId > 0 { - res.Hairstyle, _ = dao.HairstyleAsset.GetOne(ctx, plan.HairstyleId) - } - return res, nil + weatherCache.Set(cacheKey, result) + return result, nil } -// SelectMain 选定主方案(同任务其他方案清零)+ 异步生成效果图 -func (s *outfitService) SelectMain(ctx context.Context, userId, planId int64) error { - plan, err := dao.OutfitPlan.GetOne(ctx, planId, userId) - if err != nil || plan == nil { - return errors.New("方案不存在") - } - if err := dao.OutfitPlan.ClearMainFlag(ctx, plan.TaskId); err != nil { - return err - } - if err := dao.OutfitPlan.SetMainFlag(ctx, planId); err != nil { - return err - } - // 异步生成 3 视角效果图 - EffectImageService.GenerateForPlan(gctx.New(), planId, userId) - return nil +func weatherSummaryText(w *agent.WeatherResult) string { + return fmt.Sprintf("%s(%s),平均 %d℃,%d 天", w.CityCode, w.Season, w.AvgTemp, len(w.Days)) } -func (s *outfitService) Review(ctx context.Context, userId, planId int64, action, note string) error { - plan, err := dao.OutfitPlan.GetOne(ctx, planId, userId) - if err != nil || plan == nil { - return errors.New("方案不存在") +// ==================== 预筛组合(原 outfit_combiner 内联) ==================== + +// candidateSet 一套预筛组合 +type candidateSet struct { + Items []*entity.WardrobeItem + HasOuterwear bool +} + +// combineCandidates 预筛组合算法: +// 按 category 分组 → 按季节过滤 → 确定性轮询组合,最多 3 套互不相同(含外套标记) +func combineCandidates(items []*entity.WardrobeItem, season string, maxSets int) []candidateSet { + groups := map[string][]*entity.WardrobeItem{} + for _, it := range items { + if season != "" && it.Season != "" && it.Season != "四季" && it.Season != season { + continue + } + groups[it.Category] = append(groups[it.Category], it) } - _, err = dao.PlanReview.Insert(ctx, &entity.PlanReview{PlanId: planId, UserId: userId, Action: action, Note: note}) - return err + if len(groups) == 0 || maxSets <= 0 { + return nil + } + + var sets []candidateSet + cats := []string{"上衣", "下装", "鞋", "配饰"} + for i := 0; i < maxSets; i++ { + set := candidateSet{} + hasOuterwear := false + for _, cat := range cats { + g := groups[cat] + if len(g) == 0 { + continue + } + it := g[i%len(g)] + set.Items = append(set.Items, it) + if isOuterwear(it) { + hasOuterwear = true + } + } + if len(set.Items) == 0 { + break + } + set.HasOuterwear = hasOuterwear + sets = append(sets, set) + } + return sets +} + +func isOuterwear(it *entity.WardrobeItem) bool { + return it.Category == "上衣" && (it.StyleTags == "" || it.StyleTags == "外套") +} + +// toScoringOutfit 转评分用候选 +func toScoringOutfit(set candidateSet) agent.CandidateOutfit { + o := agent.CandidateOutfit{HasOuterwear: set.HasOuterwear} + for _, it := range set.Items { + o.Items = append(o.Items, agent.WardrobeItem{ + Category: it.Category, + Season: it.Season, + ColorInfo: it.ColorInfo, + StyleTags: it.StyleTags, + }) + } + return o } // ==================== 内部辅助 ==================== -func planSource(p agent.PlanCandidate) string { - for _, it := range p.Items { - if it.NewItem || it.ItemId == 0 { - return consts.PlanSourceRecommend - } - } - return consts.PlanSourceWardrobe -} - -func matchHairstyle(name string, all []*entity.HairstyleAsset) int64 { - if name == "" { - return 0 - } - for _, h := range all { - if h.Name == name { - return h.Id - } - } - return 0 -} - -func hairstyleListText(ctx context.Context) string { - all, err := dao.HairstyleAsset.ListAll(ctx) - if err != nil { - return "" - } - text := "" - for i, h := range all { - if i > 0 { - text += ";" - } - text += h.Name + "," + h.StyleTag - } - return text -} - func bodyDescText(ctx context.Context, userId int64) string { bm, err := dao.BodyMeasurement.GetByUser(ctx, userId) if err != nil || bm == nil { @@ -317,10 +324,6 @@ func bodyDescText(ctx context.Context, userId int64) string { return fmt.Sprintf("身高 %dcm,体重 %dkg,肤色 %d 档", bm.Height, bm.Weight, bm.SkinTone) } -func weatherSummaryText(w *agent.WeatherResult) string { - return fmt.Sprintf("%s(%s),平均 %d℃,%d 天", w.CityCode, w.Season, w.AvgTemp, len(w.Days)) -} - func weekdayOf(date string) string { d, err := time.Parse("2006-01-02", date) if err != nil { @@ -332,22 +335,3 @@ func weekdayOf(date string) string { } return "workday" } - -func scoringThreshold(ctx context.Context) int { - threshold := consts.DefaultScoreThreshold - rules, err := dao.ScoringRule.ListEnabled(ctx) - if err != nil { - return threshold - } - for _, r := range rules { - if r.Dimension == "threshold" { - var v struct { - Pass int `json:"pass"` - } - if json.Unmarshal([]byte(r.RulesJson), &v) == nil && v.Pass > 0 { - return v.Pass - } - } - } - return threshold -} diff --git a/styleagent/service/outfit_plan_service.go b/styleagent/service/outfit_plan_service.go new file mode 100644 index 0000000..ff13045 --- /dev/null +++ b/styleagent/service/outfit_plan_service.go @@ -0,0 +1,95 @@ +package service + +import ( + "context" + "errors" + + "slogan-agent/styleagent/agent" + "slogan-agent/styleagent/consts" + "slogan-agent/styleagent/dao" + "slogan-agent/styleagent/model/dto" + "slogan-agent/styleagent/model/entity" + + "github.com/gogf/gf/v2/os/gctx" +) + +type outfitPlanService struct{} + +var OutfitPlanService = new(outfitPlanService) + +func (s *outfitPlanService) ListPlans(ctx context.Context, userId int64) ([]*entity.OutfitPlan, error) { + return dao.OutfitPlan.ListByUser(ctx, userId) +} + +func (s *outfitPlanService) GetPlanDetail(ctx context.Context, userId, planId int64) (*dto.OutfitPlanDetailRes, error) { + plan, err := dao.OutfitPlan.GetOne(ctx, planId, userId) + if err != nil || plan == nil { + return nil, errors.New("方案不存在") + } + res := &dto.OutfitPlanDetailRes{Plan: plan} + res.Items, err = PlanOutfitItemService.ListByPlan(ctx, planId) + if err != nil { + return nil, err + } + res.Images, err = dao.PlanEffectImage.ListByPlan(ctx, planId) + if err != nil { + return nil, err + } + if plan.HairstyleId > 0 { + res.Hairstyle, _ = dao.HairstyleAsset.GetOne(ctx, plan.HairstyleId) + } + return res, nil +} + +// SelectMain 选定主方案(同任务其他方案清零)+ 异步生成效果图 +func (s *outfitPlanService) SelectMain(ctx context.Context, userId, planId int64) error { + plan, err := dao.OutfitPlan.GetOne(ctx, planId, userId) + if err != nil || plan == nil { + return errors.New("方案不存在") + } + if err := dao.OutfitPlan.ClearMainFlag(ctx, plan.TaskId); err != nil { + return err + } + if err := dao.OutfitPlan.SetMainFlag(ctx, planId); err != nil { + return err + } + // 异步生成 3 视角效果图 + EffectImageService.GenerateForPlan(gctx.New(), planId, userId) + return nil +} + +func planSource(p agent.PlanCandidate) string { + for _, it := range p.Items { + if it.NewItem || it.ItemId == 0 { + return consts.PlanSourceRecommend + } + } + return consts.PlanSourceWardrobe +} + +func matchHairstyle(name string, all []*entity.HairstyleAsset) int64 { + if name == "" { + return 0 + } + for _, h := range all { + if h.Name == name { + return h.Id + } + } + return 0 +} + +func hairstyleListText(ctx context.Context) string { + all, err := dao.HairstyleAsset.ListAll(ctx) + if err != nil { + return "" + } + text := "" + for i, h := range all { + if i > 0 { + text += ";" + } + text += h.Name + "," + h.StyleTag + } + return text +} diff --git a/styleagent/service/effect_image_service.go b/styleagent/service/plan_effect_image_service.go similarity index 88% rename from styleagent/service/effect_image_service.go rename to styleagent/service/plan_effect_image_service.go index f89ef5c..7169ed0 100644 --- a/styleagent/service/effect_image_service.go +++ b/styleagent/service/plan_effect_image_service.go @@ -4,7 +4,6 @@ import ( "context" "crypto/md5" "encoding/hex" - "encoding/json" "fmt" "strings" @@ -35,7 +34,7 @@ func (s *effectImageService) run(ctx context.Context, planId, userId int64) { } // 每日限额:VIP 不限;普通用户 = 基础额度 + 广告激励额外次数 if !dao.UserMember.IsVip(ctx, userId) { - limit := dailyEffectLimit(ctx) + limit := ScoringRuleService.EffectLimit(ctx) if limit > 0 { used, _ := dao.PlanEffectImage.CountByUserToday(ctx, userId) extra, _ := dao.AdRewardLog.CountTodayByType(ctx, userId, consts.AdTypeEffectExtra) @@ -112,22 +111,3 @@ 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[:]) } - -func dailyEffectLimit(ctx context.Context) int { - limit := consts.DefaultDailyEffectLimit - rules, err := dao.ScoringRule.ListEnabled(ctx) - if err != nil { - return limit - } - for _, r := range rules { - if r.Dimension == "effect_limit" { - var v struct { - Daily int `json:"daily"` - } - if json.Unmarshal([]byte(r.RulesJson), &v) == nil && v.Daily > 0 { - return v.Daily - } - } - } - return limit -} diff --git a/styleagent/service/plan_outfit_item_service.go b/styleagent/service/plan_outfit_item_service.go new file mode 100644 index 0000000..254352a --- /dev/null +++ b/styleagent/service/plan_outfit_item_service.go @@ -0,0 +1,16 @@ +package service + +import ( + "context" + + "slogan-agent/styleagent/dao" + "slogan-agent/styleagent/model/entity" +) + +type planOutfitItemService struct{} + +var PlanOutfitItemService = new(planOutfitItemService) + +func (s *planOutfitItemService) ListByPlan(ctx context.Context, planId int64) ([]*entity.PlanOutfitItem, error) { + return dao.PlanOutfitItem.ListByPlan(ctx, planId) +} diff --git a/styleagent/service/plan_review_service.go b/styleagent/service/plan_review_service.go new file mode 100644 index 0000000..b5c9fab --- /dev/null +++ b/styleagent/service/plan_review_service.go @@ -0,0 +1,22 @@ +package service + +import ( + "context" + "errors" + + "slogan-agent/styleagent/dao" + "slogan-agent/styleagent/model/entity" +) + +type planReviewService struct{} + +var PlanReviewService = new(planReviewService) + +func (s *planReviewService) Review(ctx context.Context, userId, planId int64, action, note string) error { + plan, err := dao.OutfitPlan.GetOne(ctx, planId, userId) + if err != nil || plan == nil { + return errors.New("方案不存在") + } + _, err = dao.PlanReview.Insert(ctx, &entity.PlanReview{PlanId: planId, UserId: userId, Action: action, Note: note}) + return err +} diff --git a/styleagent/service/scoring_rule_service.go b/styleagent/service/scoring_rule_service.go new file mode 100644 index 0000000..040ffa4 --- /dev/null +++ b/styleagent/service/scoring_rule_service.go @@ -0,0 +1,53 @@ +package service + +import ( + "context" + "encoding/json" + + "slogan-agent/styleagent/consts" + "slogan-agent/styleagent/dao" +) + +type scoringRuleService struct{} + +var ScoringRuleService = new(scoringRuleService) + +// Threshold 方案及格分(scoring_rule.dimension=threshold) +func (s *scoringRuleService) Threshold(ctx context.Context) int { + threshold := consts.DefaultScoreThreshold + rules, err := dao.ScoringRule.ListEnabled(ctx) + if err != nil { + return threshold + } + for _, r := range rules { + if r.Dimension == "threshold" { + var v struct { + Pass int `json:"pass"` + } + if json.Unmarshal([]byte(r.RulesJson), &v) == nil && v.Pass > 0 { + return v.Pass + } + } + } + return threshold +} + +// EffectLimit 每日效果图基础额度(scoring_rule.dimension=effect_limit) +func (s *scoringRuleService) EffectLimit(ctx context.Context) int { + limit := consts.DefaultDailyEffectLimit + rules, err := dao.ScoringRule.ListEnabled(ctx) + if err != nil { + return limit + } + for _, r := range rules { + if r.Dimension == "effect_limit" { + var v struct { + Daily int `json:"daily"` + } + if json.Unmarshal([]byte(r.RulesJson), &v) == nil && v.Daily > 0 { + return v.Daily + } + } + } + return limit +} diff --git a/styleagent/service/wardrobe_service.go b/styleagent/service/wardrobe_item_service.go similarity index 100% rename from styleagent/service/wardrobe_service.go rename to styleagent/service/wardrobe_item_service.go diff --git a/styleagent/service/weather_service.go b/styleagent/service/weather_service.go deleted file mode 100644 index 423c784..0000000 --- a/styleagent/service/weather_service.go +++ /dev/null @@ -1,31 +0,0 @@ -package service - -import ( - "context" - "fmt" - "time" - - "slogan-agent/styleagent/agent" -) - -var weatherCache = agent.NewTTLCache(6 * time.Hour) - -// GetWeather 地点 + 日期范围 → 天气结果(高德地理编码 + 和风 7 天预报,缓存 6 小时) -func GetWeather(ctx context.Context, location, startDate, endDate string) (*agent.WeatherResult, error) { - cityCode, err := agent.GetCityCode(ctx, location) - if err != nil { - return nil, err - } - cacheKey := fmt.Sprintf("%s:%s:%s", cityCode, startDate, endDate) - if v, ok := weatherCache.Get(cacheKey); ok { - if result, ok := v.(*agent.WeatherResult); ok { - return result, nil - } - } - result, err := agent.GetDaily(ctx, cityCode, startDate, endDate) - if err != nil { - return nil, err - } - weatherCache.Set(cacheKey, result) - return result, nil -}