From e8fa311b59b900766bd5fc41af80b9de846defa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Tue, 4 Aug 2026 15:25:11 +0800 Subject: [PATCH] 1 --- Dockerfile | 17 +- common/auth_middleware.go | 8 + config.yml | 10 +- main.go | 27 +++ short_drama.db | Bin 462848 -> 0 bytes shortdrama/consts/table_name.go | 7 + .../controller/payment_order_controller.go | 16 +- shortdrama/dao/account_transaction_dao.go | 14 +- shortdrama/dao/agent_profile_dao.go | 131 ++++++++---- shortdrama/dao/customer_profile_dao.go | 197 ++++++++++++++---- shortdrama/dao/model_config_dao.go | 73 ++++--- shortdrama/dao/payment_channel_trade_dao.go | 8 +- shortdrama/dao/payment_config_dao.go | 33 ++- shortdrama/dao/payment_order_dao.go | 24 +-- shortdrama/dao/region_pricing_dao.go | 38 ++-- shortdrama/dao/user_dao.go | 102 ++++----- shortdrama/dao/user_model_config_dao.go | 44 ++-- shortdrama/model/agent_output.go | 15 +- shortdrama/service/agent_service.go | 88 ++++++-- shortdrama/service/customer_service.go | 108 ++++++---- shortdrama/service/drama_service.go | 10 +- shortdrama/service/episode_service.go | 80 +++++-- shortdrama/service/generation_service.go | 153 ++++++++++---- shortdrama/service/model_config_service.go | 19 +- shortdrama/service/payment_order_service.go | 74 ++++--- shortdrama/service/transaction_service.go | 50 +++-- .../service/user_model_config_service.go | 22 +- shortdrama/service/user_service.go | 6 +- 28 files changed, 972 insertions(+), 402 deletions(-) delete mode 100644 short_drama.db diff --git a/Dockerfile b/Dockerfile index 30b64a0..6a0ec6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,13 @@ +# ==================== 前端构建(ui-src) ==================== +FROM node:20-alpine AS ui-builder +RUN apk add --no-cache git +WORKDIR /build-ui +COPY video-factory/ui-src/package.json video-factory/ui-src/package-lock.json ./ +RUN npm ci --registry=https://registry.npmmirror.com +COPY video-factory/ui-src/ ./ +RUN npm run build + +# ==================== 后端构建 ==================== FROM golang:alpine AS builder RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ && apk add --no-cache git ca-certificates tzdata @@ -7,10 +17,11 @@ ENV GOPROXY=https://goproxy.cn,direct ENV CGO_ENABLED=0 ENV GOTOOLCHAIN=auto WORKDIR /build -COPY . . +COPY video-factory/ . RUN go mod download && go mod tidy RUN go build -ldflags="-s -w" -o main ./main.go +# ==================== 运行镜像 ==================== FROM alpine:3.19 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ && apk add --no-cache ca-certificates tzdata ffmpeg @@ -22,6 +33,8 @@ COPY --from=builder /build/prompt.md . COPY --from=builder /build/negative_prompt.md . COPY --from=builder /build/default_first_frame.png . COPY --from=builder /build/main . -RUN printf '#!/bin/sh\nif [ -d /app/short_drama.db ]; then rm -rf /app/short_drama.db; fi\ntouch /app/short_drama.db 2>/dev/null || true\nexec ./main\n' > /app/entrypoint.sh && chmod +x /app/entrypoint.sh +COPY --from=ui-builder /build-ui/dist ./ui-src/dist +RUN mkdir -p /app/data \ + && printf '#!/bin/sh\nfor db in business.db system.db finance.db; do\n if [ -d /app/data/$db ]; then rm -rf /app/data/$db; fi\n touch /app/data/$db 2>/dev/null || true\ndone\nexec ./main\n' > /app/entrypoint.sh && chmod +x /app/entrypoint.sh EXPOSE 3006 ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/common/auth_middleware.go b/common/auth_middleware.go index 01f8ebe..907f58d 100644 --- a/common/auth_middleware.go +++ b/common/auth_middleware.go @@ -28,6 +28,14 @@ func Auth(r *ghttp.Request) { return } + // 前端静态资源放行(合并部署后 UI 由后端服务,浏览器请求不带 Authorization): + // 仅放行 /(入口页)与 /assets/*(构建产物),hash 路由下 SPA 页面只会请求这两个路径; + // 其余路径(包括与 SPA 页面同名的 /drama、/customer 等)一律走 API 鉴权,无鉴权绕过 + if r.Method == http.MethodGet && (path == "/" || strings.HasPrefix(path, "/assets/")) { + r.Middleware.Next() + return + } + auth := r.Header.Get("Authorization") if auth == "" || !strings.HasPrefix(auth, "Bearer ") { r.Response.WriteJson(ghttp.DefaultHandlerResponse{ diff --git a/config.yml b/config.yml index dec3d01..2e62754 100644 --- a/config.yml +++ b/config.yml @@ -1,8 +1,16 @@ database: default: - name: short_drama.db + name: data/business.db type: sqlite debug: true # (可选)开启调试模式 + system: + name: data/system.db + type: sqlite + debug: true + finance: + name: data/finance.db + type: sqlite + debug: true cache: ttl: 60 # DAO查询缓存时间(秒),0为禁用缓存 server: diff --git a/main.go b/main.go index 01b43b1..c531a09 100644 --- a/main.go +++ b/main.go @@ -92,6 +92,33 @@ func main() { r.Response.ServeFile(filePath) }) + // ==================== 前端静态资源服务(SPA,前后端合并部署) ==================== + // 静态资源目录为 ui-src/dist:本地是 npm run build 产物,Docker 运行镜像由 + // ui-builder 阶段将 dist 注入 /app/ui-src/dist,本地与容器路径一致,无需拷贝。 + // 不用 AddStaticPath:GF 静态前缀有边界检查(前缀后一字符必须为 /), + // "/" 前缀无法命中 /assets/* 子路径,只能服务 "/" 本身。 + // 前端使用 hash 路由,浏览器只请求 / 与 /assets/*,BindHandler("/*") 精确覆盖即可, + // 无需 SPA fallback;具体 API 路由与 /workspace/* 优先级更高不受影响, + // 且 auth 中间件仅放行 GET / 与 GET /assets/*,其余路径一律走鉴权 + if st, err := os.Stat("ui-src/dist"); err == nil && st.IsDir() { + commonHttp.Httpserver.BindHandler("/*", func(r *ghttp.Request) { + path := strings.TrimPrefix(r.URL.Path, "/") + if strings.Contains(path, "..") { + r.Response.WriteStatus(http.StatusNotFound) + return + } + if path == "" || path == "index.html" { + path = "index.html" + } + filePath := filepath.Join("ui-src/dist", path) + if st, err := os.Stat(filePath); err == nil && !st.IsDir() { + r.Response.ServeFile(filePath) + return + } + r.Response.WriteStatus(http.StatusNotFound) + }) + } + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() diff --git a/short_drama.db b/short_drama.db deleted file mode 100644 index c3305d53de65cd7da0710eed2bf77caddf2c8c50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462848 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 {64DoLC*v!D3BH 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") + if err := service.PaymentService.HandleNotify(ctx, "wechat", body); err != nil { + g.Log().Errorf(ctx, "wechat payment notify handle failed: %v", err) + r.Response.WriteString("") + } else { + r.Response.WriteString("") + } r.Exit() return nil, nil } @@ -73,8 +77,12 @@ func (c *paymentOrder) NotifyWechat(ctx context.Context, req *struct{}) (res *st func (c *paymentOrder) NotifyAlipay(ctx context.Context, req *struct{}) (res *struct{}, err error) { r := g.RequestFromCtx(ctx) body := r.GetBody() - _ = service.PaymentService.HandleNotify(ctx, "alipay", body) - r.Response.WriteString("success") + if err := service.PaymentService.HandleNotify(ctx, "alipay", body); err != nil { + g.Log().Errorf(ctx, "alipay payment notify handle failed: %v", err) + r.Response.WriteString("failure") + } else { + r.Response.WriteString("success") + } r.Exit() return nil, nil } diff --git a/shortdrama/dao/account_transaction_dao.go b/shortdrama/dao/account_transaction_dao.go index b96ff50..54b84b9 100644 --- a/shortdrama/dao/account_transaction_dao.go +++ b/shortdrama/dao/account_transaction_dao.go @@ -16,7 +16,7 @@ type accountTransactionDao struct{} func init() { ctx := context.Background() - _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameAccountTransaction+` ( + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameAccountTransaction+` ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL DEFAULT 0, type TEXT NOT NULL DEFAULT '', @@ -30,10 +30,10 @@ func init() { if err != nil { g.Log().Warningf(ctx, "create account_transaction table failed: %v", err) } - if _, err := g.DB().Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_at_user ON "+consts.TableNameAccountTransaction+"(user_id)"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_at_user ON "+consts.TableNameAccountTransaction+"(user_id)"); err != nil { g.Log().Warningf(ctx, "create index idx_at_user failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameAccountTransaction+" ADD COLUMN order_no TEXT NOT NULL DEFAULT ''"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "ALTER TABLE "+consts.TableNameAccountTransaction+" ADD COLUMN order_no TEXT NOT NULL DEFAULT ''"); err != nil { g.Log().Warningf(ctx, "add column order_no failed: %v", err) } } @@ -43,9 +43,9 @@ func clearAccountTransactionCache(ctx context.Context, userId int64) { } func (d *accountTransactionDao) Insert(ctx context.Context, data *entity.AccountTransaction) (int64, error) { - r, err := g.DB().Exec(ctx, - "INSERT INTO "+consts.TableNameAccountTransaction+" (user_id, type, amount, balance_before, balance_after, remark, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now','localtime'))", - data.UserId, data.Type, data.Amount, data.BalanceBefore, data.BalanceAfter, data.Remark, data.CreatedBy) + r, err := g.DB(consts.DbGroupFinance).Exec(ctx, + "INSERT INTO "+consts.TableNameAccountTransaction+" (user_id, type, amount, balance_before, balance_after, remark, created_by, order_no, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now','localtime'))", + data.UserId, data.Type, data.Amount, data.BalanceBefore, data.BalanceAfter, data.Remark, data.CreatedBy, data.OrderNo) if err != nil { return 0, err } @@ -54,7 +54,7 @@ func (d *accountTransactionDao) Insert(ctx context.Context, data *entity.Account } func (d *accountTransactionDao) ListByUser(ctx context.Context, userId int64, typeFilter string, page, pageSize int) ([]*entity.AccountTransaction, int, error) { - m := g.DB().Model(consts.TableNameAccountTransaction).Ctx(ctx).Where("user_id", userId) + m := g.DB(consts.DbGroupFinance).Model(consts.TableNameAccountTransaction).Ctx(ctx).Where("user_id", userId) if typeFilter != "" { m = m.Where("type", typeFilter) } diff --git a/shortdrama/dao/agent_profile_dao.go b/shortdrama/dao/agent_profile_dao.go index 4ec93d4..c16a0ba 100644 --- a/shortdrama/dao/agent_profile_dao.go +++ b/shortdrama/dao/agent_profile_dao.go @@ -31,51 +31,108 @@ type agentListRow struct { } // ListAgentWithProfile 按条件分页查询代理商列表(含 profile 信息) +// 拆库后改为多个单表查询 + 内存合并,避免跨表 JOIN func (d *agentProfileDao) ListAgentWithProfile(ctx context.Context, keyword, phone, province, region, expiredAtFrom, expiredAtTo string, page, pageSize int) ([]*agentListRow, int, error) { - m := g.DB().Model(consts.TableNameUser+" u"). - LeftJoin(consts.TableNameAgentProfile+" ap", "ap.user_id = u.id"). - Where("u.role", "agent") - // 分页+筛选查询不缓存(参数不同导致缓存键碰撞) - if keyword != "" { - m = m.Where("u.name LIKE ? OR u.username LIKE ? OR u.phone LIKE ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%") - } - if phone != "" { - m = m.Where("u.phone LIKE ?", "%"+phone+"%") - } - if province != "" { - m = m.Where("u.province = ?", province) - } - if region != "" { - m = m.Where("u.region = ?", region) - } - if expiredAtFrom != "" { - m = m.Where("ap.expired_at >= ?", expiredAtFrom) - } - if expiredAtTo != "" { - m = m.Where("ap.expired_at <= ?", expiredAtTo) - } if page < 1 { page = 1 } if pageSize < 1 { pageSize = 20 } - m = m.Fields( - "u.id", "u.username", "u.phone", "u.name", "u.province", "u.region", - "ap.expired_at", "ap.region_protected", "ap.max_customers", - ) - r, total, err := m.OrderAsc("u.id").Limit(pageSize).Offset((page - 1) * pageSize).AllAndCount(false) + + // 有效期筛选:先单表查出符合条件的代理商 user_id + var agentIds []int64 + if expiredAtFrom != "" || expiredAtTo != "" { + m := g.DB(consts.DbGroupFinance).Model(consts.TableNameAgentProfile).Ctx(ctx).Fields("user_id") + if expiredAtFrom != "" { + m = m.Where("expired_at >= ?", expiredAtFrom) + } + if expiredAtTo != "" { + m = m.Where("expired_at <= ?", expiredAtTo) + } + r, err := m.All() + if err != nil { + return nil, 0, err + } + for _, row := range r { + agentIds = append(agentIds, row["user_id"].Int64()) + } + if len(agentIds) == 0 { + return make([]*agentListRow, 0), 0, nil + } + } + + // 代理商主查询(user 表,role=agent + 关键字/手机号/省市筛选) + m := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx).Where("role", "agent") + if keyword != "" { + m = m.Where("name LIKE ? OR username LIKE ? OR phone LIKE ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%") + } + if phone != "" { + m = m.Where("phone LIKE ?", "%"+phone+"%") + } + if province != "" { + m = m.Where("province = ?", province) + } + if region != "" { + m = m.Where("region = ?", region) + } + if len(agentIds) > 0 { + m = m.WhereIn("id", agentIds) + } + total, err := m.Count() if err != nil { return nil, 0, err } - var rows []*agentListRow - err = r.Structs(&rows) - return rows, total, err + var users []*entity.User + err = m.OrderAsc("id").Limit(pageSize).Offset((page - 1) * pageSize).Scan(&users) + if err != nil { + return nil, 0, err + } + if len(users) == 0 { + return make([]*agentListRow, 0), total, nil + } + + // 批量补 profile 信息 + pageIds := make([]int64, 0, len(users)) + for _, u := range users { + pageIds = append(pageIds, u.Id) + } + profiles := make(map[int64]*entity.AgentProfile) + var profileList []*entity.AgentProfile + if err := g.DB(consts.DbGroupFinance).Model(consts.TableNameAgentProfile).Ctx(ctx). + WhereIn("user_id", pageIds).Scan(&profileList); err != nil { + return nil, 0, err + } + for _, p := range profileList { + profiles[p.UserId] = p + } + + // 内存合并组装结果(无 profile 时输出零值,与 LEFT JOIN 语义一致) + rows := make([]*agentListRow, 0, len(users)) + for _, u := range users { + row := &agentListRow{ + Id: u.Id, + Username: u.Username, + Phone: u.Phone, + Name: u.Name, + Province: u.Province, + Region: u.Region, + } + if p, ok := profiles[u.Id]; ok { + row.ExpiredAt = p.ExpiredAt + if p.RegionProtected { + row.RegionProtected = 1 + } + row.MaxCustomers = p.MaxCustomers + } + rows = append(rows, row) + } + return rows, total, nil } func init() { ctx := context.Background() - _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameAgentProfile+` ( + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameAgentProfile+` ( user_id INTEGER PRIMARY KEY, max_customers INTEGER NOT NULL DEFAULT 0, renewals INTEGER NOT NULL DEFAULT 0, @@ -85,13 +142,13 @@ func init() { if err != nil { g.Log().Warningf(ctx, "create agent_profile table failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameAgentProfile+" ADD COLUMN expired_at DATETIME"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "ALTER TABLE "+consts.TableNameAgentProfile+" ADD COLUMN expired_at DATETIME"); err != nil { g.Log().Warningf(ctx, "add column expired_at failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameAgentProfile+" ADD COLUMN region_protected INTEGER NOT NULL DEFAULT 0"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "ALTER TABLE "+consts.TableNameAgentProfile+" ADD COLUMN region_protected INTEGER NOT NULL DEFAULT 0"); err != nil { g.Log().Warningf(ctx, "add column region_protected failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameAgentProfile+" DROP COLUMN tier_id"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "ALTER TABLE "+consts.TableNameAgentProfile+" DROP COLUMN tier_id"); err != nil { g.Log().Warningf(ctx, "drop column tier_id failed: %v", err) } @@ -104,7 +161,7 @@ func clearAgentProfileCache(ctx context.Context, userId int64) { func (d *agentProfileDao) Get(ctx context.Context, userId int64) (*entity.AgentProfile, error) { var p entity.AgentProfile - err := g.DB().Model(consts.TableNameAgentProfile).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNameAgentProfile).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "agentProfile_Get_" + gconv.String(userId)}). Where("user_id", userId).Scan(&p) if err != nil { @@ -121,7 +178,7 @@ func (d *agentProfileDao) Save(ctx context.Context, data *entity.AgentProfile) e if data.ExpiredAt != nil { expiredAtStr = data.ExpiredAt.Format("Y-m-d H:i:s") } - _, err := g.DB().Exec(ctx, + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "INSERT OR REPLACE INTO "+consts.TableNameAgentProfile+" (user_id, max_customers, renewals, expired_at, region_protected) VALUES (?, ?, ?, ?, ?)", data.UserId, data.MaxCustomers, data.Renewals, expiredAtStr, data.RegionProtected) clearAgentProfileCache(ctx, data.UserId) @@ -137,7 +194,7 @@ func (d *agentProfileDao) Update(ctx context.Context, data *entity.AgentProfile) if data.ExpiredAt != nil { m["expired_at"] = data.ExpiredAt.Format("Y-m-d H:i:s") } - _, err := g.DB().Model(consts.TableNameAgentProfile).Ctx(ctx).Data(m).Where("user_id", data.UserId).Update() + _, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameAgentProfile).Ctx(ctx).Data(m).Where("user_id", data.UserId).Update() clearAgentProfileCache(ctx, data.UserId) return err } diff --git a/shortdrama/dao/customer_profile_dao.go b/shortdrama/dao/customer_profile_dao.go index 0ca1fc4..14b3b1e 100644 --- a/shortdrama/dao/customer_profile_dao.go +++ b/shortdrama/dao/customer_profile_dao.go @@ -32,55 +32,172 @@ type customerListRow struct { } // ListCustomerWithAgent 按条件分页查询客户列表(含代理商名) +// 拆库后改为多个单表查询 + 内存合并,避免跨表 JOIN func (d *customerProfileDao) ListCustomerWithAgent(ctx context.Context, agentId int64, keyword, phone, province, region, agentName string, page, pageSize int) ([]*customerListRow, int, error) { - m := g.DB().Model(consts.TableNameUser+" u"). - InnerJoin(consts.TableNameCustomerProfile+" cp", "cp.user_id = u.id"). - LeftJoin(consts.TableNameUser+" au", "au.id = cp.agent_id"). - Where("u.role", "customer") - // 分页+筛选查询不缓存(参数不同导致缓存键碰撞) - - if agentId > 0 { - m = m.Where("cp.agent_id", agentId) - } - if keyword != "" { - m = m.Where("u.name LIKE ? OR u.phone LIKE ?", "%"+keyword+"%", "%"+keyword+"%") - } - if phone != "" { - m = m.Where("u.phone LIKE ?", "%"+phone+"%") - } - if province != "" { - m = m.Where("u.province = ?", province) - } - if region != "" { - m = m.Where("u.region = ?", region) - } - if agentName != "" { - m = m.Where("au.name LIKE ? OR au.username LIKE ? OR au.phone LIKE ?", "%"+agentName+"%", "%"+agentName+"%", "%"+agentName+"%") - } - if page < 1 { page = 1 } if pageSize < 1 { pageSize = 20 } - m = m.Fields( - "u.id", "u.phone", "u.name", "u.province", "u.region", "u.address", "u.created_at", - "cp.agent_id", "cp.balance", - "au.name agent_name", - ) - r, total, err := m.OrderAsc("u.id").Limit(pageSize).Offset((page - 1) * pageSize).AllAndCount(false) + + // 代理商维度筛选:单表查出符合条件的客户 user_id 集合 + var customerIds []int64 + switch { + case agentId > 0: + ids, err := d.listUserIdsByAgent(ctx, agentId) + if err != nil { + return nil, 0, err + } + if len(ids) == 0 { + return make([]*customerListRow, 0), 0, nil + } + customerIds = ids + case agentName != "": + agentIds, err := d.listAgentIdsByName(ctx, agentName) + if err != nil { + return nil, 0, err + } + if len(agentIds) == 0 { + return make([]*customerListRow, 0), 0, nil + } + ids, err := d.listUserIdsByAgents(ctx, agentIds) + if err != nil { + return nil, 0, err + } + if len(ids) == 0 { + return make([]*customerListRow, 0), 0, nil + } + customerIds = ids + } + + // 客户主查询(user 表,role=customer + 关键字/手机号/省市筛选) + m := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx).Where("role", "customer") + if keyword != "" { + m = m.Where("name LIKE ? OR phone LIKE ?", "%"+keyword+"%", "%"+keyword+"%") + } + if phone != "" { + m = m.Where("phone LIKE ?", "%"+phone+"%") + } + if province != "" { + m = m.Where("province = ?", province) + } + if region != "" { + m = m.Where("region = ?", region) + } + if len(customerIds) > 0 { + m = m.WhereIn("id", customerIds) + } + total, err := m.Count() if err != nil { return nil, 0, err } - var rows []*customerListRow - err = r.Structs(&rows) - return rows, total, err + var users []*entity.User + err = m.OrderAsc("id").Limit(pageSize).Offset((page - 1) * pageSize).Scan(&users) + if err != nil { + return nil, 0, err + } + if len(users) == 0 { + return make([]*customerListRow, 0), total, nil + } + + // 批量补客户档案(agent_id、balance) + pageIds := make([]int64, 0, len(users)) + for _, u := range users { + pageIds = append(pageIds, u.Id) + } + profiles := make(map[int64]*entity.CustomerProfile) + var profileList []*entity.CustomerProfile + if err := g.DB(consts.DbGroupFinance).Model(consts.TableNameCustomerProfile).Ctx(ctx). + WhereIn("user_id", pageIds).Scan(&profileList); err != nil { + return nil, 0, err + } + for _, p := range profileList { + profiles[p.UserId] = p + } + + // 批量补代理商名称 + agentNameMap := make(map[int64]string) + agentIds := make([]int64, 0, len(profiles)) + for _, p := range profiles { + if p.AgentId > 0 { + agentIds = append(agentIds, p.AgentId) + } + } + if len(agentIds) > 0 { + var agents []*entity.User + if err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx). + Fields("id", "name").WhereIn("id", agentIds).Scan(&agents); err != nil { + return nil, 0, err + } + for _, a := range agents { + agentNameMap[a.Id] = a.Name + } + } + + // 内存合并组装结果 + rows := make([]*customerListRow, 0, len(users)) + for _, u := range users { + row := &customerListRow{ + Id: u.Id, + Phone: u.Phone, + Name: u.Name, + Province: u.Province, + Region: u.Region, + Address: u.Address, + CreatedAt: u.CreatedAt, + } + if p, ok := profiles[u.Id]; ok { + row.AgentId = p.AgentId + row.Balance = p.Balance + row.AgentName = agentNameMap[p.AgentId] + } + rows = append(rows, row) + } + return rows, total, nil +} + +// listAgentIdsByName 按名称/账号/手机号模糊匹配代理商 user_id +func (d *customerProfileDao) listAgentIdsByName(ctx context.Context, keyword string) ([]int64, error) { + r, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx). + Fields("id").Where("role", "agent"). + Where("name LIKE ? OR username LIKE ? OR phone LIKE ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%"). + All() + if err != nil { + return nil, err + } + ids := make([]int64, 0, len(r)) + for _, row := range r { + ids = append(ids, row["id"].Int64()) + } + return ids, nil +} + +// listUserIdsByAgent 查询指定代理商名下的客户 user_id +func (d *customerProfileDao) listUserIdsByAgent(ctx context.Context, agentId int64) ([]int64, error) { + return d.listUserIdsByAgents(ctx, []int64{agentId}) +} + +// listUserIdsByAgents 批量查询多个代理商名下的客户 user_id +func (d *customerProfileDao) listUserIdsByAgents(ctx context.Context, agentIds []int64) ([]int64, error) { + if len(agentIds) == 0 { + return nil, nil + } + r, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameCustomerProfile).Ctx(ctx). + Fields("user_id").WhereIn("agent_id", agentIds).All() + if err != nil { + return nil, err + } + ids := make([]int64, 0, len(r)) + for _, row := range r { + ids = append(ids, row["user_id"].Int64()) + } + return ids, nil } func init() { ctx := context.Background() - _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameCustomerProfile+` ( + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameCustomerProfile+` ( user_id INTEGER PRIMARY KEY, agent_id INTEGER NOT NULL DEFAULT 0, balance INTEGER NOT NULL DEFAULT 0 @@ -88,7 +205,7 @@ func init() { if err != nil { g.Log().Warningf(ctx, "create customer_profile table failed: %v", err) } - if _, err := g.DB().Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_cp_agent ON "+consts.TableNameCustomerProfile+"(agent_id)"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_cp_agent ON "+consts.TableNameCustomerProfile+"(agent_id)"); err != nil { g.Log().Warningf(ctx, "create index idx_cp_agent failed: %v", err) } } @@ -101,7 +218,7 @@ func clearCustomerProfileCache(ctx context.Context, userId int64, agentId int64) func (d *customerProfileDao) Get(ctx context.Context, userId int64) (*entity.CustomerProfile, error) { var p entity.CustomerProfile - err := g.DB().Model(consts.TableNameCustomerProfile).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNameCustomerProfile).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "customerProfile_Get_" + gconv.String(userId)}). Where("user_id", userId).Scan(&p) if err != nil { @@ -114,7 +231,7 @@ func (d *customerProfileDao) Get(ctx context.Context, userId int64) (*entity.Cus } func (d *customerProfileDao) Save(ctx context.Context, data *entity.CustomerProfile) error { - _, err := g.DB().Exec(ctx, + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "INSERT OR REPLACE INTO "+consts.TableNameCustomerProfile+" (user_id, agent_id, balance) VALUES (?, ?, ?)", data.UserId, data.AgentId, data.Balance) clearCustomerProfileCache(ctx, data.UserId, data.AgentId) @@ -127,14 +244,14 @@ func (d *customerProfileDao) UpdateBalance(ctx context.Context, userId int64, ne if p, _ := d.Get(ctx, userId); p != nil { agentId = p.AgentId } - _, err := g.DB().Model(consts.TableNameCustomerProfile).Ctx(ctx). + _, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameCustomerProfile).Ctx(ctx). Data(g.Map{"balance": newBalance}).Where("user_id", userId).Update() clearCustomerProfileCache(ctx, userId, agentId) return err } func (d *customerProfileDao) CountByAgent(ctx context.Context, agentId int64) (int, error) { - c, err := g.DB().Model(consts.TableNameCustomerProfile).Ctx(ctx). + c, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameCustomerProfile).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "customerProfile_CountByAgent_" + gconv.String(agentId)}). Where("agent_id", agentId).Count() return c, err diff --git a/shortdrama/dao/model_config_dao.go b/shortdrama/dao/model_config_dao.go index 27d382b..46de20e 100644 --- a/shortdrama/dao/model_config_dao.go +++ b/shortdrama/dao/model_config_dao.go @@ -20,7 +20,7 @@ type modelConfigDao struct{} func init() { ctx := context.Background() - _, err := g.DB().Exec(ctx, + _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "CREATE TABLE IF NOT EXISTS "+consts.TableNameModelConfig+" ("+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"+ "model_type TEXT NOT NULL DEFAULT '',"+ @@ -38,7 +38,9 @@ func init() { // 检测旧表结构:含 base_url / api_key / chat_api_key / is_active / task_callback_url 等旧列 → 迁移 hasOldColumns := false - if r, _ := g.DB().GetAll(ctx, "PRAGMA table_info("+consts.TableNameModelConfig+")"); r != nil { + if r, qErr := g.DB(consts.DbGroupSystem).GetAll(ctx, "PRAGMA table_info("+consts.TableNameModelConfig+")"); qErr != nil { + g.Log().Warningf(ctx, "query model_config table_info failed: %v", qErr) + } else if r != nil { for _, col := range r { name := col["name"].String() if name == "base_url" || name == "api_key" || name == "chat_api_key" || name == "is_active" || name == "price_per_second" || name == "max_tokens" || name == "task_callback_url" { @@ -51,7 +53,7 @@ func init() { if hasOldColumns { g.Log().Info(ctx, "检测到旧表结构,开始迁移 model_config...") newTable := consts.TableNameModelConfig + "_new" - if _, err := g.DB().Exec(ctx, + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "CREATE TABLE IF NOT EXISTS "+newTable+" ("+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"+ "model_type TEXT NOT NULL DEFAULT '',"+ @@ -68,15 +70,18 @@ func init() { } hasChatApiKey := false - if _, err := g.DB().Exec(ctx, "SELECT chat_api_key FROM "+consts.TableNameModelConfig+" LIMIT 1"); err == nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "SELECT chat_api_key FROM "+consts.TableNameModelConfig+" LIMIT 1"); err == nil { hasChatApiKey = true } now := gtime.Now().Format("Y-m-d H:i:s") if hasChatApiKey { - oldRow, _ := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx).Limit(1).One() + oldRow, qErr := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx).Limit(1).One() + if qErr != nil { + g.Log().Warningf(ctx, "query old model config row failed: %v", qErr) + } if oldRow != nil && !oldRow.IsEmpty() { - if _, err := g.DB().Model(newTable).Ctx(ctx).Data(g.Map{ + if _, err := g.DB(consts.DbGroupSystem).Model(newTable).Ctx(ctx).Data(g.Map{ "model_type": "chat", "model_name": oldRow["chat_model_name"], "schema": oldRow["chat_schema"], "price": 0, "price_unit": "video", @@ -84,7 +89,7 @@ func init() { }).Insert(); err != nil { g.Log().Warningf(ctx, "insert chat model config failed: %v", err) } - if _, err := g.DB().Model(newTable).Ctx(ctx).Data(g.Map{ + if _, err := g.DB(consts.DbGroupSystem).Model(newTable).Ctx(ctx).Data(g.Map{ "model_type": "video", "model_name": oldRow["video_model_name"], "schema": oldRow["video_schema"], "price": gconv.Int(oldRow["price_per_second"]), "price_unit": "second", @@ -94,7 +99,10 @@ func init() { } } } else { - oldRows, _ := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx).OrderAsc("id").All() + oldRows, qErr := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx).OrderAsc("id").All() + if qErr != nil { + g.Log().Warningf(ctx, "query old model config rows failed: %v", qErr) + } for _, or := range oldRows { mt := or["model_type"].String() priceVal := gconv.Int(or["price_per_second"]) @@ -105,7 +113,7 @@ func init() { if mt == "chat" { unit = "video" } - if _, err := g.DB().Model(newTable).Ctx(ctx).Data(g.Map{ + if _, err := g.DB(consts.DbGroupSystem).Model(newTable).Ctx(ctx).Data(g.Map{ "model_type": mt, "model_name": or["model_name"], "schema": or["schema"], "price": priceVal, "price_unit": unit, @@ -115,17 +123,19 @@ func init() { } } } - if _, err := g.DB().Exec(ctx, "DROP TABLE "+consts.TableNameModelConfig); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "DROP TABLE "+consts.TableNameModelConfig); err != nil { g.Log().Warningf(ctx, "drop old model_config table failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+newTable+" RENAME TO "+consts.TableNameModelConfig); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "ALTER TABLE "+newTable+" RENAME TO "+consts.TableNameModelConfig); err != nil { g.Log().Warningf(ctx, "rename temp table to model_config failed: %v", err) } g.Log().Info(ctx, "model_config 表结构迁移完成") } // 检测缺少 concurrency_count 列的情况(已有新表结构但字段不全) - if r, _ := g.DB().GetAll(ctx, "PRAGMA table_info("+consts.TableNameModelConfig+")"); r != nil { + if r, qErr := g.DB(consts.DbGroupSystem).GetAll(ctx, "PRAGMA table_info("+consts.TableNameModelConfig+")"); qErr != nil { + g.Log().Warningf(ctx, "query model_config table_info failed: %v", qErr) + } else if r != nil { hasCol := false for _, col := range r { if col["name"].String() == "concurrency_count" { @@ -135,7 +145,7 @@ func init() { } if !hasCol { g.Log().Info(ctx, "检测到 model_config 缺少 concurrency_count 列,正在补充...") - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameModelConfig+" ADD COLUMN concurrency_count INTEGER NOT NULL DEFAULT 1"); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "ALTER TABLE "+consts.TableNameModelConfig+" ADD COLUMN concurrency_count INTEGER NOT NULL DEFAULT 1"); err != nil { g.Log().Warningf(ctx, "add concurrency_count column failed: %v", err) } else { g.Log().Info(ctx, "concurrency_count 列补充完成") @@ -144,7 +154,9 @@ func init() { } // 检测并迁移:first_frame_mapping -> schema_mapping - if r, _ := g.DB().GetAll(ctx, "PRAGMA table_info("+consts.TableNameModelConfig+")"); r != nil { + if r, qErr := g.DB(consts.DbGroupSystem).GetAll(ctx, "PRAGMA table_info("+consts.TableNameModelConfig+")"); qErr != nil { + g.Log().Warningf(ctx, "query model_config table_info failed: %v", qErr) + } else if r != nil { hasFirstFrame := false hasSchemaMapping := false for _, col := range r { @@ -159,18 +171,18 @@ func init() { // 旧表重命名 first_frame_mapping -> schema_mapping if hasFirstFrame && !hasSchemaMapping { g.Log().Info(ctx, "检测到旧 first_frame_mapping 列,正在迁移到 schema_mapping...") - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameModelConfig+" RENAME COLUMN first_frame_mapping TO schema_mapping"); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "ALTER TABLE "+consts.TableNameModelConfig+" RENAME COLUMN first_frame_mapping TO schema_mapping"); err != nil { g.Log().Warningf(ctx, "rename first_frame_mapping to schema_mapping failed: %v", err) } else { // 将旧值(纯文本路径)转为 JSON 格式 - g.DB().Exec(ctx, "UPDATE "+consts.TableNameModelConfig+" SET schema_mapping = '{}' WHERE schema_mapping != '' AND schema_mapping NOT LIKE '{'") + g.DB(consts.DbGroupSystem).Exec(ctx, "UPDATE "+consts.TableNameModelConfig+" SET schema_mapping = '{}' WHERE schema_mapping != '' AND schema_mapping NOT LIKE '{'") g.Log().Info(ctx, "first_frame_mapping 迁移到 schema_mapping 完成") } } // 无任何映射列时直接添加 schema_mapping if !hasFirstFrame && !hasSchemaMapping { g.Log().Info(ctx, "检测到 model_config 缺少 schema_mapping 列,正在补充...") - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameModelConfig+" ADD COLUMN schema_mapping TEXT NOT NULL DEFAULT ''"); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "ALTER TABLE "+consts.TableNameModelConfig+" ADD COLUMN schema_mapping TEXT NOT NULL DEFAULT ''"); err != nil { g.Log().Warningf(ctx, "add schema_mapping column failed: %v", err) } else { g.Log().Info(ctx, "schema_mapping 列补充完成") @@ -179,7 +191,9 @@ func init() { } // 检测并删除已废弃的 reference_template 列(后续从 schema_mapping 中获取) - if r, _ := g.DB().GetAll(ctx, "PRAGMA table_info("+consts.TableNameModelConfig+")"); r != nil { + if r, qErr := g.DB(consts.DbGroupSystem).GetAll(ctx, "PRAGMA table_info("+consts.TableNameModelConfig+")"); qErr != nil { + g.Log().Warningf(ctx, "query model_config table_info failed: %v", qErr) + } else if r != nil { hasRefTemplate := false for _, col := range r { if col["name"].String() == "reference_template" { @@ -189,7 +203,7 @@ func init() { } if hasRefTemplate { g.Log().Info(ctx, "检测到已废弃的 reference_template 列,正在删除...") - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameModelConfig+" DROP COLUMN reference_template"); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "ALTER TABLE "+consts.TableNameModelConfig+" DROP COLUMN reference_template"); err != nil { g.Log().Warningf(ctx, "drop reference_template column failed: %v", err) } else { g.Log().Info(ctx, "reference_template 列删除完成") @@ -198,7 +212,10 @@ func init() { } // 初始化默认模型配置(仅当表为空时) - count, _ := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx).Count() + count, qErr := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx).Count() + if qErr != nil { + g.Log().Warningf(ctx, "count model config failed: %v", qErr) + } if count == 0 { now := gtime.Now().Format("Y-m-d H:i:s") defaults := []g.Map{ @@ -206,7 +223,7 @@ func init() { {"model_type": "video", "model_name": "", "schema": "", "price": 10, "price_unit": "second", "created_at": now, "updated_at": now}, } for _, m := range defaults { - if _, e := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx).Data(m).Insert(); e != nil { + if _, e := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx).Data(m).Insert(); e != nil { g.Log().Warningf(ctx, "init default model config failed: %v", e) } } @@ -224,7 +241,7 @@ func clearModelConfigCache(ctx context.Context, modelType string) { } func (d *modelConfigDao) ListPage(ctx context.Context, page, pageSize int, modelType, keyword string) (res []*entity.ModelConfig, total int, err error) { - m := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx) + m := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx) if modelType != "" { m = m.Where("model_type", modelType) } @@ -270,7 +287,7 @@ func (d *modelConfigDao) ListPage(ctx context.Context, page, pageSize int, model } func (d *modelConfigDao) GetFirst(ctx context.Context) (res *entity.ModelConfig, err error) { - r, err := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx). + r, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "modelConfig_GetFirst"}). OrderAsc("id").Limit(1).One() if err != nil { @@ -285,7 +302,7 @@ func (d *modelConfigDao) GetFirst(ctx context.Context) (res *entity.ModelConfig, } func (d *modelConfigDao) GetActiveModelByType(ctx context.Context, modelType string) (res *entity.ModelConfig, err error) { - r, err := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx). + r, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "modelConfig_GetActiveModelByType_" + modelType}). Where("model_type", modelType).Limit(1).One() if err != nil { @@ -303,7 +320,7 @@ func (d *modelConfigDao) GetByIds(ctx context.Context, ids []int64) (res []*enti if len(ids) == 0 { return make([]*entity.ModelConfig, 0), nil } - r, err := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx). + r, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx). WhereIn("id", ids).OrderAsc("id").All() if err != nil { return nil, err @@ -316,7 +333,7 @@ func (d *modelConfigDao) GetByIds(ctx context.Context, ids []int64) (res []*enti } func (d *modelConfigDao) GetAll(ctx context.Context) (res []*entity.ModelConfig, err error) { - r, err := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx). + r, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx). Cache(gdb.CacheOption{Duration: time.Minute, Name: "model_config_GetAll"}). OrderAsc("id").Limit(200).All() if err != nil { @@ -331,7 +348,7 @@ func (d *modelConfigDao) GetAll(ctx context.Context) (res []*entity.ModelConfig, func (d *modelConfigDao) Save(ctx context.Context, data *entity.ModelConfig) error { if data.Id > 0 { - _, err := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx). + _, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx). Data(data).Where("id", data.Id).Update() if err != nil { return err @@ -341,7 +358,7 @@ func (d *modelConfigDao) Save(ctx context.Context, data *entity.ModelConfig) err delete(m, "id") m["created_at"] = gtime.Now().Format("Y-m-d H:i:s") m["updated_at"] = gtime.Now().Format("Y-m-d H:i:s") - _, err := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx). + _, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameModelConfig).Ctx(ctx). Data(m).Insert() if err != nil { return err diff --git a/shortdrama/dao/payment_channel_trade_dao.go b/shortdrama/dao/payment_channel_trade_dao.go index ad1ed42..29db4a2 100644 --- a/shortdrama/dao/payment_channel_trade_dao.go +++ b/shortdrama/dao/payment_channel_trade_dao.go @@ -18,7 +18,7 @@ type paymentChannelTradeDao struct{} func init() { ctx := context.Background() - _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNamePaymentChannelTrade+` ( + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNamePaymentChannelTrade+` ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL DEFAULT 0, channel TEXT NOT NULL DEFAULT '', @@ -31,7 +31,7 @@ func init() { if err != nil { g.Log().Warningf(ctx, "create payment_channel_trade table failed: %v", err) } - if _, err := g.DB().Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_pct_order ON "+consts.TableNamePaymentChannelTrade+"(order_id)"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_pct_order ON "+consts.TableNamePaymentChannelTrade+"(order_id)"); err != nil { g.Log().Warningf(ctx, "create index idx_pct_order failed: %v", err) } } @@ -41,7 +41,7 @@ func clearPaymentChannelTradeCache(ctx context.Context, orderId int64) { } func (d *paymentChannelTradeDao) Insert(ctx context.Context, data *entity.PaymentChannelTrade) (int64, error) { - r, err := g.DB().Exec(ctx, + r, err := g.DB(consts.DbGroupFinance).Exec(ctx, "INSERT INTO "+consts.TableNamePaymentChannelTrade+" (order_id, channel, prepay_id, code_url, trade_no, channel_response, created_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now','localtime'))", data.OrderId, data.Channel, data.PrepayId, data.CodeUrl, data.TradeNo, data.ChannelResponse) if err != nil { @@ -53,7 +53,7 @@ func (d *paymentChannelTradeDao) Insert(ctx context.Context, data *entity.Paymen func (d *paymentChannelTradeDao) GetByOrderId(ctx context.Context, orderId int64) (*entity.PaymentChannelTrade, error) { var t entity.PaymentChannelTrade - err := g.DB().Model(consts.TableNamePaymentChannelTrade).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNamePaymentChannelTrade).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "paymentChannelTrade_GetByOrderId_" + gconv.String(orderId)}). Where("order_id", orderId).Scan(&t) if err != nil { diff --git a/shortdrama/dao/payment_config_dao.go b/shortdrama/dao/payment_config_dao.go index c66a69d..cb5128b 100644 --- a/shortdrama/dao/payment_config_dao.go +++ b/shortdrama/dao/payment_config_dao.go @@ -20,7 +20,7 @@ type paymentConfigDao struct{} func init() { ctx := context.Background() - if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNamePaymentConfig+` ( + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNamePaymentConfig+` ( id INTEGER PRIMARY KEY AUTOINCREMENT, channel TEXT NOT NULL DEFAULT '', channel_type TEXT NOT NULL DEFAULT '', @@ -35,6 +35,25 @@ func init() { )`); err != nil { g.Log().Warningf(ctx, "create payment config table failed: %v", err) } + // backfill created_at for legacy data + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "UPDATE "+consts.TableNamePaymentConfig+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { + g.Log().Warningf(ctx, "backfill payment_config created_at failed: %v", err) + } + // UTC to local timezone migration, run once via PRAGMA user_version + var userVersion int + if r, qErr := g.DB(consts.DbGroupSystem).Query(ctx, "PRAGMA user_version"); qErr != nil { + g.Log().Warningf(ctx, "query user_version failed: %v", qErr) + } else if len(r) > 0 { + userVersion = r[0]["user_version"].Int() + } + if userVersion == 0 { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "UPDATE "+consts.TableNamePaymentConfig+" SET created_at = datetime(created_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z'"); err != nil { + g.Log().Warningf(ctx, "migrate payment_config UTC to local failed: %v", err) + } + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "PRAGMA user_version = 1"); err != nil { + g.Log().Warningf(ctx, "set user_version failed: %v", err) + } + } } func clearPaymentConfigCache(ctx context.Context) { @@ -47,7 +66,7 @@ func (d *paymentConfigDao) GetByChannel(ctx context.Context, channel string, typ if len(typeOpt) > 0 && typeOpt[0] != "" { cacheKey += "_" + typeOpt[0] } - m := g.DB().Model(consts.TableNamePaymentConfig).Ctx(ctx). + m := g.DB(consts.DbGroupSystem).Model(consts.TableNamePaymentConfig).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}). Where("channel", channel) if len(typeOpt) > 0 && typeOpt[0] != "" { @@ -72,7 +91,7 @@ func (d *paymentConfigDao) Save(ctx context.Context, data *entity.PaymentConfig) } if existing != nil { data.Id = existing.Id - _, err = g.DB().Model(consts.TableNamePaymentConfig).Ctx(ctx). + _, err = g.DB(consts.DbGroupSystem).Model(consts.TableNamePaymentConfig).Ctx(ctx). Data(data).Where("id", existing.Id).Update() clearPaymentConfigCache(ctx) return err @@ -81,14 +100,14 @@ func (d *paymentConfigDao) Save(ctx context.Context, data *entity.PaymentConfig) delete(m, "id") m["created_at"] = gtime.Now().Format("Y-m-d H:i:s") m["updated_at"] = gtime.Now().Format("Y-m-d H:i:s") - _, err = g.DB().Model(consts.TableNamePaymentConfig).Ctx(ctx). + _, err = g.DB(consts.DbGroupSystem).Model(consts.TableNamePaymentConfig).Ctx(ctx). Data(m).Insert() clearPaymentConfigCache(ctx) return err } func (d *paymentConfigDao) GetAll(ctx context.Context) (res []*entity.PaymentConfig, err error) { - r, err := g.DB().Model(consts.TableNamePaymentConfig).Ctx(ctx). + r, err := g.DB(consts.DbGroupSystem).Model(consts.TableNamePaymentConfig).Ctx(ctx). Cache(gdb.CacheOption{Duration: time.Minute, Name: "payment_config_GetAll"}). OrderAsc("id").All() if err != nil { @@ -99,7 +118,9 @@ func (d *paymentConfigDao) GetAll(ctx context.Context) (res []*entity.PaymentCon } for _, item := range r { cfg := new(entity.PaymentConfig) - _ = item.Struct(&cfg) + if err := item.Struct(&cfg); err != nil { + g.Log().Warningf(ctx, "parse payment config row failed: %v", err) + } res = append(res, cfg) } return diff --git a/shortdrama/dao/payment_order_dao.go b/shortdrama/dao/payment_order_dao.go index 0179aa2..b6c4cc8 100644 --- a/shortdrama/dao/payment_order_dao.go +++ b/shortdrama/dao/payment_order_dao.go @@ -18,7 +18,7 @@ type paymentOrderDao struct{} func init() { ctx := context.Background() - _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNamePaymentOrder+` ( + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNamePaymentOrder+` ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_no TEXT NOT NULL UNIQUE, order_type TEXT NOT NULL DEFAULT 'recharge', @@ -37,19 +37,19 @@ func init() { if err != nil { g.Log().Warningf(ctx, "create payment_order table failed: %v", err) } - if _, err := g.DB().Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_po_user ON "+consts.TableNamePaymentOrder+"(user_id)"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_po_user ON "+consts.TableNamePaymentOrder+"(user_id)"); err != nil { g.Log().Warningf(ctx, "create index idx_po_user failed: %v", err) } - if _, err := g.DB().Exec(ctx, + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_po_order_type ON payment_order(order_type)"); err != nil { g.Log().Warningf(ctx, "create order type index failed: %v", err) } - if _, err := g.DB().Exec(ctx, + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_po_status ON payment_order(status)"); err != nil { g.Log().Warningf(ctx, "create order status index failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNamePaymentOrder+" ADD COLUMN order_type TEXT NOT NULL DEFAULT 'recharge'"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "ALTER TABLE "+consts.TableNamePaymentOrder+" ADD COLUMN order_type TEXT NOT NULL DEFAULT 'recharge'"); err != nil { g.Log().Warningf(ctx, "add column order_type failed: %v", err) } } @@ -62,7 +62,7 @@ func clearPaymentOrderCache(ctx context.Context, userId int64, status string) { } func (d *paymentOrderDao) Insert(ctx context.Context, data *entity.PaymentOrder) (int64, error) { - r, err := g.DB().Exec(ctx, + r, err := g.DB(consts.DbGroupFinance).Exec(ctx, "INSERT INTO "+consts.TableNamePaymentOrder+" (order_no, order_type, user_id, amount, channel, channel_type, status, subject, notify_raw, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now','localtime'), datetime('now','localtime'))", data.OrderNo, data.OrderType, data.UserId, data.Amount, data.Channel, data.ChannelType, data.Status, data.Subject, data.NotifyRaw) if err != nil { @@ -74,7 +74,7 @@ func (d *paymentOrderDao) Insert(ctx context.Context, data *entity.PaymentOrder) func (d *paymentOrderDao) GetByOrderNo(ctx context.Context, orderNo string) (*entity.PaymentOrder, error) { var p entity.PaymentOrder - err := g.DB().Model(consts.TableNamePaymentOrder).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNamePaymentOrder).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "paymentOrder_GetByOrderNo_" + orderNo}). Where("order_no", orderNo).Scan(&p) if err != nil { @@ -88,14 +88,14 @@ func (d *paymentOrderDao) GetByOrderNo(ctx context.Context, orderNo string) (*en func (d *paymentOrderDao) ListByStatus(ctx context.Context, status string) ([]*entity.PaymentOrder, error) { var list []*entity.PaymentOrder - err := g.DB().Model(consts.TableNamePaymentOrder).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNamePaymentOrder).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "paymentOrder_ListByStatus_" + status}). Where("status", status).OrderDesc("id").Limit(200).Scan(&list) return list, err } func (d *paymentOrderDao) UpdateSuccess(ctx context.Context, id int64, notifyRaw string) error { - _, err := g.DB().Exec(ctx, + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNamePaymentOrder+" SET status='success', paid_at=datetime('now','localtime'), notify_raw=?, notify_count=notify_count+1, updated_at=datetime('now','localtime') WHERE id=?", notifyRaw, id) // Can only clear list-by-status cache @@ -105,7 +105,7 @@ func (d *paymentOrderDao) UpdateSuccess(ctx context.Context, id int64, notifyRaw } func (d *paymentOrderDao) UpdateFail(ctx context.Context, id int64, notifyRaw string) error { - _, err := g.DB().Exec(ctx, + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNamePaymentOrder+" SET status='failed', notify_raw=?, notify_count=notify_count+1, updated_at=datetime('now','localtime') WHERE id=?", notifyRaw, id) _, _ = gcache.Remove(ctx, "paymentOrder_ListByStatus_failed") @@ -115,7 +115,7 @@ func (d *paymentOrderDao) UpdateFail(ctx context.Context, id int64, notifyRaw st func (d *paymentOrderDao) ListByUserAndType(ctx context.Context, userId int64, orderType string) ([]*entity.PaymentOrder, error) { var list []*entity.PaymentOrder - err := g.DB().Model(consts.TableNamePaymentOrder).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNamePaymentOrder).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "paymentOrder_ListByUserAndType_" + gconv.String(userId) + "_" + orderType}). Where("user_id", userId). Where("order_type", orderType). @@ -124,7 +124,7 @@ func (d *paymentOrderDao) ListByUserAndType(ctx context.Context, userId int64, o } func (d *paymentOrderDao) ListPageByUser(ctx context.Context, userId int64, page, pageSize int) (res []*entity.PaymentOrder, total int, err error) { - m := g.DB().Model(consts.TableNamePaymentOrder).Ctx(ctx).Where("user_id", userId) + m := g.DB(consts.DbGroupFinance).Model(consts.TableNamePaymentOrder).Ctx(ctx).Where("user_id", userId) if pageSize == -1 { r, err := m.OrderDesc("id").All() if err != nil { diff --git a/shortdrama/dao/region_pricing_dao.go b/shortdrama/dao/region_pricing_dao.go index 87f9110..39e7dc4 100644 --- a/shortdrama/dao/region_pricing_dao.go +++ b/shortdrama/dao/region_pricing_dao.go @@ -19,7 +19,7 @@ type regionPricingDao struct{} func init() { ctx := context.Background() - if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameRegionPricing+` ( + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameRegionPricing+` ( id INTEGER PRIMARY KEY AUTOINCREMENT, province TEXT NOT NULL DEFAULT '', region TEXT NOT NULL DEFAULT '', @@ -31,33 +31,33 @@ func init() { )`); err != nil { g.Log().Warningf(ctx, "create region_pricing table failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameRegionPricing+" ADD COLUMN province TEXT NOT NULL DEFAULT ''"); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "ALTER TABLE "+consts.TableNameRegionPricing+" ADD COLUMN province TEXT NOT NULL DEFAULT ''"); err != nil { g.Log().Warningf(ctx, "add column province failed: %v", err) } // add max_customers column for existing databases - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameRegionPricing+" ADD COLUMN max_customers INTEGER NOT NULL DEFAULT 0"); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "ALTER TABLE "+consts.TableNameRegionPricing+" ADD COLUMN max_customers INTEGER NOT NULL DEFAULT 0"); err != nil { g.Log().Warningf(ctx, "add column max_customers failed: %v", err) } // drop old indexes, recreate with new constraints - if _, err := g.DB().Exec(ctx, `DROP INDEX IF EXISTS idx_region_pricing_uniq`); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `DROP INDEX IF EXISTS idx_region_pricing_uniq`); err != nil { g.Log().Warningf(ctx, "drop index idx_region_pricing_uniq failed: %v", err) } - if _, err := g.DB().Exec(ctx, `DROP INDEX IF EXISTS idx_rp_price`); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `DROP INDEX IF EXISTS idx_rp_price`); err != nil { g.Log().Warningf(ctx, "drop index idx_rp_price failed: %v", err) } - if _, err := g.DB().Exec(ctx, `DROP INDEX IF EXISTS idx_rp_max_customers`); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `DROP INDEX IF EXISTS idx_rp_max_customers`); err != nil { g.Log().Warningf(ctx, "drop index idx_rp_max_customers failed: %v", err) } - if _, err := g.DB().Exec(ctx, `DROP INDEX IF EXISTS idx_rp_single_protected`); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `DROP INDEX IF EXISTS idx_rp_single_protected`); err != nil { g.Log().Warningf(ctx, "drop index idx_rp_single_protected failed: %v", err) } - if _, err := g.DB().Exec(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_rp_price ON `+consts.TableNameRegionPricing+`(region, protected, price)`); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_rp_price ON `+consts.TableNameRegionPricing+`(region, protected, price)`); err != nil { g.Log().Warningf(ctx, "create idx_rp_price failed: %v", err) } - if _, err := g.DB().Exec(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_rp_max_customers ON `+consts.TableNameRegionPricing+`(region, protected, max_customers)`); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_rp_max_customers ON `+consts.TableNameRegionPricing+`(region, protected, max_customers)`); err != nil { g.Log().Warningf(ctx, "create idx_rp_max_customers failed: %v", err) } - if _, err := g.DB().Exec(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_rp_single_protected ON `+consts.TableNameRegionPricing+`(region) WHERE protected=1`); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_rp_single_protected ON `+consts.TableNameRegionPricing+`(region) WHERE protected=1`); err != nil { g.Log().Warningf(ctx, "create idx_rp_single_protected failed: %v", err) } } @@ -72,7 +72,7 @@ func clearRegionPricingCache(ctx context.Context) { } func (d *regionPricingDao) ListPage(ctx context.Context, page, pageSize int, keyword string, protected int) (res []*entity.RegionPricing, total int, err error) { - m := g.DB().Model(consts.TableNameRegionPricing).Ctx(ctx) + m := g.DB(consts.DbGroupSystem).Model(consts.TableNameRegionPricing).Ctx(ctx) if keyword != "" { m = m.Where("(region LIKE ? OR province LIKE ?)", "%"+keyword+"%", "%"+keyword+"%") } @@ -119,7 +119,7 @@ func (d *regionPricingDao) ListPage(ctx context.Context, page, pageSize int, key func (d *regionPricingDao) List(ctx context.Context) ([]*entity.RegionPricing, error) { var list []*entity.RegionPricing - err := g.DB().Model(consts.TableNameRegionPricing).Ctx(ctx). + err := g.DB(consts.DbGroupSystem).Model(consts.TableNameRegionPricing).Ctx(ctx). Cache(gdb.CacheOption{Duration: time.Minute, Name: "region_pricing_List"}). OrderAsc("region, protected").Scan(&list) return list, err @@ -127,7 +127,7 @@ func (d *regionPricingDao) List(ctx context.Context) ([]*entity.RegionPricing, e func (d *regionPricingDao) GetOne(ctx context.Context, region string, protected int, maxCustomers int) (*entity.RegionPricing, error) { var r entity.RegionPricing - err := g.DB().Model(consts.TableNameRegionPricing).Ctx(ctx). + err := g.DB(consts.DbGroupSystem).Model(consts.TableNameRegionPricing).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "regionPricing_GetOne_" + region + "_" + gconv.String(protected) + "_" + gconv.String(maxCustomers)}). Where("region", region).Where("protected", protected).Where("max_customers", maxCustomers).Scan(&r) if err != nil || r.Id == 0 { @@ -138,7 +138,7 @@ func (d *regionPricingDao) GetOne(ctx context.Context, region string, protected func (d *regionPricingDao) GetByRegion(ctx context.Context, region string, protected int) (*entity.RegionPricing, error) { var r entity.RegionPricing - err := g.DB().Model(consts.TableNameRegionPricing).Ctx(ctx). + err := g.DB(consts.DbGroupSystem).Model(consts.TableNameRegionPricing).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "regionPricing_GetByRegion_" + region + "_" + gconv.String(protected)}). Where("region", region).Where("protected", protected).Scan(&r) if err != nil || r.Id == 0 { @@ -149,7 +149,7 @@ func (d *regionPricingDao) GetByRegion(ctx context.Context, region string, prote func (d *regionPricingDao) GetById(ctx context.Context, id int64) (*entity.RegionPricing, error) { var r entity.RegionPricing - err := g.DB().Model(consts.TableNameRegionPricing).Ctx(ctx). + err := g.DB(consts.DbGroupSystem).Model(consts.TableNameRegionPricing).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "regionPricing_GetById_" + gconv.String(id)}). Where("id", id).Scan(&r) if err != nil || r.Id == 0 { @@ -160,13 +160,13 @@ func (d *regionPricingDao) GetById(ctx context.Context, id int64) (*entity.Regio func (d *regionPricingDao) Save(ctx context.Context, data *entity.RegionPricing) error { if data.Id > 0 { - _, err := g.DB().Exec(ctx, + _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "UPDATE "+consts.TableNameRegionPricing+" SET province=?, region=?, protected=?, price=?, max_customers=?, updated_at=datetime('now','localtime') WHERE id=?", data.Province, data.Region, data.Protected, data.Price, data.MaxCustomers, data.Id) clearRegionPricingCache(ctx) return err } - _, err := g.DB().Exec(ctx, + _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "INSERT INTO "+consts.TableNameRegionPricing+" (province, region, protected, price, max_customers, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now','localtime'), datetime('now','localtime'))", data.Province, data.Region, data.Protected, data.Price, data.MaxCustomers) clearRegionPricingCache(ctx) @@ -174,7 +174,7 @@ func (d *regionPricingDao) Save(ctx context.Context, data *entity.RegionPricing) } func (d *regionPricingDao) ListRegions(ctx context.Context) ([]string, error) { - values, err := g.DB().Model(consts.TableNameRegionPricing).Ctx(ctx). + values, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameRegionPricing).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "regionPricing_ListRegions"}). Fields("DISTINCT region").OrderAsc("region").Array() if err != nil { @@ -188,7 +188,7 @@ func (d *regionPricingDao) ListRegions(ctx context.Context) ([]string, error) { } func (d *regionPricingDao) Delete(ctx context.Context, id int64) error { - _, err := g.DB().Model(consts.TableNameRegionPricing).Ctx(ctx).Where("id", id).Delete() + _, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameRegionPricing).Ctx(ctx).Where("id", id).Delete() clearRegionPricingCache(ctx) return err } diff --git a/shortdrama/dao/user_dao.go b/shortdrama/dao/user_dao.go index f86516e..716f2ff 100644 --- a/shortdrama/dao/user_dao.go +++ b/shortdrama/dao/user_dao.go @@ -18,7 +18,7 @@ type userDao struct{} func init() { ctx := context.Background() - _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameUser+` ( + _, err := g.DB(consts.DbGroupFinance).Exec(ctx, `CREATE TABLE IF NOT EXISTS `+consts.TableNameUser+` ( id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT NOT NULL DEFAULT 'customer', username TEXT NOT NULL DEFAULT '', @@ -33,73 +33,68 @@ func init() { if err != nil { g.Log().Warningf(ctx, "create user table failed: %v", err) } - if _, err := g.DB().Exec(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_user_username ON "+consts.TableNameUser+"(username) WHERE username != ''"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_user_username ON "+consts.TableNameUser+"(username) WHERE username != ''"); err != nil { g.Log().Warningf(ctx, "create index idx_user_username failed: %v", err) } - if _, err := g.DB().Exec(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_user_phone ON "+consts.TableNameUser+"(phone) WHERE phone != ''"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_user_phone ON "+consts.TableNameUser+"(phone) WHERE phone != ''"); err != nil { g.Log().Warningf(ctx, "create index idx_user_phone failed: %v", err) } // add columns for existing databases - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameUser+" ADD COLUMN province TEXT NOT NULL DEFAULT ''"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "ALTER TABLE "+consts.TableNameUser+" ADD COLUMN province TEXT NOT NULL DEFAULT ''"); err != nil { g.Log().Warningf(ctx, "add column province failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameUser+" ADD COLUMN address TEXT NOT NULL DEFAULT ''"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "ALTER TABLE "+consts.TableNameUser+" ADD COLUMN address TEXT NOT NULL DEFAULT ''"); err != nil { g.Log().Warningf(ctx, "add column address failed: %v", err) } // cleanup legacy columns - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameUser+" DROP COLUMN status"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "ALTER TABLE "+consts.TableNameUser+" DROP COLUMN status"); err != nil { g.Log().Warningf(ctx, "drop column status failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameUser+" DROP COLUMN expired_at"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "ALTER TABLE "+consts.TableNameUser+" DROP COLUMN expired_at"); err != nil { g.Log().Warningf(ctx, "drop column expired_at failed: %v", err) } // backfill created_at for legacy data - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNameUser+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNameUser+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { g.Log().Warningf(ctx, "backfill user created_at failed: %v", err) } - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNameAccountTransaction+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNameAccountTransaction+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { g.Log().Warningf(ctx, "backfill account_transaction created_at failed: %v", err) } - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNamePaymentOrder+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNamePaymentOrder+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { g.Log().Warningf(ctx, "backfill payment_order created_at failed: %v", err) } - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNamePaymentConfig+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { - g.Log().Warningf(ctx, "backfill payment_config created_at failed: %v", err) - } - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNamePaymentChannelTrade+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNamePaymentChannelTrade+" SET created_at = datetime('now','localtime') WHERE created_at IS NULL"); err != nil { g.Log().Warningf(ctx, "backfill payment_channel_trade created_at failed: %v", err) } // UTC to local timezone migration, run once via PRAGMA user_version var userVersion int - r, _ := g.DB().Query(ctx, "PRAGMA user_version") - if len(r) > 0 { + if r, qErr := g.DB(consts.DbGroupFinance).Query(ctx, "PRAGMA user_version"); qErr != nil { + g.Log().Warningf(ctx, "query user_version failed: %v", qErr) + } else if len(r) > 0 { userVersion = r[0]["user_version"].Int() } if userVersion == 0 { g.Log().Info(ctx, "migration: convert historical UTC time data to local timezone") - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNameUser+" SET created_at = datetime(created_at, '+8 hours'), updated_at = datetime(updated_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z'"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNameUser+" SET created_at = datetime(created_at, '+8 hours'), updated_at = datetime(updated_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z'"); err != nil { g.Log().Warningf(ctx, "migrate user UTC to local failed: %v", err) } - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNameAccountTransaction+" SET created_at = datetime(created_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z' AND created_at IS NOT NULL"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNameAccountTransaction+" SET created_at = datetime(created_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z' AND created_at IS NOT NULL"); err != nil { g.Log().Warningf(ctx, "migrate account_transaction UTC to local failed: %v", err) } - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNamePaymentOrder+" SET created_at = datetime(created_at, '+8 hours'), updated_at = datetime(updated_at, '+8 hours'), paid_at = datetime(paid_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z'"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNamePaymentOrder+" SET created_at = datetime(created_at, '+8 hours'), updated_at = datetime(updated_at, '+8 hours'), paid_at = datetime(paid_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z'"); err != nil { g.Log().Warningf(ctx, "migrate payment_order UTC to local failed: %v", err) } - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNamePaymentConfig+" SET created_at = datetime(created_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z'"); err != nil { - g.Log().Warningf(ctx, "migrate payment_config UTC to local failed: %v", err) - } - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNamePaymentChannelTrade+" SET created_at = datetime(created_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z'"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNamePaymentChannelTrade+" SET created_at = datetime(created_at, '+8 hours') WHERE created_at LIKE '%-%T%:%.%Z' OR created_at LIKE '%-%T%:%s%z'"); err != nil { g.Log().Warningf(ctx, "migrate payment_channel_trade UTC to local failed: %v", err) } - if _, err := g.DB().Exec(ctx, "PRAGMA user_version = 1"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "PRAGMA user_version = 1"); err != nil { g.Log().Warningf(ctx, "set user_version failed: %v", err) } } // 补充 province 字段到各表的 DDL(仅针对在 province 列存在前入库的老数据) - if _, err := g.DB().Exec(ctx, "UPDATE "+consts.TableNameUser+" SET province = region WHERE province = '' AND region != ''"); err != nil { + if _, err := g.DB(consts.DbGroupFinance).Exec(ctx, "UPDATE "+consts.TableNameUser+" SET province = region WHERE province = '' AND region != ''"); err != nil { g.Log().Warningf(ctx, "backfill province from region failed: %v", err) } } @@ -113,7 +108,7 @@ func clearUserCache(ctx context.Context, id int64) { } func (d *userDao) Insert(ctx context.Context, data *entity.User) (int64, error) { - r, err := g.DB().Exec(ctx, + r, err := g.DB(consts.DbGroupFinance).Exec(ctx, "INSERT INTO "+consts.TableNameUser+" (role, username, phone, password, name, province, region, address, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now','localtime'), datetime('now','localtime'))", data.Role, data.Username, data.Phone, data.Password, data.Name, data.Province, data.Region, data.Address) if err != nil { @@ -124,7 +119,7 @@ func (d *userDao) Insert(ctx context.Context, data *entity.User) (int64, error) func (d *userDao) GetOne(ctx context.Context, id int64) (*entity.User, error) { var u entity.User - err := g.DB().Model(consts.TableNameUser).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "user_GetOne_" + gconv.String(id)}). Where("id", id).Scan(&u) if err != nil { @@ -138,7 +133,7 @@ func (d *userDao) GetOne(ctx context.Context, id int64) (*entity.User, error) { func (d *userDao) GetByUsername(ctx context.Context, username string) (*entity.User, error) { var u entity.User - err := g.DB().Model(consts.TableNameUser).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "user_GetByUsername_" + username}). Where("username", username).Scan(&u) if err != nil { @@ -152,7 +147,7 @@ func (d *userDao) GetByUsername(ctx context.Context, username string) (*entity.U func (d *userDao) GetByAccount(ctx context.Context, account string) (*entity.User, error) { var u entity.User - err := g.DB().Model(consts.TableNameUser).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "user_GetByAccount_" + account}). Where("username = ? OR phone = ?", account, account).Scan(&u) if err != nil { @@ -166,7 +161,7 @@ func (d *userDao) GetByAccount(ctx context.Context, account string) (*entity.Use func (d *userDao) GetByPhone(ctx context.Context, phone string) (*entity.User, error) { var u entity.User - err := g.DB().Model(consts.TableNameUser).Ctx(ctx). + err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "user_GetByPhone_" + phone}). Where("phone", phone).Scan(&u) if err != nil || u.Id == 0 { @@ -176,47 +171,60 @@ func (d *userDao) GetByPhone(ctx context.Context, phone string) (*entity.User, e } func (d *userDao) Update(ctx context.Context, data *entity.User) error { - _, err := g.DB().Model(consts.TableNameUser).Ctx(ctx).Data(data).Where("id", data.Id).Update() + _, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx).Data(data).Where("id", data.Id).Update() clearUserCache(ctx, data.Id) return err } func (d *userDao) UpdateFields(ctx context.Context, id int64, data g.Map) error { - _, err := g.DB().Model(consts.TableNameUser).Ctx(ctx).Data(data).Where("id", id).Update() + _, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx).Data(data).Where("id", id).Update() clearUserCache(ctx, id) return err } func (d *userDao) ListByRole(ctx context.Context, role string, page, pageSize int) ([]*entity.User, int, error) { cacheKey := "user_ListByRole_" + role - total, err := g.DB().Model(consts.TableNameUser).Ctx(ctx).Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey + "_count"}).Where("role", role).Count() + total, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx).Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey + "_count"}).Where("role", role).Count() if err != nil { return nil, 0, err } var users []*entity.User - err = g.DB().Model(consts.TableNameUser).Ctx(ctx).Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}).Where("role", role).Page(page, pageSize).OrderAsc("id").Scan(&users) + err = g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx).Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}).Where("role", role).Page(page, pageSize).OrderAsc("id").Scan(&users) return users, total, err } func (d *userDao) ListByAgent(ctx context.Context, agentId int64, page, pageSize int) ([]*entity.User, int, error) { + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } cacheKey := "user_ListByAgent_" + gconv.String(agentId) - // 查询指定代理商名下的客户 - total, err := g.DB().Model(consts.TableNameUser+" u"). - InnerJoin(consts.TableNameCustomerProfile+" cp", "cp.user_id = u.id"). - Where("cp.agent_id", agentId). + // 单表查询:客户档案表统计并分页取 user_id(customer_profile 主键即 user_id,顺序与 user 表一致) + total, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameCustomerProfile).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey + "_count"}). - Count() + Where("agent_id", agentId).Count() if err != nil { return nil, 0, err } - var users []*entity.User - err = g.DB().Model(consts.TableNameUser+" u"). - InnerJoin(consts.TableNameCustomerProfile+" cp", "cp.user_id = u.id"). - Where("cp.agent_id", agentId). + r, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameCustomerProfile).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}). - Fields("u.*"). - Page(page, pageSize). - OrderAsc("u.id"). - Scan(&users) + Fields("user_id").Where("agent_id", agentId). + OrderAsc("user_id").Limit(pageSize).Offset((page - 1) * pageSize).Array() + if err != nil { + return nil, 0, err + } + ids := make([]int64, 0, len(r)) + for _, v := range r { + ids = append(ids, v.Int64()) + } + if len(ids) == 0 { + return make([]*entity.User, 0), total, nil + } + // 批量查询用户信息 + var users []*entity.User + err = g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx). + WhereIn("id", ids).OrderAsc("id").Scan(&users) return users, total, err } diff --git a/shortdrama/dao/user_model_config_dao.go b/shortdrama/dao/user_model_config_dao.go index 55cff19..c8029cd 100644 --- a/shortdrama/dao/user_model_config_dao.go +++ b/shortdrama/dao/user_model_config_dao.go @@ -24,14 +24,14 @@ func init() { ctx := context.Background() // 检测表是否已存在 var tableExists bool - if r, err := g.DB().Exec(ctx, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='"+consts.TableNameUserModelConfig+"'"); err != nil { + if r, err := g.DB(consts.DbGroupSystem).Exec(ctx, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='"+consts.TableNameUserModelConfig+"'"); err != nil { g.Log().Warningf(ctx, "check user_model_config table existence failed: %v", err) } else if r != nil { tableExists = true } if !tableExists { - _, err := g.DB().Exec(ctx, `CREATE TABLE `+consts.TableNameUserModelConfig+` ( + _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `CREATE TABLE `+consts.TableNameUserModelConfig+` ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL DEFAULT 0, model_config_id INTEGER NOT NULL DEFAULT 0, @@ -53,7 +53,7 @@ func init() { } // 表已存在,检测是否包含旧列(base_url),有则迁移到不含 base_url/task_callback_url 的新表 - r, err := g.DB().GetAll(ctx, "PRAGMA table_info("+consts.TableNameUserModelConfig+")") + r, err := g.DB(consts.DbGroupSystem).GetAll(ctx, "PRAGMA table_info("+consts.TableNameUserModelConfig+")") var hasBaseUrl bool if err != nil { g.Log().Warningf(ctx, "check user_model_config table columns failed: %v", err) @@ -68,7 +68,7 @@ func init() { g.Log().Info(ctx, "检测到 user_model_config 表含旧列(base_url),开始迁移到新结构...") newTable := consts.TableNameUserModelConfig + "_new" - if _, err := g.DB().Exec(ctx, `CREATE TABLE `+newTable+` ( + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `CREATE TABLE `+newTable+` ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL DEFAULT 0, model_config_id INTEGER NOT NULL DEFAULT 0, @@ -88,7 +88,7 @@ func init() { } // 复制数据:旧表的 endpoint_url/interface_path/callback_path 已存在则原样保留,否则从 base_url 填充 - if _, err := g.DB().Exec(ctx, + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `INSERT INTO `+newTable+`(user_id,model_config_id,model_type,api_key,endpoint_url,interface_path,callback_path,temperature,max_tokens,concurrency_count,created_at,updated_at) SELECT uc.user_id,uc.model_config_id, COALESCE(mc.model_type,''), @@ -106,10 +106,10 @@ func init() { g.Log().Error(ctx, "迁移数据失败:", err) } - if _, err := g.DB().Exec(ctx, "DROP TABLE "+consts.TableNameUserModelConfig); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "DROP TABLE "+consts.TableNameUserModelConfig); err != nil { g.Log().Warningf(ctx, "drop old table failed: %v", err) } - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+newTable+" RENAME TO "+consts.TableNameUserModelConfig); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "ALTER TABLE "+newTable+" RENAME TO "+consts.TableNameUserModelConfig); err != nil { g.Log().Error(ctx, "重命名表失败:", err) return } @@ -117,7 +117,7 @@ func init() { } // 检测是否缺少 concurrency_count 列(已有新表结构但字段不全) - if r, err := g.DB().GetAll(ctx, "PRAGMA table_info("+consts.TableNameUserModelConfig+")"); err != nil { + if r, err := g.DB(consts.DbGroupSystem).GetAll(ctx, "PRAGMA table_info("+consts.TableNameUserModelConfig+")"); err != nil { g.Log().Warningf(ctx, "check user_model_config concurrency_count column failed: %v", err) } else if r != nil { hasCol := false @@ -129,7 +129,7 @@ func init() { } if !hasCol { g.Log().Info(ctx, "检测到 user_model_config 缺少 concurrency_count 列,正在补充...") - if _, err := g.DB().Exec(ctx, "ALTER TABLE "+consts.TableNameUserModelConfig+" ADD COLUMN concurrency_count INTEGER NOT NULL DEFAULT 0"); err != nil { + if _, err := g.DB(consts.DbGroupSystem).Exec(ctx, "ALTER TABLE "+consts.TableNameUserModelConfig+" ADD COLUMN concurrency_count INTEGER NOT NULL DEFAULT 0"); err != nil { g.Log().Warningf(ctx, "add concurrency_count column failed: %v", err) } else { g.Log().Info(ctx, "concurrency_count 列补充完成") @@ -140,7 +140,7 @@ func init() { // GetByUserId 获取指定用户所有模型配置(降序排列) func (d *userModelConfigDao) GetByUserId(ctx context.Context, userId int64) (res []*entity.UserModelConfig, err error) { - r, err := g.DB().Model(consts.TableNameUserModelConfig).Ctx(ctx). + r, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameUserModelConfig).Ctx(ctx). Cache(gdb.CacheOption{Duration: time.Minute, Name: fmt.Sprintf("user_model_config_GetByUserId_%d", userId)}). Where("user_id", userId).OrderDesc("id").All() if err != nil { @@ -156,7 +156,7 @@ func (d *userModelConfigDao) GetByUserId(ctx context.Context, userId int64) (res // GetByModelConfigId 获取指定用户对指定系统模型的配置 func (d *userModelConfigDao) GetByModelConfigId(ctx context.Context, userId, modelConfigId int64) (res *entity.UserModelConfig, err error) { - r, err := g.DB().Model(consts.TableNameUserModelConfig).Ctx(ctx). + r, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameUserModelConfig).Ctx(ctx). Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: fmt.Sprintf("user_model_config_GetByModelConfigId_%d_%d", userId, modelConfigId)}). Where("user_id", userId).Where("model_config_id", modelConfigId).Limit(1).One() if err != nil { @@ -172,7 +172,7 @@ func (d *userModelConfigDao) GetByModelConfigId(ctx context.Context, userId, mod // GetByModelType 获取指定用户对指定模型类型的配置 func (d *userModelConfigDao) GetByModelType(ctx context.Context, userId int64, modelType string) (res *entity.UserModelConfig, err error) { - r, err := g.DB().Model(consts.TableNameUserModelConfig).Ctx(ctx). + r, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameUserModelConfig).Ctx(ctx). Cache(gdb.CacheOption{Duration: time.Minute, Name: fmt.Sprintf("user_model_config_GetByModelType_%d_%s", userId, modelType)}). Where("user_id", userId). Where("model_type", modelType). @@ -191,22 +191,28 @@ func (d *userModelConfigDao) GetByModelType(ctx context.Context, userId int64, m // GetFirstWithApiKeyByModelType 获取指定模型类型第一个有 API Key 的用户配置 func (d *userModelConfigDao) GetFirstWithApiKeyByModelType(ctx context.Context, modelType string) *entity.UserModelConfig { - r, err := g.DB().Model(consts.TableNameUserModelConfig).Ctx(ctx). + r, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameUserModelConfig).Ctx(ctx). Where("model_type", modelType). Where("api_key != ''"). Limit(1). One() - if err != nil || r == nil { + if err != nil { + g.Log().Warningf(ctx, "query user model config by type %s failed: %v", modelType, err) + return nil + } + if r == nil { return nil } res := new(entity.UserModelConfig) - _ = r.Struct(&res) + if err := r.Struct(&res); err != nil { + g.Log().Warningf(ctx, "parse user model config row failed: %v", err) + } return res } // DeleteSameType 删除同用户同模型类型的其他配置(保留当前配置) func (d *userModelConfigDao) DeleteSameType(ctx context.Context, userId int64, modelType string, excludeId int64) error { - _, err := g.DB().Exec(ctx, + _, err := g.DB(consts.DbGroupSystem).Exec(ctx, `DELETE FROM `+consts.TableNameUserModelConfig+` WHERE user_id=? AND id!=? AND model_type=?`, userId, excludeId, modelType) @@ -221,12 +227,12 @@ func (d *userModelConfigDao) Save(ctx context.Context, data *entity.UserModelCon now := gtime.Now().Format("Y-m-d H:i:s") m["updated_at"] = now if data.Id > 0 { - _, err := g.DB().Model(consts.TableNameUserModelConfig).Ctx(ctx).Data(m).Where("id", data.Id).Update() + _, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameUserModelConfig).Ctx(ctx).Data(m).Where("id", data.Id).Update() clearUserModelConfigCache(ctx, data.UserId, data.ModelType, data.ModelConfigId) return err } m["created_at"] = now - lid, err := g.DB().Model(consts.TableNameUserModelConfig).Ctx(ctx).Data(m).InsertAndGetId() + lid, err := g.DB(consts.DbGroupSystem).Model(consts.TableNameUserModelConfig).Ctx(ctx).Data(m).InsertAndGetId() if err == nil && lid > 0 { data.Id = lid } @@ -238,7 +244,7 @@ func (d *userModelConfigDao) Save(ctx context.Context, data *entity.UserModelCon // SaveBatch 批量保存用户模型配置(事务内执行) func (d *userModelConfigDao) SaveBatch(ctx context.Context, items []*entity.UserModelConfig) error { - err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + err := g.DB(consts.DbGroupSystem).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { for _, data := range items { m := gconv.Map(data, gconv.MapOption{Tags: []string{"orm"}}) delete(m, "id") diff --git a/shortdrama/model/agent_output.go b/shortdrama/model/agent_output.go index 589a412..d1057e8 100644 --- a/shortdrama/model/agent_output.go +++ b/shortdrama/model/agent_output.go @@ -2,8 +2,11 @@ package model import ( "bytes" + "context" "encoding/json" "strings" + + "github.com/gogf/gf/v2/frame/g" ) // AgentOutput Agent 最终输出的 JSON 结构 @@ -91,13 +94,17 @@ func ParseAgentOutput(jsonStr string, segIdx int) *SegmentOutput { if err := json.Unmarshal([]byte(extracted), &agentOut); err != nil { // JSON 解析失败时尝试修复常见错误(如对话中的未转义引号) if fixed := repairJSON(extracted); fixed != extracted { - _ = json.Unmarshal([]byte(fixed), &agentOut) + if fErr := json.Unmarshal([]byte(fixed), &agentOut); fErr != nil { + g.Log().Warningf(context.Background(), "parse agent output failed after repair: %v", fErr) + } } else if len(extracted) > 0 && extracted[0] == '{' { // 尝试修复整个 textOutput 中的 JSON fixed2 := repairJSON(jsonStr) extracted2 := extractJSON(fixed2) if extracted2 != jsonStr { - _ = json.Unmarshal([]byte(extracted2), &agentOut) + if fErr := json.Unmarshal([]byte(extracted2), &agentOut); fErr != nil { + g.Log().Warningf(context.Background(), "parse agent output failed after full repair: %v", fErr) + } } } if agentOut.Title == "" { @@ -179,7 +186,9 @@ func ExtractAgentImages(jsonStr string) *AgentExtractedImages { var agentOut AgentOutput if err := json.Unmarshal([]byte(extracted), &agentOut); err != nil { if fixed := repairJSON(extracted); fixed != extracted { - _ = json.Unmarshal([]byte(fixed), &agentOut) + if fErr := json.Unmarshal([]byte(fixed), &agentOut); fErr != nil { + g.Log().Warningf(context.Background(), "extract agent images failed after repair: %v", fErr) + } } } if agentOut.Title == "" { diff --git a/shortdrama/service/agent_service.go b/shortdrama/service/agent_service.go index bf1cc03..fd0bb73 100644 --- a/shortdrama/service/agent_service.go +++ b/shortdrama/service/agent_service.go @@ -81,12 +81,18 @@ func (s *agentService) ExtractRegionFromAddress(ctx context.Context, address str // CreateAgent 创建代理商 func (s *agentService) CreateAgent(ctx context.Context, username, password, phone, name, province, region string, regionProtected bool) (*entity.User, error) { - existing, _ := dao.User.GetByUsername(ctx, username) + existing, err := dao.User.GetByUsername(ctx, username) + if err != nil { + return nil, err + } if existing != nil { return nil, errors.New("username already exists") } if phone != "" { - existing, _ = dao.User.GetByPhone(ctx, phone) + existing, err = dao.User.GetByPhone(ctx, phone) + if err != nil { + return nil, err + } if existing != nil { return nil, errors.New("phone already in use") } @@ -98,13 +104,45 @@ func (s *agentService) CreateAgent(ctx context.Context, username, password, phon RegionProtected int `orm:"region_protected"` } var activeAgents []*agentRow - _ = g.DB().Model(consts.TableNameUser+" u"). - InnerJoin(consts.TableNameAgentProfile+" ap", "ap.user_id = u.id"). - Where("u.role", "agent"). - Where("u.region", region). - Where("(ap.expired_at IS NULL OR ap.expired_at > datetime('now','localtime'))"). - Fields("u.id", "ap.region_protected"). - Scan(&activeAgents) + // 拆库后改为单表查询 + 内存合并:先查未过期代理商的 user_id + var activeIds []int64 + if apRows, err := g.DB(consts.DbGroupFinance).Model(consts.TableNameAgentProfile).Ctx(ctx). + Fields("user_id"). + Where("(expired_at IS NULL OR expired_at > datetime('now','localtime'))"). + All(); err == nil { + for _, row := range apRows { + activeIds = append(activeIds, row["user_id"].Int64()) + } + } + // 再查该区域的代理商 user_id + var regionAgentIds []int64 + if len(activeIds) > 0 { + var agentUsers []*entity.User + if err := g.DB(consts.DbGroupFinance).Model(consts.TableNameUser).Ctx(ctx). + Fields("id"). + Where("role", "agent"). + Where("region", region). + WhereIn("id", activeIds). + Scan(&agentUsers); err == nil { + for _, u := range agentUsers { + regionAgentIds = append(regionAgentIds, u.Id) + } + } + } + // 最后批量查 profile 组装 region_protected + if len(regionAgentIds) > 0 { + var profiles []*entity.AgentProfile + if err := g.DB(consts.DbGroupFinance).Model(consts.TableNameAgentProfile).Ctx(ctx). + WhereIn("user_id", regionAgentIds).Scan(&profiles); err == nil { + for _, ap := range profiles { + protected := 0 + if ap.RegionProtected { + protected = 1 + } + activeAgents = append(activeAgents, &agentRow{Id: ap.UserId, RegionProtected: protected}) + } + } + } if regionProtected { if len(activeAgents) > 0 { @@ -123,12 +161,18 @@ func (s *agentService) CreateAgent(ctx context.Context, username, password, phon if regionProtected { protectedVal = 1 } - pricing, _ := dao.RegionPricing.GetByRegion(ctx, region, protectedVal) - if pricing == nil { - return nil, errors.New("region pricing not configured") + pricing, err := dao.RegionPricing.GetByRegion(ctx, region, protectedVal) + if err != nil || pricing == nil { + if err == nil { + err = errors.New("region pricing not configured") + } + return nil, err } - hash, _ := bcryptGenerate(password) + hash, err := bcryptGenerate(password) + if err != nil { + return nil, err + } user := &entity.User{ Role: "agent", Username: username, @@ -139,7 +183,7 @@ func (s *agentService) CreateAgent(ctx context.Context, username, password, phon Region: region, } - if err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + if err := g.DB(consts.DbGroupFinance).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { userId, e := dao.User.Insert(ctx, user) if e != nil { return e @@ -236,19 +280,27 @@ func (s *agentService) UpdateAgent(ctx context.Context, req *dto.UpdateAgentReq) } } - profile, _ := dao.AgentProfile.Get(ctx, req.Id) + profile, err := dao.AgentProfile.Get(ctx, req.Id) + if err != nil { + return err + } if profile == nil { maxCustomers := 0 - if pricing, _ := dao.RegionPricing.GetByRegion(ctx, req.Region, 0); pricing != nil { + pricing, pErr := dao.RegionPricing.GetByRegion(ctx, req.Region, 0) + if pErr != nil { + g.Log().Warningf(ctx, "get region pricing for agent %d failed: %v", req.Id, pErr) + } else if pricing != nil { maxCustomers = pricing.MaxCustomers } - _ = dao.AgentProfile.Save(ctx, &entity.AgentProfile{ + if err := dao.AgentProfile.Save(ctx, &entity.AgentProfile{ UserId: req.Id, MaxCustomers: maxCustomers, Renewals: 0, ExpiredAt: gtime.Now().AddDate(1, 0, 0), RegionProtected: false, - }) + }); err != nil { + return err + } } return nil } diff --git a/shortdrama/service/customer_service.go b/shortdrama/service/customer_service.go index 97fc05a..669c33c 100644 --- a/shortdrama/service/customer_service.go +++ b/shortdrama/service/customer_service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "video-factory/shortdrama/consts" "video-factory/shortdrama/dao" "video-factory/shortdrama/model/dto" @@ -19,7 +20,10 @@ type customerService struct{} var CustomerService = new(customerService) func (s *customerService) GetBalance(ctx context.Context, userId int64) int64 { - cp, _ := dao.CustomerProfile.Get(ctx, userId) + cp, err := dao.CustomerProfile.Get(ctx, userId) + if err != nil { + g.Log().Warningf(ctx, "get customer profile %d failed: %v", userId, err) + } if cp == nil { return 0 } @@ -33,23 +37,35 @@ func (s *customerService) CreateCustomer(ctx context.Context, phone, name, addre } agent, err := dao.User.GetOne(ctx, agentId) - if err != nil || agent == nil || agent.Role != "agent" { + if err != nil { + return nil, err + } + if agent == nil || agent.Role != "agent" { return nil, errors.New("agent not found") } - ap, _ := dao.AgentProfile.Get(ctx, agentId) - if ap != nil && ap.ExpiredAt != nil && ap.ExpiredAt.Before(gtime.Now()) { - return nil, errors.New("agent has expired") + ap, err := dao.AgentProfile.Get(ctx, agentId) + if err != nil { + return nil, err } - - ap, _ = dao.AgentProfile.Get(ctx, agentId) - if ap != nil && ap.MaxCustomers > 0 { - count, _ := dao.CustomerProfile.CountByAgent(ctx, agentId) - if count >= ap.MaxCustomers { - return nil, fmt.Errorf("max customer limit reached (%d)", ap.MaxCustomers) + if ap != nil { + if ap.ExpiredAt != nil && ap.ExpiredAt.Before(gtime.Now()) { + return nil, errors.New("agent has expired") + } + if ap.MaxCustomers > 0 { + count, err := dao.CustomerProfile.CountByAgent(ctx, agentId) + if err != nil { + return nil, err + } + if count >= ap.MaxCustomers { + return nil, fmt.Errorf("max customer limit reached (%d)", ap.MaxCustomers) + } } } - existing, _ := dao.User.GetByPhone(ctx, phone) + existing, err := dao.User.GetByPhone(ctx, phone) + if err != nil { + return nil, err + } if existing != nil { return nil, errors.New("phone number already registered") } @@ -58,7 +74,10 @@ func (s *customerService) CreateCustomer(ctx context.Context, phone, name, addre if len(defaultPwd) > 6 { defaultPwd = defaultPwd[len(defaultPwd)-6:] } - hash, _ := bcryptGenerate(defaultPwd) + hash, err := bcryptGenerate(defaultPwd) + if err != nil { + return nil, err + } user := &entity.User{ Role: "customer", @@ -70,7 +89,7 @@ func (s *customerService) CreateCustomer(ctx context.Context, phone, name, addre Address: address, } - err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + err = g.DB(consts.DbGroupFinance).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { userId, e := dao.User.Insert(ctx, user) if e != nil { return e @@ -87,7 +106,10 @@ func (s *customerService) CreateCustomer(ctx context.Context, phone, name, addre // CheckBalance 校验余额是否充足,返回 (是否充足, 预估费用(分)) func (s *customerService) CheckBalance(ctx context.Context, customerId int64, durationSec int64) (bool, int64, error) { - cp, _ := dao.CustomerProfile.Get(ctx, customerId) + cp, err := dao.CustomerProfile.Get(ctx, customerId) + if err != nil { + return false, 0, err + } if cp == nil { return false, 0, errors.New("customer not found") } @@ -96,29 +118,34 @@ func (s *customerService) CheckBalance(ctx context.Context, customerId int64, du return cp.Balance >= cost, cost, nil } -// DeductBalance 扣费 +// DeductBalance 扣费:余额扣减与流水写入放在同一事务内,保证原子性 func (s *customerService) DeductBalance(ctx context.Context, customerId int64, amount int64, remark, createdBy string) error { - cp, err := dao.CustomerProfile.Get(ctx, customerId) - if err != nil || cp == nil { - return errors.New("customer not found") - } - if cp.Balance < amount { - return errors.New("insufficient balance") - } - newBalance := cp.Balance - amount - if err := dao.CustomerProfile.UpdateBalance(ctx, customerId, newBalance); err != nil { + return g.DB(consts.DbGroupFinance).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + cp, err := dao.CustomerProfile.Get(ctx, customerId) + if err != nil { + return err + } + if cp == nil { + return errors.New("customer not found") + } + if cp.Balance < amount { + return errors.New("insufficient balance") + } + newBalance := cp.Balance - amount + if err := dao.CustomerProfile.UpdateBalance(ctx, customerId, newBalance); err != nil { + return err + } + _, err = dao.AccountTransaction.Insert(ctx, &entity.AccountTransaction{ + UserId: customerId, + Type: "deduct", + Amount: amount, + BalanceBefore: cp.Balance, + BalanceAfter: newBalance, + Remark: remark, + CreatedBy: createdBy, + }) return err - } - _, err = dao.AccountTransaction.Insert(ctx, &entity.AccountTransaction{ - UserId: customerId, - Type: "deduct", - Amount: amount, - BalanceBefore: cp.Balance, - BalanceAfter: newBalance, - Remark: remark, - CreatedBy: createdBy, }) - return err } // ListCustomers 分页查询客户列表 @@ -155,14 +182,21 @@ func (s *customerService) GetCustomerDetail(ctx context.Context, id int64) (*dto if err != nil || user == nil { return nil, err } - profile, _ := dao.CustomerProfile.Get(ctx, id) + profile, err := dao.CustomerProfile.Get(ctx, id) + if err != nil { + g.Log().Warningf(ctx, "get customer profile %d failed: %v", id, err) + } balance := s.GetBalance(ctx, id) return &dto.CustomerDetail{User: user, Profile: profile, Balance: balance}, nil } // UpdateCustomer 更新客户信息 func (s *customerService) UpdateCustomer(ctx context.Context, agentId int64, req *dto.UpdateCustomerReq) error { - if ap, _ := dao.AgentProfile.Get(ctx, agentId); ap != nil && ap.ExpiredAt != nil && ap.ExpiredAt.Before(gtime.Now()) { + ap, err := dao.AgentProfile.Get(ctx, agentId) + if err != nil { + return err + } + if ap != nil && ap.ExpiredAt != nil && ap.ExpiredAt.Before(gtime.Now()) { return errors.New("agent has expired, cannot edit customer") } diff --git a/shortdrama/service/drama_service.go b/shortdrama/service/drama_service.go index a5bb317..9638fd6 100644 --- a/shortdrama/service/drama_service.go +++ b/shortdrama/service/drama_service.go @@ -23,7 +23,10 @@ var DramaService = new(dramaService) // ==================== Drama CRUD ==================== func (s *dramaService) Create(ctx context.Context, title, contentType, config, aspectRatio string, episodeDuration int64, resolution string, userId int64) (int64, error) { - existing, _ := dao.Drama.GetByTitle(ctx, title) + existing, err := dao.Drama.GetByTitle(ctx, title) + if err != nil { + return 0, err + } if existing != nil { return 0, fmt.Errorf("drama title already exists: %s", title) } @@ -102,7 +105,10 @@ func (s *dramaService) Update(ctx context.Context, id int64, title, contentType, } oldTitle := d.Title if title != "" && title != oldTitle { - existing, _ := dao.Drama.GetByTitle(ctx, title) + existing, err := dao.Drama.GetByTitle(ctx, title) + if err != nil { + return err + } if existing != nil { return fmt.Errorf("drama title already exists: %s", title) } diff --git a/shortdrama/service/episode_service.go b/shortdrama/service/episode_service.go index a77d9a4..7aa65a4 100644 --- a/shortdrama/service/episode_service.go +++ b/shortdrama/service/episode_service.go @@ -185,7 +185,10 @@ func (s *episodeService) DeleteEpisode(ctx context.Context, dramaId, epId int64) if !e.IsDir() { name := e.Name() if strings.HasPrefix(name, segPrefix) || name == safeEp+".mp4" { - _ = os.Remove(filepath.Join(videoDir, name)) + fp := filepath.Join(videoDir, name) + if rmErr := os.Remove(fp); rmErr != nil && !os.IsNotExist(rmErr) { + g.Log().Warningf(ctx, "remove old video file %s failed: %v", fp, rmErr) + } } } } @@ -872,7 +875,10 @@ func createPendingTasks(ctx context.Context, dramaId, epId int64, script string, // 只包含已有真实文件的实体(PortraitPath/ImagePath 非空), // 全部加载(不设上限),上限在按段过滤后施加。 loadEntityRefs := func() []*entity.Character { - chars, _, _ := dao.Character.ListPageByDrama(ctx, dramaId, 1, -1) + chars, _, le := dao.Character.ListPageByDrama(ctx, dramaId, 1, -1) + if le != nil { + g.Log().Warningf(ctx, "load characters for refs failed: %v", le) + } namedRefs = nil for _, ch := range chars { if ch.PortraitPath == "" { @@ -880,14 +886,20 @@ func createPendingTasks(ctx context.Context, dramaId, epId int64, script string, } namedRefs = append(namedRefs, _namedRef{path: ch.PortraitPath, name: ch.Name}) } - scenes, _ := dao.Scene.ListByDrama(ctx, dramaId) + scenes, le := dao.Scene.ListByDrama(ctx, dramaId) + if le != nil { + g.Log().Warningf(ctx, "load scenes for refs failed: %v", le) + } for _, sc := range scenes { if sc.ImagePath == "" { continue } namedRefs = append(namedRefs, _namedRef{path: sc.ImagePath, name: sc.Name}) } - props, _ := dao.Prop.ListByDrama(ctx, dramaId) + props, le := dao.Prop.ListByDrama(ctx, dramaId) + if le != nil { + g.Log().Warningf(ctx, "load props for refs failed: %v", le) + } for _, p := range props { if p.ImagePath == "" { continue @@ -962,7 +974,9 @@ func createPendingTasks(ctx context.Context, dramaId, epId int64, script string, } // ============ 从镜头脚本提取演员/场景/道具,自动写入数据库(避免重复)============ - extractAndSaveEntities(ctx, dramaId, allShots) + if e := extractAndSaveEntities(ctx, dramaId, allShots); e != nil { + return e + } // 重新加载实体引用,确保新增记录也被纳入后续的 media/prompt 构建 chars = loadEntityRefs() @@ -1185,7 +1199,10 @@ func createPendingTasks(ctx context.Context, dramaId, epId int64, script string, } injectFirstFrame(body, "", modelCfg.SchemaMapping, modelCfg.Schema) - bodyJSON, _ := json.Marshal(body) + bodyJSON, mErr := json.Marshal(body) + if mErr != nil { + return fmt.Errorf("marshal segment %d body failed: %w", i, mErr) + } records = append(records, g.Map{ "drama_id": dramaId, @@ -1241,9 +1258,9 @@ func buildCharacterGuide(charNames []string, chars []*entity.Character, labelOf // extractAndSaveEntities 从镜头数组中提取演员/场景/道具,去重后写入数据库。 // 如果同 drama 下已存在同名记录,跳过插入(避免重复)。 -func extractAndSaveEntities(ctx context.Context, dramaId int64, shots []domain.Shot) { +func extractAndSaveEntities(ctx context.Context, dramaId int64, shots []domain.Shot) error { if len(shots) == 0 { - return + return nil } charSet := make(map[string]bool) @@ -1269,9 +1286,18 @@ func extractAndSaveEntities(ctx context.Context, dramaId int64, shots []domain.S } // 查询已有记录 - existingChars, _, _ := dao.Character.ListPageByDrama(ctx, dramaId, 1, -1) - existingScenes, _ := dao.Scene.ListByDrama(ctx, dramaId) - existingProps, _ := dao.Prop.ListByDrama(ctx, dramaId) + existingChars, _, err := dao.Character.ListPageByDrama(ctx, dramaId, 1, -1) + if err != nil { + return fmt.Errorf("list existing characters failed: %w", err) + } + existingScenes, err := dao.Scene.ListByDrama(ctx, dramaId) + if err != nil { + return fmt.Errorf("list existing scenes failed: %w", err) + } + existingProps, err := dao.Prop.ListByDrama(ctx, dramaId) + if err != nil { + return fmt.Errorf("list existing props failed: %w", err) + } existName := func(list interface{}, name string) bool { switch l := list.(type) { @@ -1299,41 +1325,61 @@ func extractAndSaveEntities(ctx context.Context, dramaId int64, shots []domain.S now := time.Now().Format("2006-01-02 15:04:05") + // 收集新增记录后批量插入(避免循环单条查询/插入) + newChars := make([]g.Map, 0, len(charSet)) for name := range charSet { if existName(existingChars, name) { continue } - _, _ = g.DB().Model(consts.TableNameCharacter).Ctx(ctx).Data(g.Map{ + newChars = append(newChars, g.Map{ "drama_id": dramaId, "name": name, "created_at": now, "updated_at": now, - }).Insert() + }) + } + if len(newChars) > 0 { + if _, e := g.DB().Model(consts.TableNameCharacter).Ctx(ctx).Data(newChars).Insert(); e != nil { + return fmt.Errorf("batch insert characters failed: %w", e) + } } + newScenes := make([]g.Map, 0, len(sceneSet)) for name := range sceneSet { if existName(existingScenes, name) { continue } - _, _ = g.DB().Model(consts.TableNameScene).Ctx(ctx).Data(g.Map{ + newScenes = append(newScenes, g.Map{ "drama_id": dramaId, "name": name, "created_at": now, "updated_at": now, - }).Insert() + }) + } + if len(newScenes) > 0 { + if _, e := g.DB().Model(consts.TableNameScene).Ctx(ctx).Data(newScenes).Insert(); e != nil { + return fmt.Errorf("batch insert scenes failed: %w", e) + } } + newProps := make([]g.Map, 0, len(propSet)) for name := range propSet { if existName(existingProps, name) { continue } - _, _ = g.DB().Model(consts.TableNameProp).Ctx(ctx).Data(g.Map{ + newProps = append(newProps, g.Map{ "drama_id": dramaId, "name": name, "created_at": now, "updated_at": now, - }).Insert() + }) } + if len(newProps) > 0 { + if _, e := g.DB().Model(consts.TableNameProp).Ctx(ctx).Data(newProps).Insert(); e != nil { + return fmt.Errorf("batch insert props failed: %w", e) + } + } + return nil } // inferCharPerSecond 根据台词情绪推断语速(字/秒) diff --git a/shortdrama/service/generation_service.go b/shortdrama/service/generation_service.go index 192d1d7..6ec1c95 100644 --- a/shortdrama/service/generation_service.go +++ b/shortdrama/service/generation_service.go @@ -76,7 +76,9 @@ func (s *generationService) GenerateEpisode(ctx context.Context, dramaId, epId i // ============ 检查待生成任务的 media 文件是否存在 ============ if !continueIfMissing { if missingNames := checkTaskMediaExistence(ctx, epId); len(missingNames) > 0 { - _ = dao.Episode.UpdateStatus(ctx, epId, consts.EpisodeStatusPending, "") + if e := dao.Episode.UpdateStatus(ctx, epId, consts.EpisodeStatusPending, ""); e != nil { + g.Log().Errorf(ctx, "reset episode %d status to pending failed: %v", epId, e) + } return errors.New("至少需要包含一个人物形象") } } else { @@ -161,8 +163,12 @@ func (s *generationService) GenerateEpisode(ctx context.Context, dramaId, epId i g.Log().Infof(genCtx, "第%d集第%d段开始串行生成(segDur=%ds))", ep.Index, i+1, segDur) if err := s.generateOneSegment(genCtx, d, ep, taskId, i, segDur, "", genCtx2); err != nil { g.Log().Errorf(genCtx, "episode %d segment %d serial generation failed: %v", ep.Index, i+1, err) - _ = dao.GenerationTask.UpdateFailed(genCtx, taskId, err.Error()) - _ = dao.Episode.UpdateStatus(ctx, epId, consts.EpisodeStatusPending, "") + if ufErr := dao.GenerationTask.UpdateFailed(genCtx, taskId, err.Error()); ufErr != nil { + g.Log().Errorf(genCtx, "mark task %d failed failed: %v", taskId, ufErr) + } + if ueErr := dao.Episode.UpdateStatus(ctx, epId, consts.EpisodeStatusPending, ""); ueErr != nil { + g.Log().Errorf(genCtx, "reset episode %d status to pending failed: %v", epId, ueErr) + } clearPollCache(genCtx, epId) return err } @@ -190,7 +196,9 @@ func (s *generationService) GenerateEpisode(ctx context.Context, dramaId, epId i if r := recover(); r != nil { errMsg := fmt.Sprintf("panic: %v", r) g.Log().Errorf(genCtx, "episode %d segment %d generation panic: %v", ep.Index, idx+1, r) - _ = dao.GenerationTask.UpdateFailed(genCtx, tid, errMsg) + if ufErr := dao.GenerationTask.UpdateFailed(genCtx, tid, errMsg); ufErr != nil { + g.Log().Errorf(genCtx, "mark task %d failed after panic failed: %v", tid, ufErr) + } clearPollCache(genCtx, epId) } <-sem @@ -200,7 +208,9 @@ func (s *generationService) GenerateEpisode(ctx context.Context, dramaId, epId i g.Log().Infof(genCtx, "第%d集第%d段开始生成(segDur=%ds)", ep.Index, idx+1, dur) if err := s.generateOneSegment(genCtx, d, ep, tid, idx, dur, "", genCtx2); err != nil { g.Log().Errorf(genCtx, "episode %d segment %d generation failed (elapsed %v): %v", ep.Index, idx+1, time.Since(startTime), err) - _ = dao.GenerationTask.UpdateFailed(genCtx, tid, err.Error()) + if ufErr := dao.GenerationTask.UpdateFailed(genCtx, tid, err.Error()); ufErr != nil { + g.Log().Errorf(genCtx, "mark task %d failed failed: %v", tid, ufErr) + } clearPollCache(genCtx, epId) } else { g.Log().Infof(genCtx, "第%d集第%d段生成完成(耗时%v)", ep.Index, idx+1, time.Since(startTime)) @@ -372,7 +382,9 @@ func (s *generationService) generateOneSegment(ctx context.Context, d *entity.Dr updateFields["video_task_id"] = taskID } updateFields["model_name"] = videoCfg.ModelName - _, _ = g.DB().Model(consts.TableNameGenerationTask).Ctx(ctx).Data(updateFields).Where("id", taskId).Update() + if _, ue := g.DB().Model(consts.TableNameGenerationTask).Ctx(ctx).Data(updateFields).Where("id", taskId).Update(); ue != nil { + g.Log().Errorf(ctx, "update task %d after video submit failed: %v", taskId, ue) + } return nil } @@ -729,7 +741,9 @@ func (s *generationService) FeedbackSegment(ctx context.Context, taskId int64, f } } // 清除 DB 中的 video_url,防止轮询器使用旧数据 - _ = dao.GenerationTask.UpdateFields(genCtx, taskId, g.Map{"video_url": "", "video_task_id": ""}) + if e := dao.GenerationTask.UpdateFields(genCtx, taskId, g.Map{"video_url": "", "video_task_id": ""}); e != nil { + g.Log().Errorf(genCtx, "clear video_url of task %d failed: %v", taskId, e) + } g.Log().Infof(genCtx, "第%d集第%d段重新提交视频(跳过Agent)", ep.Index, task.SegmentIdx+1) @@ -755,25 +769,33 @@ func (s *generationService) FeedbackSegment(ctx context.Context, taskId int64, f } // 调用模型前写入模型名称 - _ = dao.GenerationTask.UpdateFields(genCtx, taskId, g.Map{"model_name": modelCfg.ModelName}) + if e := dao.GenerationTask.UpdateFields(genCtx, taskId, g.Map{"model_name": modelCfg.ModelName}); e != nil { + g.Log().Errorf(genCtx, "write model_name to task %d failed: %v", taskId, e) + } newTaskID, resolvedBody, submitErr := resubmitVideoTask(genCtx, modelCfg.ApiKey, modelCfg.BaseUrl, modelCfg.Schema, bodyBytes, modelCfg.SchemaMapping) if submitErr != nil { g.Log().Errorf(genCtx, "episode %d segment %d video resubmit failed: %v", ep.Index, task.SegmentIdx+1, submitErr) - _ = dao.GenerationTask.UpdateFailed(genCtx, taskId, submitErr.Error()) + if ufErr := dao.GenerationTask.UpdateFailed(genCtx, taskId, submitErr.Error()); ufErr != nil { + g.Log().Errorf(genCtx, "mark task %d failed failed: %v", taskId, ufErr) + } clearPollCache(genCtx, task.EpisodeId) return } g.Log().Infof(genCtx, "第%d集第%d段重提提交成功(taskId=%s)", ep.Index, task.SegmentIdx+1, newTaskID) - _ = dao.GenerationTask.UpdateFields(genCtx, taskId, g.Map{ + if e := dao.GenerationTask.UpdateFields(genCtx, taskId, g.Map{ "video_task_id": newTaskID, "script": string(resolvedBody), "model_name": modelCfg.ModelName, "updated_at": nil, - }) + }); e != nil { + g.Log().Errorf(genCtx, "persist resubmit result to task %d failed: %v", taskId, e) + } clearPollCache(genCtx, task.EpisodeId) - _ = dao.Episode.UpdateStatus(genCtx, task.EpisodeId, consts.EpisodeStatusGenerating, "") + if e := dao.Episode.UpdateStatus(genCtx, task.EpisodeId, consts.EpisodeStatusGenerating, ""); e != nil { + g.Log().Errorf(genCtx, "mark episode %d generating failed: %v", task.EpisodeId, e) + } if ts, e := dao.GenerationTask.ListByEpisode(genCtx, task.EpisodeId); e == nil { setPollCache(genCtx, task.EpisodeId, ts) } @@ -936,7 +958,10 @@ func cleanupEpisodeWorkspace(ctx context.Context, dramaTitle string, epIndex int if !e.IsDir() { name := e.Name() if strings.HasPrefix(name, segPrefix) || name == safeEp+".mp4" || strings.HasPrefix(name, "concat_") { - _ = os.Remove(filepath.Join(videoDir, name)) + fp := filepath.Join(videoDir, name) + if rmErr := os.Remove(fp); rmErr != nil && !os.IsNotExist(rmErr) { + g.Log().Warningf(ctx, "remove old video file %s failed: %v", fp, rmErr) + } } } } @@ -1585,12 +1610,14 @@ func setPollCache(ctx context.Context, epId int64, tasks []*entity.GenerationTas if allDone { status = consts.EpisodeStatusCompleted } - _ = gcache.Set(ctx, fmt.Sprintf("episode_poll:%d", epId), &dto.EpisodePollRes{ + if e := gcache.Set(ctx, fmt.Sprintf("episode_poll:%d", epId), &dto.EpisodePollRes{ Status: status, ErrorMessage: errMsg, Tasks: tasks, CurrentTaskId: currentTaskId, - }, pollCacheTTL) + }, pollCacheTTL); e != nil { + g.Log().Warningf(ctx, "set poll cache for episode %d failed: %v", epId, e) + } } // clearPollCache 清除剧集轮询缓存 @@ -1612,7 +1639,9 @@ func (s *generationService) GetEpisodePollStatus(ctx context.Context, epId int64 emptyRes := &dto.EpisodePollRes{ Status: "pending", } - _ = gcache.Set(ctx, cacheKey, emptyRes, 5*time.Second) + if cErr := gcache.Set(ctx, cacheKey, emptyRes, 5*time.Second); cErr != nil { + g.Log().Warningf(ctx, "set empty poll cache for episode %d failed: %v", epId, cErr) + } return nil, nil } setPollCache(ctx, epId, tasks) @@ -1652,7 +1681,11 @@ func (s *generationService) StartVideoPoller(ctx context.Context) { // pollPendingVideos 扫描所有 generating 任务,按剧集分组推进(不同剧集并行,同剧集串行) func (s *generationService) pollPendingVideos(ctx context.Context) { allTasks, err := dao.GenerationTask.ListByStatuses(ctx, []string{consts.TaskStatusGenerating, consts.TaskStatusReview}) - if err != nil || len(allTasks) == 0 { + if err != nil { + g.Log().Warningf(ctx, "poller: list generating tasks failed: %v", err) + return + } + if len(allTasks) == 0 { return } @@ -1729,9 +1762,9 @@ func (s *generationService) processOneEpisode(ctx context.Context, tasks []*enti } // 加载短剧和 merged config - drama, _ := dao.Drama.GetOne(ctx, current.DramaId) - if drama == nil { - g.Log().Warningf(ctx, "poller: drama %d not found, skipping", current.DramaId) + drama, err := dao.Drama.GetOne(ctx, current.DramaId) + if err != nil || drama == nil { + g.Log().Warningf(ctx, "poller: load drama %d failed: %v", current.DramaId, err) return } modelCfg := UserModelConfigService.GetMergedConfig(ctx, drama.UserId, "video") @@ -1746,7 +1779,9 @@ func (s *generationService) processOneEpisode(ctx context.Context, tasks []*enti if err != nil { if strings.Contains(err.Error(), "FAILED") || strings.Contains(err.Error(), "failed") { g.Log().Warningf(ctx, "poller: task %d segment %d video task failed: %v", current.Id, current.SegmentIdx+1, err) - _ = dao.GenerationTask.UpdateFailed(ctx, current.Id, err.Error()) + if ufErr := dao.GenerationTask.UpdateFailed(ctx, current.Id, err.Error()); ufErr != nil { + g.Log().Errorf(ctx, "poller: mark task %d failed failed: %v", current.Id, ufErr) + } clearPollCache(ctx, current.EpisodeId) } else if strings.Contains(err.Error(), "RUNNING") { g.Log().Debugf(ctx, "轮询器: 任务 %d 第%d段视频正在生成中,继续等待...", current.Id, current.SegmentIdx+1) @@ -1770,16 +1805,24 @@ func (s *generationService) processOneEpisode(ctx context.Context, tasks []*enti fmt.Sprintf("%s_seg_%d.mp4", safeEp, current.SegmentIdx)) if dlErr != nil { g.Log().Warningf(ctx, "poller: task %d segment %d video download failed, using remote url: %v", current.Id, current.SegmentIdx+1, dlErr) - _ = dao.GenerationTask.UpdateFields(ctx, current.Id, g.Map{"video_url": videoURL, "video_task_id": ""}) + if ue := dao.GenerationTask.UpdateFields(ctx, current.Id, g.Map{"video_url": videoURL, "video_task_id": ""}); ue != nil { + g.Log().Errorf(ctx, "poller: set remote video_url of task %d failed: %v", current.Id, ue) + } } else { g.Log().Infof(ctx, "轮询器: 任务 %d 第%d段视频已下载: %s", current.Id, current.SegmentIdx+1, localPath) - _ = dao.GenerationTask.UpdateFields(ctx, current.Id, g.Map{"video_url": localPath, "video_task_id": ""}) + if ue := dao.GenerationTask.UpdateFields(ctx, current.Id, g.Map{"video_url": localPath, "video_task_id": ""}); ue != nil { + g.Log().Errorf(ctx, "poller: set local video_url of task %d failed: %v", current.Id, ue) + } + } + if ue := dao.GenerationTask.UpdateStatus(ctx, current.Id, consts.TaskStatusReview); ue != nil { + g.Log().Errorf(ctx, "poller: mark task %d review failed: %v", current.Id, ue) } - _ = dao.GenerationTask.UpdateStatus(ctx, current.Id, consts.TaskStatusReview) g.Log().Infof(ctx, "轮询器: 任务 %d 第%d段视频生成完成,进入审核", current.Id, current.SegmentIdx+1) // 更新剧集状态:如果全部任务都到了 review/completed,剧集标记为 review - if allTasksDone, _ := dao.GenerationTask.ListByEpisode(ctx, current.EpisodeId); len(allTasksDone) > 0 { + if allTasksDone, leErr := dao.GenerationTask.ListByEpisode(ctx, current.EpisodeId); leErr != nil { + g.Log().Warningf(ctx, "poller: list tasks of episode %d failed: %v", current.EpisodeId, leErr) + } else if len(allTasksDone) > 0 { allReview := true for _, t := range allTasksDone { if t.Status != consts.TaskStatusReview && t.Status != consts.TaskStatusCompleted { @@ -1788,7 +1831,9 @@ func (s *generationService) processOneEpisode(ctx context.Context, tasks []*enti } } if allReview { - _ = dao.Episode.UpdateStatus(ctx, current.EpisodeId, consts.EpisodeStatusReview, "") + if ue := dao.Episode.UpdateStatus(ctx, current.EpisodeId, consts.EpisodeStatusReview, ""); ue != nil { + g.Log().Errorf(ctx, "poller: mark episode %d review failed: %v", current.EpisodeId, ue) + } } } return @@ -1800,7 +1845,9 @@ func (s *generationService) processOneEpisode(ctx context.Context, tasks []*enti var bodyMap map[string]any if err := json.Unmarshal([]byte(current.Script), &bodyMap); err != nil { g.Log().Errorf(ctx, "poller: task %d script is not valid JSON: %v", current.Id, err) - _ = dao.GenerationTask.UpdateFailed(ctx, current.Id, "script 不是合法 JSON") + if ufErr := dao.GenerationTask.UpdateFailed(ctx, current.Id, "script 不是合法 JSON"); ufErr != nil { + g.Log().Errorf(ctx, "poller: mark task %d failed failed: %v", current.Id, ufErr) + } return } @@ -1837,7 +1884,9 @@ func (s *generationService) processOneEpisode(ctx context.Context, tasks []*enti } // 调用模型前写入模型名称 - _ = dao.GenerationTask.UpdateFields(ctx, current.Id, g.Map{"model_name": modelCfg.ModelName}) + if ue := dao.GenerationTask.UpdateFields(ctx, current.Id, g.Map{"model_name": modelCfg.ModelName}); ue != nil { + g.Log().Errorf(ctx, "poller: write model_name to task %d failed: %v", current.Id, ue) + } taskId, _, err := resubmitVideoTask(ctx, modelCfg.ApiKey, modelCfg.BaseUrl, modelCfg.Schema, bodyJSON, modelCfg.SchemaMapping) if err != nil { @@ -1845,9 +1894,11 @@ func (s *generationService) processOneEpisode(ctx context.Context, tasks []*enti return } - _ = dao.GenerationTask.UpdateFields(ctx, current.Id, g.Map{ + if ue := dao.GenerationTask.UpdateFields(ctx, current.Id, g.Map{ "video_task_id": taskId, - }) + }); ue != nil { + g.Log().Errorf(ctx, "poller: persist video_task_id to task %d failed: %v", current.Id, ue) + } g.Log().Infof(ctx, "轮询器: 第%d段视频已提交(taskId=%s)", current.SegmentIdx+1, taskId) } @@ -2158,7 +2209,10 @@ func callVideoAPI(ctx context.Context, apiKey, baseURL string, payload []byte, s } defer resp.Body.Close() - respData, _ := io.ReadAll(resp.Body) + respData, err := io.ReadAll(resp.Body) + if err != nil { + return "", nil, fmt.Errorf("read response failed: %w", err) + } var result struct { ID string `json:"id"` TaskID string `json:"task_id"` @@ -2679,10 +2733,12 @@ func prepareFirstFrame(ctx context.Context, body map[string]any, segIdx int, gen firstFrameItem[urlField] = resolvedPath // 持久化到 DB(崩溃恢复用) - if updatedScript, err := json.Marshal(body); err == nil { - _ = dao.GenerationTask.UpdateFields(ctx, genTaskId, g.Map{ - "script": string(updatedScript), - }) + if updatedScript, err := json.Marshal(body); err != nil { + g.Log().Warningf(ctx, "marshal updated script failed: %v", err) + } else if ue := dao.GenerationTask.UpdateFields(ctx, genTaskId, g.Map{ + "script": string(updatedScript), + }); ue != nil { + g.Log().Errorf(ctx, "persist updated script to task %d failed: %v", genTaskId, ue) } return } @@ -2802,6 +2858,12 @@ func rebuildTasksMedia(ctx context.Context, epId int64) { g.Log().Warningf(ctx, "rebuildTasksMedia: list tasks failed: %v", err) return } + // 收集需要更新的任务,用单条 CASE 语句批量更新(避免循环单条 UPDATE) + var ( + ids []int64 + cases []string + args []any + ) for _, t := range tasks { if t.Status != consts.TaskStatusPending || t.Script == "" { continue @@ -2810,11 +2872,22 @@ func rebuildTasksMedia(ctx context.Context, epId int64) { if !modified { continue } - if _, e := g.DB().Model(consts.TableNameGenerationTask).Ctx(ctx). - Data(g.Map{"script": string(newScript)}). - Where("id", t.Id).Update(); e != nil { - g.Log().Warningf(ctx, "rebuildTasksMedia: update task %d script failed: %v", t.Id, e) - } + ids = append(ids, t.Id) + cases = append(cases, "WHEN id=? THEN ?") + args = append(args, t.Id, string(newScript)) + } + if len(ids) == 0 { + return + } + for _, id := range ids { + args = append(args, id) + } + sql := "UPDATE " + consts.TableNameGenerationTask + + " SET script = CASE id " + strings.Join(cases, " ") + + " END, updated_at = datetime('now','localtime') WHERE id IN (" + + strings.TrimSuffix(strings.Repeat("?,", len(ids)), ",") + ")" + if _, e := g.DB().Exec(ctx, sql, args...); e != nil { + g.Log().Warningf(ctx, "rebuildTasksMedia: batch update %d tasks failed: %v", len(ids), e) } } diff --git a/shortdrama/service/model_config_service.go b/shortdrama/service/model_config_service.go index 451e108..e9fb8ce 100644 --- a/shortdrama/service/model_config_service.go +++ b/shortdrama/service/model_config_service.go @@ -23,7 +23,11 @@ var ModelConfigService = new(modelConfigService) func (s *modelConfigService) getModelList(ctx context.Context) []*entity.ModelConfig { list, err := dao.ModelConfig.GetAll(ctx) - if err != nil || len(list) == 0 { + if err != nil { + g.Log().Warningf(ctx, "load model config list failed: %v", err) + return make([]*entity.ModelConfig, 0) + } + if len(list) == 0 { return make([]*entity.ModelConfig, 0) } return list @@ -55,15 +59,22 @@ func (s *modelConfigService) GetModelListResponse(ctx context.Context) *dto.GetM func (s *modelConfigService) SaveModelConfig(ctx context.Context, req *dto.SaveModelConfigReq) error { cfg := new(entity.ModelConfig) if req.Id > 0 { - existing, _ := dao.ModelConfig.GetAll(ctx) + existing, err := dao.ModelConfig.GetAll(ctx) + if err != nil { + return err + } for _, m := range existing { if m.Id == req.Id { - gconv.Struct(m, cfg) + if err := gconv.Struct(m, cfg); err != nil { + return err + } break } } } - gconv.Struct(req, cfg) + if err := gconv.Struct(req, cfg); err != nil { + return err + } if req.Schema != nil { cfg.Schema = req.Schema.String() diff --git a/shortdrama/service/payment_order_service.go b/shortdrama/service/payment_order_service.go index 4ac82bc..77a6f1c 100644 --- a/shortdrama/service/payment_order_service.go +++ b/shortdrama/service/payment_order_service.go @@ -15,6 +15,7 @@ import ( "sort" "strings" "time" + "video-factory/shortdrama/consts" "video-factory/shortdrama/dao" "video-factory/shortdrama/model/entity" @@ -113,18 +114,22 @@ func (s *paymentService) Prepay(ctx context.Context, userId int64, amount int64, } if err != nil { - _ = dao.PaymentOrder.UpdateFail(ctx, id, fmt.Sprintf(`{"error":"%s"}`, err.Error())) + if ufErr := dao.PaymentOrder.UpdateFail(ctx, id, fmt.Sprintf(`{"error":"%s"}`, err.Error())); ufErr != nil { + g.Log().Errorf(ctx, "update order %s to failed failed: %v", orderNo, ufErr) + } return nil, "", "", "", err } if channel != "offline" { - _, _ = dao.PaymentChannelTrade.Insert(ctx, &entity.PaymentChannelTrade{ + if _, trErr := dao.PaymentChannelTrade.Insert(ctx, &entity.PaymentChannelTrade{ OrderId: id, Channel: channel, CodeUrl: codeUrl, PrepayId: prepayJson, ChannelResponse: "{}", - }) + }); trErr != nil { + g.Log().Warningf(ctx, "insert channel trade for order %s failed: %v", orderNo, trErr) + } } return order, codeUrl, prepayJson, redirectUrl, nil @@ -166,13 +171,17 @@ func (s *paymentService) CreateRenewalOrder(ctx context.Context, agentId int64, NotifyRaw: fmt.Sprintf(`{"duration":%d}`, duration), } - err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + err = g.DB(consts.DbGroupFinance).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { ap, _ := dao.AgentProfile.Get(ctx, agentId) if ap != nil { ap.RegionProtected = pricing.Protected == 1 ap.MaxCustomers = pricing.MaxCustomers - _ = dao.AgentProfile.Update(ctx, ap) - _ = dao.User.UpdateFields(ctx, agentId, g.Map{"region": pricing.Region}) + if e := dao.AgentProfile.Update(ctx, ap); e != nil { + return e + } + if e := dao.User.UpdateFields(ctx, agentId, g.Map{"region": pricing.Region}); e != nil { + return e + } } id, e := dao.PaymentOrder.Insert(ctx, order) if e != nil { @@ -200,7 +209,7 @@ func (s *paymentService) ConfirmOffline(ctx context.Context, orderNo string) err return errors.New("only offline payment orders can be confirmed") } - return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + return g.DB(consts.DbGroupFinance).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { if err := dao.PaymentOrder.UpdateSuccess(ctx, order.Id, `{"confirm":"manual"}`); err != nil { return err } @@ -250,26 +259,27 @@ func (s *paymentService) HandleNotify(ctx context.Context, channel string, body } rawStr := string(body) - if err := dao.PaymentOrder.UpdateSuccess(ctx, order.Id, rawStr); err != nil { - return err - } - - if _, err := dao.PaymentChannelTrade.Insert(ctx, &entity.PaymentChannelTrade{ - OrderId: order.Id, - Channel: channel, - TradeNo: tradeNo, - ChannelResponse: rawStr, - }); err != nil { - return err - } - - switch order.OrderType { - case "recharge": - return TransactionService.Recharge(ctx, order.UserId, order.Amount, orderNo, fmt.Sprintf("支付充值 %.2f 元", float64(order.Amount)/100)) - case "renewal": - return AgentService.RenewAgentByOrder(ctx, order.UserId, order.NotifyRaw) - } - return nil + // 订单状态更新、渠道流水写入、余额/续费变更放在同一事务内,保证原子性 + return g.DB(consts.DbGroupFinance).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + if err := dao.PaymentOrder.UpdateSuccess(ctx, order.Id, rawStr); err != nil { + return err + } + if _, err := dao.PaymentChannelTrade.Insert(ctx, &entity.PaymentChannelTrade{ + OrderId: order.Id, + Channel: channel, + TradeNo: tradeNo, + ChannelResponse: rawStr, + }); err != nil { + return err + } + switch order.OrderType { + case "recharge": + return TransactionService.Recharge(ctx, order.UserId, order.Amount, orderNo, fmt.Sprintf("支付充值 %.2f 元", float64(order.Amount)/100)) + case "renewal": + return AgentService.RenewAgentByOrder(ctx, order.UserId, order.NotifyRaw) + } + return nil + }) } // GetStatus 查询订单状态 @@ -318,7 +328,10 @@ func (s *paymentService) callWechat(ctx context.Context, order *entity.PaymentOr return "", "", fmt.Errorf("wechat order failed: %w", err) } defer resp.Body.Close() - b, _ := io.ReadAll(resp.Body) + b, err := io.ReadAll(resp.Body) + if err != nil { + return "", "", fmt.Errorf("wechat order response read failed: %w", err) + } result := s.xmlToMap(string(b)) if result["return_code"] != "SUCCESS" || result["result_code"] != "SUCCESS" { @@ -391,7 +404,10 @@ func (s *paymentService) callAlipay(ctx context.Context, order *entity.PaymentOr return "", "", fmt.Errorf("alipay order failed: %w", err) } defer resp.Body.Close() - b, _ := io.ReadAll(resp.Body) + b, err := io.ReadAll(resp.Body) + if err != nil { + return "", "", fmt.Errorf("alipay order response read failed: %w", err) + } var result struct { Response struct { diff --git a/shortdrama/service/transaction_service.go b/shortdrama/service/transaction_service.go index f5b1e97..930c6e9 100644 --- a/shortdrama/service/transaction_service.go +++ b/shortdrama/service/transaction_service.go @@ -3,9 +3,13 @@ package service import ( "context" "fmt" + "video-factory/shortdrama/consts" "video-factory/shortdrama/dao" "video-factory/shortdrama/model/entity" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" ) type transactionService struct{} @@ -20,31 +24,35 @@ func (s *transactionService) Insert(ctx context.Context, data *entity.AccountTra return dao.AccountTransaction.Insert(ctx, data) } +// Recharge 充值:余额更新与流水写入放在同一事务内,保证原子性。 +// 若调用方已开启 finance 事务(如确认支付),会自动复用外层事务。 func (s *transactionService) Recharge(ctx context.Context, customerId int64, amount int64, orderNo, remark string) error { if amount <= 0 { return fmt.Errorf("recharge amount must be greater than 0") } - cp, err := dao.CustomerProfile.Get(ctx, customerId) - if err != nil || cp == nil { - return fmt.Errorf("customer not found") - } - newBalance := cp.Balance + amount - if err := dao.CustomerProfile.UpdateBalance(ctx, customerId, newBalance); err != nil { + return g.DB(consts.DbGroupFinance).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + cp, err := dao.CustomerProfile.Get(ctx, customerId) + if err != nil || cp == nil { + return fmt.Errorf("customer not found") + } + newBalance := cp.Balance + amount + if err := dao.CustomerProfile.UpdateBalance(ctx, customerId, newBalance); err != nil { + return err + } + rk := remark + if rk == "" { + rk = fmt.Sprintf("充值 %.2f 元", float64(amount)/100) + } + _, err = dao.AccountTransaction.Insert(ctx, &entity.AccountTransaction{ + UserId: customerId, + Type: "recharge", + Amount: amount, + BalanceBefore: cp.Balance, + BalanceAfter: newBalance, + OrderNo: orderNo, + Remark: rk, + CreatedBy: "system", + }) return err - } - rk := remark - if rk == "" { - rk = fmt.Sprintf("充值 %.2f 元", float64(amount)/100) - } - _, err = dao.AccountTransaction.Insert(ctx, &entity.AccountTransaction{ - UserId: customerId, - Type: "recharge", - Amount: amount, - BalanceBefore: cp.Balance, - BalanceAfter: newBalance, - OrderNo: orderNo, - Remark: rk, - CreatedBy: "system", }) - return err } diff --git a/shortdrama/service/user_model_config_service.go b/shortdrama/service/user_model_config_service.go index 84de4e2..685f3c8 100644 --- a/shortdrama/service/user_model_config_service.go +++ b/shortdrama/service/user_model_config_service.go @@ -54,7 +54,10 @@ func (s *userModelConfigService) GetUserConfig(ctx context.Context, userId int64 } func (s *userModelConfigService) SaveUserConfigs(ctx context.Context, userId int64, items []*dto.SaveUserModelConfigItem) error { - existingList, _ := dao.UserModelConfig.GetByUserId(ctx, userId) + existingList, err := dao.UserModelConfig.GetByUserId(ctx, userId) + if err != nil { + return err + } existingByType := make(map[string]*entity.UserModelConfig) for _, ec := range existingList { if ec.ModelType != "" { @@ -112,7 +115,10 @@ func (s *userModelConfigService) SaveUserConfig(ctx context.Context, userId int6 } } - existing, _ := dao.UserModelConfig.GetByModelConfigId(ctx, userId, cfg.ModelConfigId) + existing, err := dao.UserModelConfig.GetByModelConfigId(ctx, userId, cfg.ModelConfigId) + if err != nil { + return err + } if existing != nil { cfg.Id = existing.Id cfg.CreatedAt = existing.CreatedAt @@ -190,7 +196,11 @@ func (s *userModelConfigService) GetMergedConfig(ctx context.Context, userId int } func (s *userModelConfigService) GetUserModelList(ctx context.Context, userId int64) *dto.GetUserModelListRes { - userCfgs, _ := dao.UserModelConfig.GetByUserId(ctx, userId) + userCfgs, err := dao.UserModelConfig.GetByUserId(ctx, userId) + if err != nil { + g.Log().Warningf(ctx, "list user model configs of user %d failed: %v", userId, err) + return &dto.GetUserModelListRes{List: make([]*dto.UserModelItem, 0)} + } if len(userCfgs) == 0 { return &dto.GetUserModelListRes{List: make([]*dto.UserModelItem, 0)} } @@ -232,7 +242,11 @@ func (s *userModelConfigService) GetUserModelList(ctx context.Context, userId in func (s *userModelConfigService) GetUserModelListPage(ctx context.Context, userId int64, page, pageSize int, modelType, keyword string) *dto.GetUserModelListRes { // 先查用户配置,获取 model_config_id 列表 - userCfgs, _ := dao.UserModelConfig.GetByUserId(ctx, userId) + userCfgs, err := dao.UserModelConfig.GetByUserId(ctx, userId) + if err != nil { + g.Log().Warningf(ctx, "list user model configs of user %d failed: %v", userId, err) + return &dto.GetUserModelListRes{List: make([]*dto.UserModelItem, 0)} + } if len(userCfgs) == 0 { return &dto.GetUserModelListRes{List: make([]*dto.UserModelItem, 0)} } diff --git a/shortdrama/service/user_service.go b/shortdrama/service/user_service.go index f0564ac..6c836b4 100644 --- a/shortdrama/service/user_service.go +++ b/shortdrama/service/user_service.go @@ -9,6 +9,7 @@ import ( "video-factory/shortdrama/dao" "video-factory/shortdrama/model/entity" + "github.com/gogf/gf/v2/frame/g" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) @@ -34,7 +35,10 @@ func (s *userService) Login(ctx context.Context, account, password string) (*ent var agentId int64 if user.Role == "customer" { - cp, _ := dao.CustomerProfile.Get(ctx, user.Id) + cp, err := dao.CustomerProfile.Get(ctx, user.Id) + if err != nil { + g.Log().Warningf(ctx, "login: get customer profile %d failed: %v", user.Id, err) + } if cp != nil { agentId = cp.AgentId }