diff --git a/scripts/routes.sh b/scripts/routes.sh new file mode 100644 index 0000000..35f55c5 --- /dev/null +++ b/scripts/routes.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# 路由快照:输出 /api.json 的全部路径(排序去重) +# 用法:bash scripts/routes.sh > /tmp/routes-before.txt +set -e +BASE="${BASE:-http://localhost:3007}" +curl -s "$BASE/api.json" | python3 -c " +import json, sys +d = json.load(sys.stdin) +for p in sorted(d.get('paths', {}).keys()): + print(p) +" diff --git a/scripts/smoke.sh b/scripts/smoke.sh new file mode 100644 index 0000000..ad86985 --- /dev/null +++ b/scripts/smoke.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# 全路径冒烟:register/login → token → 依次打全部接口,断言 HTTP 200 + code 符合预期 +# 用法:bash scripts/smoke.sh +# 约定:check_code 第三参 = 允许的降级 code 列表(逗号分隔,默认只许 0) +set -e +BASE="${BASE:-http://localhost:3007}" +FAIL=0 + +say() { echo "[smoke] $*"; } +fail() { echo "[smoke] FAIL: $*"; FAIL=1; } + +check_code() { + local name="$1" body="$2" allow="$3" + local code + code=$(echo "$body" | python3 -c "import json,sys; print(json.load(sys.stdin).get('code','?'))" 2>/dev/null || echo "?") + if [ "$code" = "0" ] || echo ",$allow," | grep -q ",$code,"; then + say "OK: $name" + else + fail "$name: unexpected code=$code (allow: $allow) body=$(echo "$body" | head -c 200)" + fi +} + +# 1. 注册 + 登录拿 token +USER="smoke_$(date +%s)" +REG=$(curl -s -X POST "$BASE/user/register" -H 'Content-Type: application/json' -d "{\"account\":\"$USER\",\"password\":\"smoketest123\"}") +say "register: $(echo "$REG" | head -c 120)" +LOGIN=$(curl -s -X POST "$BASE/user/login" -H 'Content-Type: application/json' -d "{\"account\":\"$USER\",\"password\":\"smoketest123\"}") +TOKEN=$(echo "$LOGIN" | python3 -c "import json,sys; print(json.load(sys.stdin).get('data',{}).get('token',''))" 2>/dev/null) +if [ -z "$TOKEN" ]; then + fail "login: no token in $(echo "$LOGIN" | head -c 200)" + exit 1 +fi +say "login OK, token len=${#TOKEN}" +AUTH="Authorization: Bearer $TOKEN" + +# 2. GET 接口(第三参允许的 code:50=未开通/降级/无数据) +for item in \ + "GET /user/profile 0" \ + "GET /user-photo/list 0" \ + "GET /wardrobe/list 0" \ + "GET /body-measurement/get 0" \ + "GET /avatar/get 0" \ + "GET /hairstyle/list 0" \ + "GET /outfit/task/status?task_id=0 50" \ + "GET /outfit/plan/list 0" \ + "GET /partner-store/list 0" \ + "GET /member/plan/list 0" \ + "GET /member/status 0" ; do + set -- $item + METHOD="$1"; PATH_="$2"; ALLOW="${3:-0}" + RESP=$(curl -s -X "$METHOD" "$BASE$PATH_" -H "$AUTH") + check_code "$PATH_" "$RESP" "$ALLOW" +done + +# 3. POST 接口 +post_check() { + local name="$1" json="$2" allow="${3:-0}" + local resp + resp=$(curl -s -X POST "$BASE$name" -H "$AUTH" -H 'Content-Type: application/json' -d "$json") + check_code "$name" "$resp" "$allow" +} +post_check "/body-measurement/save" '{"height_cm":175,"weight_kg":65}' +post_check "/outfit/plan/review" '{"plan_id":0,"action":"fav"}' "50" +post_check "/member/order/create" '{"plan_id":1}' "50" +post_check "/ad/reward/claim" '{"ad_type":"effect_extra"}' "50" +post_check "/outfit/generate" '{"start_date":"2026-08-01","end_date":"2026-08-07","location":"上海"}' "50" +post_check "/user/change-password" '{"old_password":"smoketest123","new_password":"smoketest456"}' + +# 4. 裸回调(无鉴权;no_order 预期返回 fail) +NOTIFY=$(curl -s -X POST "$BASE/member/order/notify" -H 'Content-Type: application/x-www-form-urlencoded' -d 'out_trade_no=nonexist&trade_no=x&amount=0&status=paid') +say "notify(no_order)=$NOTIFY" + +# 5. workspace 静态文件 +WS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/workspace/nonexist.png" -H "$AUTH") +say "workspace/404: $WS" + +if [ "$FAIL" = "0" ]; then + say "ALL SMOKE PASS" +else + say "SMOKE HAS FAILURES" + exit 1 +fi diff --git a/styleagent/avatar/glb_packer.go b/styleagent/agent/avatar_glb_packer.go similarity index 95% rename from styleagent/avatar/glb_packer.go rename to styleagent/agent/avatar_glb_packer.go index 0d382c9..3ec6a87 100644 --- a/styleagent/avatar/glb_packer.go +++ b/styleagent/agent/avatar_glb_packer.go @@ -1,4 +1,4 @@ -package avatar +package agent import "fmt" diff --git a/styleagent/avatar/template_matcher.go b/styleagent/agent/avatar_matcher.go similarity index 98% rename from styleagent/avatar/template_matcher.go rename to styleagent/agent/avatar_matcher.go index 9cfc64b..95bb9eb 100644 --- a/styleagent/avatar/template_matcher.go +++ b/styleagent/agent/avatar_matcher.go @@ -1,4 +1,4 @@ -package avatar +package agent // FaceFeature 从照片+用户填写提取的化身特征(v1:肤色/身高/体重来自身形参数,照片贴图后续增强) type FaceFeature struct { diff --git a/styleagent/agent/imagegen_cache.go b/styleagent/agent/imagegen_cache.go new file mode 100644 index 0000000..0b64878 --- /dev/null +++ b/styleagent/agent/imagegen_cache.go @@ -0,0 +1,21 @@ +package agent + +import "time" + +// 效果图 URL 缓存(key: 方案内容 hash:角度,24h TTL,复用泛型 TTL 缓存) +var effectCache = NewTTLCache(24 * time.Hour) + +// CacheGet 读取缓存 URL +func CacheGet(key string) (string, bool) { + v, ok := effectCache.Get(key) + if !ok { + return "", false + } + s, _ := v.(string) + return s, s != "" +} + +// CacheSet 写入缓存 URL +func CacheSet(key, url string) { + effectCache.Set(key, url) +} diff --git a/styleagent/imagegen/client.go b/styleagent/agent/imagegen_client.go similarity index 98% rename from styleagent/imagegen/client.go rename to styleagent/agent/imagegen_client.go index 657785c..aeee770 100644 --- a/styleagent/imagegen/client.go +++ b/styleagent/agent/imagegen_client.go @@ -1,4 +1,4 @@ -package imagegen +package agent import ( "context" diff --git a/styleagent/imagegen/mock_client.go b/styleagent/agent/imagegen_mock_client.go similarity index 94% rename from styleagent/imagegen/mock_client.go rename to styleagent/agent/imagegen_mock_client.go index 91c5fe4..3dc9425 100644 --- a/styleagent/imagegen/mock_client.go +++ b/styleagent/agent/imagegen_mock_client.go @@ -1,4 +1,4 @@ -package imagegen +package agent import ( "context" diff --git a/styleagent/imagegen/wanx_client.go b/styleagent/agent/imagegen_wanx_client.go similarity index 92% rename from styleagent/imagegen/wanx_client.go rename to styleagent/agent/imagegen_wanx_client.go index ac75a86..2e2b7fb 100644 --- a/styleagent/imagegen/wanx_client.go +++ b/styleagent/agent/imagegen_wanx_client.go @@ -1,4 +1,4 @@ -package imagegen +package agent import ( "bytes" @@ -31,14 +31,14 @@ type wanxInput struct { type wanxResp struct { Output struct { - TaskID string `json:"task_id"` + TaskID string `json:"task_id"` TaskStatus string `json:"task_status"` - Results []struct { + Results []struct { URL string `json:"url"` } `json:"results"` } `json:"output"` - Code string `json:"code"` - Message string `json:"message"` + Code string `json:"code"` + Message string `json:"message"` } type wanxTaskResp struct { @@ -55,8 +55,8 @@ type wanxTaskResp struct { // Generate 提交任务并轮询直到完成,失败返回错误(由上层降级 mock) func (c *wanxClient) Generate(ctx context.Context, req *GenerateReq) (string, error) { body, err := json.Marshal(wanxSubmitReq{ - Model: c.model, - Input: wanxInput{Prompt: buildPrompt(req.Prompt, "", "", req.Angle), BaseImageURL: req.BaseImageURL}, + Model: c.model, + Input: wanxInput{Prompt: buildPrompt(req.Prompt, "", "", req.Angle), BaseImageURL: req.BaseImageURL}, Parameters: map[string]any{"n": 1, "size": "768*1024", "seed": req.Seed}, }) if err != nil { diff --git a/styleagent/agent/outfit_agent.go b/styleagent/agent/outfit_agent.go index af25c5c..5268275 100644 --- a/styleagent/agent/outfit_agent.go +++ b/styleagent/agent/outfit_agent.go @@ -10,8 +10,8 @@ import ( // CandidateData 预筛候选服装(供 LLM 选择组合) type CandidateData struct { - SetId int64 `json:"set_id"` // 所属预筛组合编号 - ItemId int64 `json:"item_id"` // 衣橱条目 id + SetId int64 `json:"set_id"` // 所属预筛组合编号 + ItemId int64 `json:"item_id"` // 衣橱条目 id Category string `json:"category"` Name string `json:"name"` Color string `json:"color"` diff --git a/styleagent/agent/output.go b/styleagent/agent/output.go index 45cb5df..254a2ca 100644 --- a/styleagent/agent/output.go +++ b/styleagent/agent/output.go @@ -21,11 +21,11 @@ type PlanCandidate struct { // PlanItemOut 方案内一件单品 type PlanItemOut struct { - Slot string `json:"slot"` // 上衣/下装/鞋/配饰 - ItemId int64 `json:"item_id,omitempty"` // 衣橱条目(wardrobe 来源) - Name string `json:"name"` // 单品名 - Desc string `json:"desc"` // 搭配说明 - NewItem bool `json:"new_item"` // 是否为推荐新服装 + Slot string `json:"slot"` // 上衣/下装/鞋/配饰 + ItemId int64 `json:"item_id,omitempty"` // 衣橱条目(wardrobe 来源) + Name string `json:"name"` // 单品名 + Desc string `json:"desc"` // 搭配说明 + NewItem bool `json:"new_item"` // 是否为推荐新服装 } // ParsePlanOutput 解析并校验 LLM 输出(去除 markdown 代码围栏后 json.Unmarshal) diff --git a/styleagent/scoring/color_rule.go b/styleagent/agent/scoring_color_rule.go similarity index 99% rename from styleagent/scoring/color_rule.go rename to styleagent/agent/scoring_color_rule.go index acdfb07..5db5a1a 100644 --- a/styleagent/scoring/color_rule.go +++ b/styleagent/agent/scoring_color_rule.go @@ -1,4 +1,4 @@ -package scoring +package agent import ( "strconv" diff --git a/styleagent/scoring/completeness_rule.go b/styleagent/agent/scoring_completeness_rule.go similarity index 98% rename from styleagent/scoring/completeness_rule.go rename to styleagent/agent/scoring_completeness_rule.go index 8a10ee2..a50eac3 100644 --- a/styleagent/scoring/completeness_rule.go +++ b/styleagent/agent/scoring_completeness_rule.go @@ -1,4 +1,4 @@ -package scoring +package agent // completenessScore 层次完整度(20 分制):上衣+5 下装+5 鞋+5 配饰+5 func completenessScore(o CandidateOutfit) int { diff --git a/styleagent/scoring/engine.go b/styleagent/agent/scoring_engine.go similarity index 95% rename from styleagent/scoring/engine.go rename to styleagent/agent/scoring_engine.go index 4dc4dc5..a27ac97 100644 --- a/styleagent/scoring/engine.go +++ b/styleagent/agent/scoring_engine.go @@ -1,4 +1,4 @@ -package scoring +package agent // Score 总分(100 分制) func Score(c *CandidateOutfit, ctx *ScoreContext) int { diff --git a/styleagent/scoring/occasion_rule.go b/styleagent/agent/scoring_occasion_rule.go similarity index 97% rename from styleagent/scoring/occasion_rule.go rename to styleagent/agent/scoring_occasion_rule.go index 382521a..eaff2cb 100644 --- a/styleagent/scoring/occasion_rule.go +++ b/styleagent/agent/scoring_occasion_rule.go @@ -1,4 +1,4 @@ -package scoring +package agent import "strings" diff --git a/styleagent/scoring/rules.go b/styleagent/agent/scoring_rules.go similarity index 97% rename from styleagent/scoring/rules.go rename to styleagent/agent/scoring_rules.go index 083dbf6..1763179 100644 --- a/styleagent/scoring/rules.go +++ b/styleagent/agent/scoring_rules.go @@ -1,4 +1,4 @@ -package scoring +package agent // WardrobeItem 评分用服装条目(从衣橱 entity 转换) type WardrobeItem struct { diff --git a/styleagent/scoring/weather_rule.go b/styleagent/agent/scoring_weather_rule.go similarity index 98% rename from styleagent/scoring/weather_rule.go rename to styleagent/agent/scoring_weather_rule.go index 4261b46..3bfd500 100644 --- a/styleagent/scoring/weather_rule.go +++ b/styleagent/agent/scoring_weather_rule.go @@ -1,4 +1,4 @@ -package scoring +package agent // weatherScore 天气适宜度(25 分制) // 温度匹配每件服装季节 +5;<10℃ 无外套 -10;>30℃ 有外套 -8 diff --git a/styleagent/weather/cache.go b/styleagent/agent/weather_cache.go similarity index 67% rename from styleagent/weather/cache.go rename to styleagent/agent/weather_cache.go index 3e38f8c..e850c51 100644 --- a/styleagent/weather/cache.go +++ b/styleagent/agent/weather_cache.go @@ -1,12 +1,13 @@ -package weather +package agent import ( "sync" "time" ) +// 通用 TTL 缓存:天气结果(*WeatherResult)、CPS 转链(string)等均可复用 type cacheEntry struct { - data *WeatherResult + data any expiresAt time.Time } @@ -16,11 +17,11 @@ type Cache struct { items map[string]cacheEntry } -func NewCache(ttl time.Duration) *Cache { +func NewTTLCache(ttl time.Duration) *Cache { return &Cache{ttl: ttl, items: make(map[string]cacheEntry)} } -func (c *Cache) Get(key string) (*WeatherResult, bool) { +func (c *Cache) Get(key string) (any, bool) { c.mu.Lock() defer c.mu.Unlock() e, ok := c.items[key] @@ -34,7 +35,7 @@ func (c *Cache) Get(key string) (*WeatherResult, bool) { return e.data, true } -func (c *Cache) Set(key string, data *WeatherResult) { +func (c *Cache) Set(key string, data any) { c.mu.Lock() defer c.mu.Unlock() c.items[key] = cacheEntry{data: data, expiresAt: time.Now().Add(c.ttl)} diff --git a/styleagent/weather/geo.go b/styleagent/agent/weather_geo.go similarity index 96% rename from styleagent/weather/geo.go rename to styleagent/agent/weather_geo.go index 8ee7d30..0528f21 100644 --- a/styleagent/weather/geo.go +++ b/styleagent/agent/weather_geo.go @@ -1,4 +1,4 @@ -package weather +package agent import ( "context" @@ -13,7 +13,7 @@ import ( ) type amapResp struct { - Status string `json:"status"` + Status string `json:"status"` Geocodes []struct { Adcode string `json:"adcode"` } `json:"geocodes"` diff --git a/styleagent/weather/qweather.go b/styleagent/agent/weather_qweather.go similarity index 97% rename from styleagent/weather/qweather.go rename to styleagent/agent/weather_qweather.go index 20e9ff1..77d80b6 100644 --- a/styleagent/weather/qweather.go +++ b/styleagent/agent/weather_qweather.go @@ -1,4 +1,4 @@ -package weather +package agent import ( "context" @@ -20,7 +20,7 @@ type DayWeather struct { } type WeatherResult struct { - CityCode string `json:"city_code"` + CityCode string `json:"city_code"` Days []DayWeather `json:"days"` // AvgTemp 日期范围平均温度(评分用) AvgTemp int `json:"avg_temp"` diff --git a/styleagent/dao/hairstyle_asset_dao.go b/styleagent/dao/hairstyle_asset_dao.go index 71084b2..f4f6561 100644 --- a/styleagent/dao/hairstyle_asset_dao.go +++ b/styleagent/dao/hairstyle_asset_dao.go @@ -37,7 +37,10 @@ func seedHairstyles(ctx context.Context) { return } _ = cnt - items := []struct{ name, tag, face string; sort int }{ + items := []struct { + name, tag, face string + sort int + }{ {"清爽短发", "清爽", "all", 1}, {"中分微卷", "温婉", "all", 2}, {"披肩长发", "优雅", "all", 3}, diff --git a/styleagent/imagegen/cache.go b/styleagent/imagegen/cache.go deleted file mode 100644 index ee5b6a8..0000000 --- a/styleagent/imagegen/cache.go +++ /dev/null @@ -1,49 +0,0 @@ -package imagegen - -import ( - "sync" - "time" -) - -// cache 效果图 URL 缓存(key: 方案内容 hash:角度,24h TTL) -type cache struct { - mu sync.Mutex - items map[string]cacheEntry -} - -type cacheEntry struct { - url string - expiresAt time.Time -} - -var effectCache = &cache{items: make(map[string]cacheEntry)} - -// CacheGet 读取缓存 URL -func CacheGet(key string) (string, bool) { - return cacheGet(key) -} - -// CacheSet 写入缓存 URL -func CacheSet(key, url string) { - cacheSet(key, url) -} - -func cacheGet(key string) (string, bool) { - effectCache.mu.Lock() - defer effectCache.mu.Unlock() - e, ok := effectCache.items[key] - if !ok { - return "", false - } - if time.Now().After(e.expiresAt) { - delete(effectCache.items, key) - return "", false - } - return e.url, true -} - -func cacheSet(key, url string) { - effectCache.mu.Lock() - defer effectCache.mu.Unlock() - effectCache.items[key] = cacheEntry{url: url, expiresAt: time.Now().Add(24 * time.Hour)} -} diff --git a/styleagent/model/entity/avatar_model.go b/styleagent/model/entity/avatar_model.go index 5ba46c5..8e7ea82 100644 --- a/styleagent/model/entity/avatar_model.go +++ b/styleagent/model/entity/avatar_model.go @@ -3,16 +3,16 @@ package entity import "github.com/gogf/gf/v2/os/gtime" type AvatarModel struct { - Id int64 `orm:"id" json:"id"` - UserId int64 `orm:"user_id" json:"user_id"` - FaceTemplateId int `orm:"face_template_id" json:"face_template_id"` - BodyTemplateId int `orm:"body_template_id" json:"body_template_id"` - SkinToneIndex int `orm:"skin_tone_index" json:"skin_tone_index"` - FaceTextureUrl string `orm:"face_texture_url" json:"face_texture_url"` - GlbUrl string `orm:"glb_url" json:"glb_url"` - BuildStatus string `orm:"build_status" json:"build_status"` - Error string `orm:"error" json:"error"` - ParamsSnapshot string `orm:"params_snapshot" json:"params_snapshot"` - CreatedAt *gtime.Time `orm:"created_at" json:"created_at"` - UpdatedAt *gtime.Time `orm:"updated_at" json:"updated_at"` + Id int64 `orm:"id" json:"id"` + UserId int64 `orm:"user_id" json:"user_id"` + FaceTemplateId int `orm:"face_template_id" json:"face_template_id"` + BodyTemplateId int `orm:"body_template_id" json:"body_template_id"` + SkinToneIndex int `orm:"skin_tone_index" json:"skin_tone_index"` + FaceTextureUrl string `orm:"face_texture_url" json:"face_texture_url"` + GlbUrl string `orm:"glb_url" json:"glb_url"` + BuildStatus string `orm:"build_status" json:"build_status"` + Error string `orm:"error" json:"error"` + ParamsSnapshot string `orm:"params_snapshot" json:"params_snapshot"` + CreatedAt *gtime.Time `orm:"created_at" json:"created_at"` + UpdatedAt *gtime.Time `orm:"updated_at" json:"updated_at"` } diff --git a/styleagent/model/entity/hairstyle_asset.go b/styleagent/model/entity/hairstyle_asset.go index cb95bab..6f67a27 100644 --- a/styleagent/model/entity/hairstyle_asset.go +++ b/styleagent/model/entity/hairstyle_asset.go @@ -3,12 +3,12 @@ package entity import "github.com/gogf/gf/v2/os/gtime" type HairstyleAsset struct { - Id int64 `orm:"id" json:"id"` - Name string `orm:"name" json:"name"` - StyleTag string `orm:"style_tag" json:"style_tag"` - GlbUrl string `orm:"glb_url" json:"glb_url"` - ThumbUrl string `orm:"thumb_url" json:"thumb_url"` - ApplicableFace string `orm:"applicable_face" json:"applicable_face"` - Sort int `orm:"sort" json:"sort"` - CreatedAt *gtime.Time `orm:"created_at" json:"created_at"` + Id int64 `orm:"id" json:"id"` + Name string `orm:"name" json:"name"` + StyleTag string `orm:"style_tag" json:"style_tag"` + GlbUrl string `orm:"glb_url" json:"glb_url"` + ThumbUrl string `orm:"thumb_url" json:"thumb_url"` + ApplicableFace string `orm:"applicable_face" json:"applicable_face"` + Sort int `orm:"sort" json:"sort"` + CreatedAt *gtime.Time `orm:"created_at" json:"created_at"` } diff --git a/styleagent/model/entity/plan_outfit_item.go b/styleagent/model/entity/plan_outfit_item.go index 42864b4..879dba6 100644 --- a/styleagent/model/entity/plan_outfit_item.go +++ b/styleagent/model/entity/plan_outfit_item.go @@ -3,13 +3,13 @@ package entity import "github.com/gogf/gf/v2/os/gtime" type PlanOutfitItem struct { - Id int64 `orm:"id" json:"id"` - PlanId int64 `orm:"plan_id" json:"plan_id"` - Slot string `orm:"slot" json:"slot"` - Source string `orm:"source" json:"source"` - WardrobeItemId int64 `orm:"wardrobe_item_id" json:"wardrobe_item_id"` - ProductName string `orm:"product_name" json:"product_name"` - Name string `orm:"name" json:"name"` - Desc string `orm:"desc" json:"desc"` - CreatedAt *gtime.Time `orm:"created_at" json:"created_at"` + Id int64 `orm:"id" json:"id"` + PlanId int64 `orm:"plan_id" json:"plan_id"` + Slot string `orm:"slot" json:"slot"` + Source string `orm:"source" json:"source"` + WardrobeItemId int64 `orm:"wardrobe_item_id" json:"wardrobe_item_id"` + ProductName string `orm:"product_name" json:"product_name"` + Name string `orm:"name" json:"name"` + Desc string `orm:"desc" json:"desc"` + CreatedAt *gtime.Time `orm:"created_at" json:"created_at"` } diff --git a/styleagent/payment/gateway.go b/styleagent/payment/gateway.go index 422804d..9aa04d9 100644 --- a/styleagent/payment/gateway.go +++ b/styleagent/payment/gateway.go @@ -67,7 +67,7 @@ func CreateOrder(ctx context.Context, orderNo string, amountFen int) (payURL str Url string `json:"url"` } // 注意:Post 的最后一个参数不会自动解析响应体,需手动读取后反序列化 - respRaw, err := g.Client().SetTimeout(10 * time.Second).Post(context.Background(), cfg.ApiBase+"/payment/do.html", params) + respRaw, err := g.Client().SetTimeout(10*time.Second).Post(context.Background(), cfg.ApiBase+"/payment/do.html", params) if err != nil { return "", fmt.Errorf("虎皮棋下单失败: %w", err) } diff --git a/styleagent/service/avatar_service.go b/styleagent/service/avatar_service.go index dcfda87..5af4e15 100644 --- a/styleagent/service/avatar_service.go +++ b/styleagent/service/avatar_service.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" - "slogan-agent/styleagent/avatar" + "slogan-agent/styleagent/agent" "slogan-agent/styleagent/consts" "slogan-agent/styleagent/dao" "slogan-agent/styleagent/model/entity" @@ -41,18 +41,18 @@ func (s *avatarService) Build(ctx context.Context, userId int64) (*entity.Avatar if err != nil { return nil, err } - feature := &avatar.FaceFeature{SkinTone: 3, HeightCm: 170, WeightKg: 60} + feature := &agent.FaceFeature{SkinTone: 3, HeightCm: 170, WeightKg: 60} if bm != nil { - feature = &avatar.FaceFeature{SkinTone: bm.SkinTone, HeightCm: bm.Height, WeightKg: bm.Weight} + feature = &agent.FaceFeature{SkinTone: bm.SkinTone, HeightCm: bm.Height, WeightKg: bm.Weight} } - faceId, bodyId, skinIdx := avatar.MatchTemplates(feature) + faceId, bodyId, skinIdx := agent.MatchTemplates(feature) // 已有化身则重建(更新模板索引),否则新建 existing, _ := dao.AvatarModel.GetByUser(ctx, userId) if existing != nil { err := dao.AvatarModel.Update(ctx, existing.Id, map[string]any{ "face_template_id": faceId, "body_template_id": bodyId, - "skin_tone_index": skinIdx, "glb_url": avatar.PackGlbUrl(faceId, bodyId, skinIdx), + "skin_tone_index": skinIdx, "glb_url": agent.PackGlbUrl(faceId, bodyId, skinIdx), "build_status": consts.AvatarBuildDone, "error": "", "params_snapshot": mustJSON(map[string]any{ "height_cm": feature.HeightCm, "weight_kg": feature.WeightKg, "skin_tone": skinIdx, @@ -65,7 +65,7 @@ func (s *avatarService) Build(ctx context.Context, userId int64) (*entity.Avatar existing.FaceTemplateId = faceId existing.BodyTemplateId = bodyId existing.SkinToneIndex = skinIdx - existing.GlbUrl = avatar.PackGlbUrl(faceId, bodyId, skinIdx) + existing.GlbUrl = agent.PackGlbUrl(faceId, bodyId, skinIdx) existing.BuildStatus = consts.AvatarBuildDone return existing, nil } @@ -75,7 +75,7 @@ func (s *avatarService) Build(ctx context.Context, userId int64) (*entity.Avatar FaceTemplateId: faceId, BodyTemplateId: bodyId, SkinToneIndex: skinIdx, - GlbUrl: avatar.PackGlbUrl(faceId, bodyId, skinIdx), + GlbUrl: agent.PackGlbUrl(faceId, bodyId, skinIdx), BuildStatus: consts.AvatarBuildDone, ParamsSnapshot: mustJSON(map[string]any{ "height_cm": feature.HeightCm, "weight_kg": feature.WeightKg, "skin_tone": skinIdx, diff --git a/styleagent/service/effect_image_service.go b/styleagent/service/effect_image_service.go index 61bb398..f89ef5c 100644 --- a/styleagent/service/effect_image_service.go +++ b/styleagent/service/effect_image_service.go @@ -8,9 +8,9 @@ import ( "fmt" "strings" + "slogan-agent/styleagent/agent" "slogan-agent/styleagent/consts" "slogan-agent/styleagent/dao" - "slogan-agent/styleagent/imagegen" "slogan-agent/styleagent/model/entity" "github.com/gogf/gf/v2/frame/g" @@ -63,10 +63,10 @@ func (s *effectImageService) run(ctx context.Context, planId, userId int64) { } } - client := imagegen.NewClient(g.Cfg().MustGet(ctx, "imagegen.supplier", "mock").String()) + client := agent.NewClient(g.Cfg().MustGet(ctx, "agent.supplier", "mock").String()) for i, angle := range effectAngles { cacheKey := effectCacheKey(plan, angle) - if url, ok := imagegen.CacheGet(cacheKey); ok { + if url, ok := agent.CacheGet(cacheKey); ok { _, _ = dao.PlanEffectImage.Insert(ctx, &entity.PlanEffectImage{ PlanId: planId, Angle: angle, Url: url, Status: consts.EffectStatusDone, PromptSnapshot: planDesc, @@ -80,7 +80,7 @@ func (s *effectImageService) run(ctx context.Context, planId, userId int64) { if err != nil { continue } - url, err := client.Generate(ctx, &imagegen.GenerateReq{ + url, err := client.Generate(ctx, &agent.GenerateReq{ BaseImageURL: baseImageURL, Prompt: planDesc, Angle: angle, Seed: plan.Id*100 + int64(i), }) if err != nil { @@ -88,7 +88,7 @@ func (s *effectImageService) run(ctx context.Context, planId, userId int64) { _ = dao.PlanEffectImage.UpdateStatus(ctx, recId, consts.EffectStatusFailed, "") continue } - imagegen.CacheSet(cacheKey, url) + agent.CacheSet(cacheKey, url) _ = dao.PlanEffectImage.UpdateStatus(ctx, recId, consts.EffectStatusDone, url) } g.Log().Infof(ctx, "方案 %d 效果图生成完成", planId) diff --git a/styleagent/service/outfit_combiner.go b/styleagent/service/outfit_combiner.go index 1638636..2e42be5 100644 --- a/styleagent/service/outfit_combiner.go +++ b/styleagent/service/outfit_combiner.go @@ -1,8 +1,8 @@ package service import ( + "slogan-agent/styleagent/agent" "slogan-agent/styleagent/model/entity" - "slogan-agent/styleagent/scoring" ) // candidateSet 一套预筛组合 @@ -55,10 +55,10 @@ func isOuterwear(it *entity.WardrobeItem) bool { } // toScoringOutfit 转评分用候选 -func toScoringOutfit(set candidateSet) scoring.CandidateOutfit { - o := scoring.CandidateOutfit{HasOuterwear: set.HasOuterwear} +func toScoringOutfit(set candidateSet) agent.CandidateOutfit { + o := agent.CandidateOutfit{HasOuterwear: set.HasOuterwear} for _, it := range set.Items { - o.Items = append(o.Items, scoring.WardrobeItem{ + o.Items = append(o.Items, agent.WardrobeItem{ Category: it.Category, Season: it.Season, ColorInfo: it.ColorInfo, diff --git a/styleagent/service/outfit_service.go b/styleagent/service/outfit_service.go index dfd632a..abbfea5 100644 --- a/styleagent/service/outfit_service.go +++ b/styleagent/service/outfit_service.go @@ -12,8 +12,6 @@ import ( "slogan-agent/styleagent/dao" "slogan-agent/styleagent/model/dto" "slogan-agent/styleagent/model/entity" - "slogan-agent/styleagent/scoring" - "slogan-agent/styleagent/weather" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gctx" @@ -132,7 +130,7 @@ func runGenerateTask(ctx context.Context, taskId, userId int64) { setTask(consts.TaskStatusScoring, "") threshold := scoringThreshold(ctx) plans := out.Plans - ctxScore := scoring.ScoreContext{ + ctxScore := agent.ScoreContext{ TempAvg: weatherResult.AvgTemp, Season: weatherResult.Season, Occasion: occasion, Weekday: weekdayOf(task.StartDate), } @@ -197,20 +195,20 @@ func runGenerateTask(ctx context.Context, taskId, userId int64) { g.Log().Infof(ctx, "任务 %d 完成,共 %d 套方案", taskId, len(plans)) } -func scorePlan(p agent.PlanCandidate, items []*entity.WardrobeItem, ctxScore scoring.ScoreContext) int { - var out scoring.CandidateOutfit +func scorePlan(p agent.PlanCandidate, items []*entity.WardrobeItem, ctxScore agent.ScoreContext) int { + var out agent.CandidateOutfit byId := map[int64]*entity.WardrobeItem{} for _, it := range items { byId[it.Id] = it } for _, it := range p.Items { if w := byId[it.ItemId]; w != nil { - out.Items = append(out.Items, scoring.WardrobeItem{ + out.Items = append(out.Items, agent.WardrobeItem{ Category: w.Category, Season: w.Season, ColorInfo: w.ColorInfo, StyleTags: w.StyleTags, }) } } - return scoring.Score(&out, &ctxScore) + return agent.Score(&out, &ctxScore) } // ==================== 查询/操作 ==================== @@ -319,7 +317,7 @@ func bodyDescText(ctx context.Context, userId int64) string { return fmt.Sprintf("身高 %dcm,体重 %dkg,肤色 %d 档", bm.Height, bm.Weight, bm.SkinTone) } -func weatherSummaryText(w *weather.WeatherResult) string { +func weatherSummaryText(w *agent.WeatherResult) string { return fmt.Sprintf("%s(%s),平均 %d℃,%d 天", w.CityCode, w.Season, w.AvgTemp, len(w.Days)) } diff --git a/styleagent/service/weather_service.go b/styleagent/service/weather_service.go index 6877b81..423c784 100644 --- a/styleagent/service/weather_service.go +++ b/styleagent/service/weather_service.go @@ -5,22 +5,24 @@ import ( "fmt" "time" - "slogan-agent/styleagent/weather" + "slogan-agent/styleagent/agent" ) -var weatherCache = weather.NewCache(6 * time.Hour) +var weatherCache = agent.NewTTLCache(6 * time.Hour) // GetWeather 地点 + 日期范围 → 天气结果(高德地理编码 + 和风 7 天预报,缓存 6 小时) -func GetWeather(ctx context.Context, location, startDate, endDate string) (*weather.WeatherResult, error) { - cityCode, err := weather.GetCityCode(ctx, location) +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 result, ok := weatherCache.Get(cacheKey); ok { - return result, nil + if v, ok := weatherCache.Get(cacheKey); ok { + if result, ok := v.(*agent.WeatherResult); ok { + return result, nil + } } - result, err := weather.GetDaily(ctx, cityCode, startDate, endDate) + result, err := agent.GetDaily(ctx, cityCode, startDate, endDate) if err != nil { return nil, err }