美团/京东/淘宝联盟适配器(选品/搜索/转链,未配置 key 优雅降级), CPS 四表五层骨架,方案驱动推荐零新增 LLM(发型/买同款/到店试穿/场合), 定时同步 + 点击日志 + 最近优惠;outfit_plan 容错迁移 occasion 列; Dockerfile 补 mesa/nodejs 运行时。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
120 lines
4.1 KiB
Go
120 lines
4.1 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"slogan-agent/styleagent/consts"
|
|
"slogan-agent/styleagent/dao"
|
|
"slogan-agent/styleagent/model/entity"
|
|
)
|
|
|
|
// sceneCategoryMapService 方案驱动推荐(零新增 LLM:场景映射表 + 联盟选品/搜索)
|
|
type sceneCategoryMapService struct {
|
|
productSvc *cpsProductService // 测试注入;nil 时用全局 CpsProductService
|
|
}
|
|
|
|
var SceneCategoryMapService = new(sceneCategoryMapService)
|
|
|
|
// Recommend 按场景给方案推荐联盟商品;查不到返回空列表(客户端隐藏入口)
|
|
func (s *sceneCategoryMapService) Recommend(ctx context.Context, plan *entity.OutfitPlan, scene string) ([]*entity.CpsProduct, error) {
|
|
svc := s.productSvc
|
|
if svc == nil {
|
|
svc = CpsProductService
|
|
}
|
|
switch scene {
|
|
case consts.CpsSceneHaircut:
|
|
return s.recommendHaircut(ctx, svc, plan)
|
|
case consts.CpsSceneItemBuy:
|
|
return s.recommendItemBuy(ctx, svc, plan)
|
|
case consts.CpsSceneItemUpgrade:
|
|
return s.recommendItemUpgrade(ctx, svc, plan)
|
|
case consts.CpsSceneOccasion:
|
|
return s.recommendOccasion(ctx, svc, plan)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// recommendHaircut 发型 → 映射表丽人(美团到店),城市过滤
|
|
func (s *sceneCategoryMapService) recommendHaircut(ctx context.Context, svc *cpsProductService, plan *entity.OutfitPlan) ([]*entity.CpsProduct, error) {
|
|
if plan.HairstyleId <= 0 {
|
|
return nil, nil
|
|
}
|
|
return s.byScene(ctx, svc, consts.CpsSceneHaircut, "", plan.Location)
|
|
}
|
|
|
|
// recommendOccasion 场合 → 映射表(通勤/约会/旅行/运动),城市过滤
|
|
func (s *sceneCategoryMapService) recommendOccasion(ctx context.Context, svc *cpsProductService, plan *entity.OutfitPlan) ([]*entity.CpsProduct, error) {
|
|
return s.byScene(ctx, svc, consts.CpsSceneOccasion, plan.Occasion, plan.Location)
|
|
}
|
|
|
|
// byScene 场景映射表(priority 最高者)→ 分页商品
|
|
func (s *sceneCategoryMapService) byScene(ctx context.Context, svc *cpsProductService, sceneType, occasion, city string) ([]*entity.CpsProduct, error) {
|
|
maps, err := dao.SceneCategoryMap.QueryByScene(ctx, sceneType, occasion)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(maps) == 0 {
|
|
return nil, nil
|
|
}
|
|
list, _, err := svc.ListByCategory(ctx, maps[0].Source, maps[0].CategoryCode, city, 1, 10)
|
|
return list, err
|
|
}
|
|
|
|
// recommendItemBuy 买同款:推荐条目名作关键词 → 电商搜索
|
|
func (s *sceneCategoryMapService) recommendItemBuy(ctx context.Context, svc *cpsProductService, plan *entity.OutfitPlan) ([]*entity.CpsProduct, error) {
|
|
items, err := dao.PlanOutfitItem.ListByPlan(ctx, plan.Id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyword := ""
|
|
for _, it := range items {
|
|
if it.Source == consts.PlanSourceRecommend && it.Name != "" {
|
|
keyword = it.Name
|
|
break
|
|
}
|
|
}
|
|
if keyword == "" {
|
|
return nil, nil
|
|
}
|
|
return s.search(ctx, svc, keyword, "")
|
|
}
|
|
|
|
// recommendItemUpgrade 到店试穿:首条条目名 → 服装类目(美团)
|
|
func (s *sceneCategoryMapService) recommendItemUpgrade(ctx context.Context, svc *cpsProductService, plan *entity.OutfitPlan) ([]*entity.CpsProduct, error) {
|
|
items, err := dao.PlanOutfitItem.ListByPlan(ctx, plan.Id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(items) == 0 {
|
|
return nil, nil
|
|
}
|
|
return s.search(ctx, svc, items[0].Name, "clothing")
|
|
}
|
|
|
|
// WardrobeUpgrade 衣橱升级款:物品品类作关键词 → 服装类目搜索
|
|
func (s *sceneCategoryMapService) WardrobeUpgrade(ctx context.Context, userId, itemId int64) ([]*entity.CpsProduct, error) {
|
|
item, err := dao.WardrobeItem.GetOne(ctx, itemId, userId)
|
|
if err != nil || item == nil {
|
|
return nil, errors.New("衣橱物品不存在")
|
|
}
|
|
svc := s.productSvc
|
|
if svc == nil {
|
|
svc = CpsProductService
|
|
}
|
|
return s.search(ctx, svc, item.Category, "clothing")
|
|
}
|
|
|
|
// search 联盟实时搜索,失败降级为空列表而非错误
|
|
func (s *sceneCategoryMapService) search(ctx context.Context, svc *cpsProductService, keyword, catCode string) ([]*entity.CpsProduct, error) {
|
|
raw, err := svc.Search(ctx, keyword, catCode, 1)
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
out := make([]*entity.CpsProduct, 0, len(raw))
|
|
for i := range raw {
|
|
out = append(out, toCpsProductEntity(&raw[i]))
|
|
}
|
|
return out, nil
|
|
}
|