refactor: 工具包并入 agent/(weather/imagegen/avatar/scoring,NewCache 泛化为 NewTTLCache)
This commit is contained in:
@@ -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)
|
||||
"
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
package avatar
|
||||
package agent
|
||||
|
||||
import "fmt"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package avatar
|
||||
package agent
|
||||
|
||||
// FaceFeature 从照片+用户填写提取的化身特征(v1:肤色/身高/体重来自身形参数,照片贴图后续增强)
|
||||
type FaceFeature struct {
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package imagegen
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package imagegen
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -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 {
|
||||
@@ -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"`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package scoring
|
||||
package agent
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package scoring
|
||||
package agent
|
||||
|
||||
// completenessScore 层次完整度(20 分制):上衣+5 下装+5 鞋+5 配饰+5
|
||||
func completenessScore(o CandidateOutfit) int {
|
||||
@@ -1,4 +1,4 @@
|
||||
package scoring
|
||||
package agent
|
||||
|
||||
// Score 总分(100 分制)
|
||||
func Score(c *CandidateOutfit, ctx *ScoreContext) int {
|
||||
@@ -1,4 +1,4 @@
|
||||
package scoring
|
||||
package agent
|
||||
|
||||
import "strings"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package scoring
|
||||
package agent
|
||||
|
||||
// WardrobeItem 评分用服装条目(从衣橱 entity 转换)
|
||||
type WardrobeItem struct {
|
||||
@@ -1,4 +1,4 @@
|
||||
package scoring
|
||||
package agent
|
||||
|
||||
// weatherScore 天气适宜度(25 分制)
|
||||
// 温度匹配每件服装季节 +5;<10℃ 无外套 -10;>30℃ 有外套 -8
|
||||
@@ -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)}
|
||||
@@ -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"`
|
||||
@@ -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"`
|
||||
@@ -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},
|
||||
|
||||
@@ -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)}
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user