diff --git a/.DS_Store b/.DS_Store index f4fe911..36c4879 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.trae/documents/API 与后台管理代码审计与整改计划.md b/.trae/documents/API 与后台管理代码审计与整改计划.md new file mode 100644 index 0000000..4590929 --- /dev/null +++ b/.trae/documents/API 与后台管理代码审计与整改计划.md @@ -0,0 +1,50 @@ +## 概览 +- 后端以 Go 实现,路由集中在 `internal/router/router.go`,分5组:管理端非认证(`/api/admin`登录)、管理端认证(`/api/admin`全量管理接口)、系统管理(`/api`模板兼容)、APP公开(`/api/app`)、APP认证(`/api/app`)。 +- 管理端处理器分布在 `internal/api/admin/*`,覆盖活动/期次/奖励/抽奖/用户资产/称号/道具卡/优惠券/工会/系统用户角色菜单等。 +- 前端后台管理位于 `web/admin`,Axios 基础 `baseURL=/api`,API 模块在 `web/admin/src/api/*`。 + +## 发现与结论 +- 路由与前端 URL 基本一致;系统管理模块通过 `baseURL=/api` 访问 `/api/user/list|/api/role/list|/api/v3/system/menus/simple`,与后端 `systemApiRouter` 对齐(`internal/router/router.go:59-167`)。 +- 前端存在绝对 URL 前缀问题:`web/admin/src/api/reward-grant.ts:50` 使用 `url: '/api/admin/...'`,与 `baseURL=/api` 叠加为重复前缀,应改为相对 `admin/...`。 +- 奖励发放接口在前端有重复实现:`reward-grant.ts` 与 `player-manage.ts` 同指向 `POST /api/admin/users/{id}/rewards/grant`(后端实现见 `internal/api/admin/users_reward_admin.go:32-75`),建议合并与统一用法。 +- 认证链路清晰:管理端拦截器校验 JWT、登录哈希与状态(`internal/router/interceptor/admin_auth.go:18-82`);APP 端简化(`internal/router/interceptor/app_auth.go:13-35`)。前端 Axios 统一加 `Authorization` 并防抖登出(`web/admin/src/utils/http/index.ts:65-76,84-92`)。 +- 分层一致性:多数管理端处理器调用 service 层,但也有直接操作 DAO 的情况(如称号管理 `internal/api/admin/titles_admin.go`),建议将复杂业务(效果校验、用户称号唯一激活切换)沉入 service 保持一致性与复用。 +- 性能与合理性: + - 工作台积分统计扫表累加(`internal/api/admin/dashboard_admin.go:74-84`)可改为 SQL 聚合并按有效期过滤;积分净变动已用流水 SUM(`87-107`),建议两者一致走 SQL。 + - 新用户概览多表聚合(`224-381`)逻辑合理但查询密集,后续可评估增加索引与批量 UNION/子查询降往返。 +- API 路由一致性:系统管理接口挂在 `/api` 与管理端其余接口挂在 `/api/admin`,存在认知负担;短期保留兼容,补齐文档;中期评估统一路径策略。 + +## 整改实施计划 +### 阶段1:快速修复 +- 修复前端绝对 URL 前缀问题:将 `web/admin/src/api/reward-grant.ts:50` 的 `url: '/api/admin/users/${userId}/rewards/grant'` 改为 `url: 'admin/users/${userId}/rewards/grant'`,与其余模块一致。 +- 合并奖励发放 API:删除/重定向 `reward-grant.ts` 的重复实现,统一在 `player-manage.ts` 暴露 `fetchGrantPlayerReward`,或反之,在 `reward-grant.ts` 统一导出并在 `player-manage.ts` 复用。 + +### 阶段2:分层与可维护性 +- 抽取称号管理核心逻辑到 service: + - 将 `AssignUserTitle` 的 upsert 与“仅保留一个激活称号”的切换(`internal/api/admin/titles_admin.go:439-490`)抽到 `internal/service/title`,供管理端与未来 APP 端统一调用。 + - 将效果参数校验 `validateEffectParams`(`titles_admin.go:296-377`)迁移为可复用的校验器,便于单元测试与前后端一致性。 +- 优化工作台统计: + - 积分总额改为 SQL `SUM(points)` 并过滤过期(替换 `dashboard_admin.go:74-84` 的内存累加),减少 I/O 与CPU。 + +### 阶段3:一致性与文档 +- 路由规范化策略评审:维持 `/api`(系统管理)与 `/api/admin` 并存,新增开发文档条目明确两者用途与历史原因;中期根据前端路由与菜单加载需求评估是否迁移到 `/api/admin/system/*`。 +- 更新 Swagger 文档:核对 `docs/swagger.yaml`/`docs/swagger.json` 与现状路由一致,补充工作台接口、称号/效果接口、批量发放接口说明。 + +### 阶段4:测试与验证 +- 前端:为 `Axios` 层与关键 API(登录、奖励发放、称号分配)增加最小可运行的集成测试或契约测试;校验 401 防抖与登出行为。 +- 后端:为称号 service 新增单元测试(效果参数校验、scope 命中、唯一激活切换),为工作台统计新增 SQL 聚合测试。 + +## 参考定位 +- 路由总表:`internal/router/router.go:50-53,56-167,169-188,190-210,213-232` +- 绝对 URL 问题:`web/admin/src/api/reward-grant.ts:50` +- Axios baseURL:`.env.development:7`、`web/admin/src/utils/http/index.ts:45-48` +- 奖励发放后端:`internal/api/admin/users_reward_admin.go:32-75` +- 工作台积分统计:`internal/api/admin/dashboard_admin.go:74-84,87-107` +- 称号分配逻辑:`internal/api/admin/titles_admin.go:439-490` +- 认证拦截器:`internal/router/interceptor/admin_auth.go:18-82`、`internal/router/interceptor/app_auth.go:13-35` + +## 验收标准 +- 前端所有 API 使用相对路径,无 `/api/` 重复前缀;奖励发放仅一处实现且通过。 +- 工作台统计在数据量上升时仍稳定(SQL 聚合后端性能可观)。 +- 称号管理逻辑具备可测试的 service 层,接口行为与前端预期一致。 +- Swagger 文档与实际路由对齐,前后端调用一目了然。 \ No newline at end of file diff --git a/.trae/documents/APP端工会审核适配与可选增强.md b/.trae/documents/APP端工会审核适配与可选增强.md new file mode 100644 index 0000000..c89b628 --- /dev/null +++ b/.trae/documents/APP端工会审核适配与可选增强.md @@ -0,0 +1,25 @@ +## 是否变更APP接口 +- 路由与参数:不变。现有 APP 端接口保持原路径与入参。 + - `POST /api/app/guilds/:guild_id/members`(加入):internal/api/guild/members_app.go:19-50 + - `DELETE /api/app/guilds/:guild_id/members/:user_id`(离开):internal/api/guild/members_app.go:52-83 + - `GET /api/app/guilds/:guild_id/members`(成员列表):internal/api/guild/members_app.go:102-141 +- 响应结构:不变(仍返回 `{ message }`)。 +- 行为变化:当工会 `join_mode=1` 时,加入后处于待审状态,不会立即出现在成员列表(服务层变更:internal/service/guild/join.go:13-48;成员列表过滤正式成员:internal/service/guild/members_list.go:9-29)。 + +## 客户端适配建议(保持接口不变) +- 加入后状态提示:将文案改为“申请已提交,待审核”,并在个人/工会页显示“待审核”状态。 +- 功能限制:待审期间禁用仅成员可用的操作(如成员互动、贡献等)。 +- 成员列表:无需变更,列表只显示已通过成员。 + +## 可选后端增强(增强体验,仍向后兼容) +1) 加入响应增强:在 `POST /api/app/guilds/:guild_id/members` 返回体中新增 `join_status`(0待审/2已通过),便于客户端无额外查询即可知状态。 +2) 成员自查接口:新增 `GET /api/app/guilds/:guild_id/members/me` 返回当前用户在该工会的 `role/status/join_status`。 +3) 申请查询接口:新增 `GET /api/app/guilds/:guild_id/applications?user_id=xxx` 用于查询自己的待审记录(只读)。 +- 以上均为增量字段与新端点,旧客户端不受影响。 + +## 验收 +- 加入审核工会时,APP 加入成功但显示“待审核”,成员列表不包含该用户;审核通过后自动展示。 +- 保持现有路由与参数不变,无需强制更新客户端。 +- 若启用增强:客户端通过 `join_status` 或新查询端点准确展示状态。 + +如确认,我将按照“保持接口不变 + 提供可选增强”的方式推进服务与(可选)APP接口扩展,并提供前端适配示例。 \ No newline at end of file diff --git a/.trae/documents/Go 代码规范整改计划.md b/.trae/documents/Go 代码规范整改计划.md new file mode 100644 index 0000000..0511fce --- /dev/null +++ b/.trae/documents/Go 代码规范整改计划.md @@ -0,0 +1,40 @@ +## 范围 +- API 层:补齐接口文档注释、统一 req/res 模式、限制为路由与参数控制 +- Service 层:迁移业务与数据访问、补齐函数级注释、对齐 activity_* 分层命名 + +## 工作项 +1. API 注释补全(Swagger) +- 为缺少注释的端点补充 `@Summary/@Description/@Tags/@Param/@Success/@Failure/@Router`: + - `internal/api/activity/draw_app.go:16 ExecuteDraw` + - `internal/api/admin/draw_simulate.go:36 SimulateIssueDraw` + - `internal/api/admin/issue_random_commit.go:21/48/80 Commit/Get/History` + - `internal/api/admin/draw_receipt.go:31/77 GetDrawReceipt/GetDrawReceiptByLogID` + - `internal/api/admin/verify_draw.go:34 VerifyDrawReceipt` + +2. API 限定职责 +- 将 API 中直接访问数据库的逻辑迁移到 Service 层: + - `internal/api/admin/draw_receipt.go` 查询收据改为调用 `activity.Service.GetDrawReceipt/GetDrawReceiptByLogID` +- 保持 API 仅做:参数绑定、鉴权、调用 service、返回 res + +3. 统一 req/res 代码风格 +- 统一使用 `req := new(XxxRequest)` / `res := new(XxxResponse)`,并在所有端点输出 `ctx.Payload(res)` +- 检查并修正个别端点的 `var req` 临时写法 + +4. Service 注释与接口规范 +- 为 `internal/service/activity/activity.go` 的 `Service` 接口所有方法添加函数级注释(功能、参数、返回值) +- 为 `internal/service/activity/*.go`(如 `draw_execute.go` 等)关键导出函数添加注释 +- 保持 `internal/service/user/*.go` 已有中文注释风格的一致性 + +5. 分层与命名对齐 +- 保持并补齐 `internal/api/activity/*` 与 `internal/service/activity/*` 的 `activity_*` 命名映射(CRUD、issue、rewards、draw、logs) +- 审核并调整少量不一致的文件/函数命名(如 helper 置于非 API 包或私有化) + +## 验证 +- 运行 Swagger 生成与本地文档预览,检查所有路由均有完整注释 +- 运行单元测试/集成冒烟:抽奖、承诺、收据查询、奖励发放 +- 代码静态检查:确保 API 无直接 DAO 访问、Service 注释完整 + +## 交付物 +- 已修正的 API 与 Service 源码 +- 通过的 Swagger 文档与接口列表 +- 简要测试记录(通过用例与关键接口返回示例) diff --git a/.trae/documents/修复“添加奖品弹出批量抽奖”与奖品列表优化计划.md b/.trae/documents/修复“添加奖品弹出批量抽奖”与奖品列表优化计划.md new file mode 100644 index 0000000..9008e8f --- /dev/null +++ b/.trae/documents/修复“添加奖品弹出批量抽奖”与奖品列表优化计划.md @@ -0,0 +1,40 @@ +## 问题定位 +- 奖品页 `web/admin/src/views/activity/rewards/index.vue` 在工具栏下方无条件渲染了批量抽奖对话框:``。 +- 该子组件内部以 `props.activity !== null` 控制 `v-model`,而当前页的 `currentActivity` 始终返回对象(非 null),导致对话框初始即打开,表现为点击“新增奖励”时出现“批量抽奖”。 + +## 修复方案 +- 直接移除奖品页中的 `BatchDrawDialog` 渲染(该页仅做奖励 CRUD,模拟批量抽奖逻辑属于活动管理页/模拟页)。 +- 若保留(备选):使用受控显示状态,改为 ``,并提供单独按钮触发。默认 `showBatchDrawDialog=false`。 +- 采用“移除”方案,影响文件:`web/admin/src/views/activity/rewards/index.vue`(删除组件引用与相关变量)。 + +## 奖品列表优化 +- 新增展示: + - “剩余/总量”列:`quantity / original_qty` + - “期望概率”列:基于总权重 `sumWeight` 本地计算 `weight/sumWeight*100`,保留两位小数。 + - Boss 标签保持,但列名更明确为“Boss”。 +- 汇总信息:表头上方显示“奖品总数 / 总权重”。 +- 交互优化: + - 编辑校验完善(名称、商品、权重、数量、原始数量、等级为必填/大于零)。 + - 删除增加二次确认弹窗。 +- 过滤与排序(前端本地): + - 过滤项:名称(模糊)、等级(S/A/B/C)、是否 Boss。 + - 默认排序:按等级(S→C)+ `sort`。 +- 数据加载优化:商品列表一次加载并缓存,避免重复请求。 + +## 具体改动 +- 修改文件:`web/admin/src/views/activity/rewards/index.vue` + - 删除 `` 与相关 `currentActivity` 计算属性。 + - 在 `columns` 增加 `剩余/总量`、`期望概率` 两列(期望概率通过本地计算得到)。 + - 在顶部新增汇总区域(使用 `computed` 基于 `data` 计算)。 + - 增加简单的本地过滤控件(`ElInput`/`ElSelect`),对展示数据做 `computed` 过滤与排序。 + - 删除与批量抽奖相关的无用状态(如 `showSimDialog`)。 + - 删除按钮文案更明确:`批量新增奖励` → `新增奖励`。 +- 不改后端接口;所有优化为前端表现层。 + +## 验收 +- 进入“奖励”页,点击“新增奖励”只弹奖励编辑对话框,不再出现批量抽奖对话框。 +- 列表显示“剩余/总量”、“期望概率”,汇总信息正确;过滤与排序生效。 +- 构建通过,交互校验正常,删除有确认提示。 + +## 风险与回滚 +- 风险低,仅前端视图改动;如需回滚,保留原组件引用并恢复初始布局即可。 \ No newline at end of file diff --git a/.trae/documents/修复微信支付回调验签失败(支持平台公钥_PUB_KEY_ID).md b/.trae/documents/修复微信支付回调验签失败(支持平台公钥_PUB_KEY_ID).md new file mode 100644 index 0000000..58ae401 --- /dev/null +++ b/.trae/documents/修复微信支付回调验签失败(支持平台公钥_PUB_KEY_ID).md @@ -0,0 +1,38 @@ +## 问题定位 +- 现象:POST `/api/pay/wechat/notify` 返回 400,日志提示“certificate [PUB_KEY_ID_0116104396352025041000211519001600] not found in verifier”。 +- 原因:微信侧请求头 `Wechatpay-Serial` 为 `PUB_KEY_ID_*` 格式,说明该商户已启用“微信支付平台公钥”模式;当前代码仅使用“平台证书”验签(`verifiers.NewSHA256WithRSAVerifier` + `downloader`),导致找不到匹配的证书序列号而验签失败。 +- 代码位置: + - 路由注册:`internal/router/router.go:252` + - 回调处理:`internal/api/pay/wechat_notify.go:40-46`(注册证书下载器+证书验签器) + - 客户端初始化:`internal/pkg/pay/client.go:32-39`(`WithWechatPayAutoAuthCipher` 使用平台证书模式) + +## 修复方案 +- 增加“平台公钥”模式配置并切换验签/签名实现: + - 在配置新增 `wechatpay.public_key_id` 与 `wechatpay.public_key_path`(商户后台下载的 `pub_key.pem` 与对应 `PUB_KEY_ID_*`)。 + - 回调验签:当检测到已配置公钥,改用 `verifiers.NewSHA256WithRSAPubkeyVerifier(publicKeyID, publicKey)` 构造 `notify.Handler`;否则保持现有证书模式。 + - 请求侧签名与自动加密:当检测到已配置公钥,改用 `option.WithWechatPayPublicKeyAuthCipher(mchid, serialNo, privateKey, publicKeyID, publicKey)` 初始化 `core.Client`;否则保持 `WithWechatPayAutoAuthCipher`。 +- 初始化时机优化: + - 将证书下载器注册/公钥加载前置至应用启动阶段,避免首次回调时的“尚未拉取证书”竞态问题。 +- 兼容策略: + - 双模式自动选择:优先检测公钥配置;缺省走证书模式,确保对旧商户不破坏。 + +## 实施步骤 +- 配置: + - 在 `configs/*.toml` 的 `[wechatpay]` 增加 `public_key_id` 与 `public_key_path` 键;同时支持环境变量覆盖(如 `WECHAT_PUBLIC_KEY_ID`、`WECHAT_PUBLIC_KEY_PATH`)。 +- 代码改动: + - `internal/api/pay/wechat_notify.go`:根据配置分支选择 `verifiers.NewSHA256WithRSAPubkeyVerifier` 或现有证书验签器;加载公钥使用 `utils.LoadPublicKeyWithPath`。 + - `internal/pkg/pay/client.go`:根据配置分支选择 `option.WithWechatPayPublicKeyAuthCipher` 或现有证书选项。 + - 启动流程:在应用初始化位置集中完成下载器注册/公钥加载与复用,不在每次回调时重复注册。 + +## 验证步骤 +- 本地联调: + - 使用真实微信回调数据(包含 `Wechatpay-Serial: PUB_KEY_ID_*`)验证回调成功返回 `{"code":"SUCCESS","message":"OK"}`。 + - 下单→支付→回调链路验证:检查订单状态从 1→2,`paidAt` 按回调 `success_time` 生效(`internal/api/pay/wechat_notify.go:67-71`)。 +- 日志与安全: + - 打印初始化模式(证书/公钥)与关键配置是否完整,避免静默失败。 + - 不输出密钥/私钥内容,符合安全规范。 + +## 额外检查(关于“req 没有写”) +- 当前回调处理直接使用 `ctx.Request()` 原始请求体(`internal/api/pay/wechat_notify.go:48`),未提前读取/篡改;若上游中间件读取了 `Body`,需保证使用 `io.NopCloser` 复位请求体后再交给 `notify.Handler`。本次改造同时确认路由链路未破坏请求体传递。 + +请确认是否按照“平台公钥”接入(已具备 `pub_key.pem` 与 `PUB_KEY_ID_*`)。确认后我将按上述方案实现代码与配置改动,并完成联调与验证。 \ No newline at end of file diff --git a/.trae/documents/发货统计SQL设计与字段映射.md b/.trae/documents/发货统计SQL设计与字段映射.md new file mode 100644 index 0000000..2d63b41 --- /dev/null +++ b/.trae/documents/发货统计SQL设计与字段映射.md @@ -0,0 +1,109 @@ +## 模型目标 +- 为运营提供“发货统计”独立表,脱离业务在线查询复杂度,支持导出与审计。 +- 字段覆盖:产品、价格、发货数量、用户收件信息、物流、订单聚合、盈亏、来源、垫付人、时间。 + +## 表结构(DDL) +```sql +CREATE TABLE IF NOT EXISTS ops_shipping_stats ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + shipped_at DATETIME NOT NULL COMMENT '发货时间', + product_id BIGINT NULL, + product_name VARCHAR(255) NOT NULL, + product_price_cents BIGINT NOT NULL COMMENT '单位:分', + shipped_qty BIGINT NOT NULL, + user_id BIGINT NOT NULL, + user_name VARCHAR(100) NOT NULL, + user_address_text VARCHAR(512) NOT NULL, + express_code VARCHAR(64) NULL, + express_no VARCHAR(128) NULL, + order_id BIGINT NULL, + order_no VARCHAR(64) NULL, + order_qty BIGINT NULL, + order_amount_cents BIGINT NULL COMMENT '单位:分', + profit_loss_cents BIGINT NULL COMMENT '单位:分', + order_source_type INT NULL, + order_source_text VARCHAR(32) NULL, + payer VARCHAR(128) NULL COMMENT '垫付人,后续录入或从remark规范解析', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + KEY idx_shipped_at(shipped_at), + KEY idx_product(product_id), + KEY idx_order(order_id), + KEY idx_express(express_no) +) COMMENT='运营发货统计'; +``` + +## 明细数据入库(INSERT…SELECT) +```sql +/* 参数:@start_date, @end_date */ +INSERT INTO ops_shipping_stats ( + shipped_at, product_id, product_name, product_price_cents, shipped_qty, + user_id, user_name, user_address_text, express_code, express_no, + order_id, order_no, order_qty, order_amount_cents, profit_loss_cents, + order_source_type, order_source_text, payer +) +SELECT + sr.shipped_at, + COALESCE(sr.product_id, oi.product_id) AS product_id, + COALESCE(oi.title, p.name) AS product_name, + COALESCE(sr.price, oi.price, p.price) AS product_price_cents, + sr.quantity AS shipped_qty, + ua.user_id AS user_id, + ua.name AS user_name, + CONCAT(ua.province, ua.city, ua.district, ua.address) AS user_address_text, + sr.express_code, + sr.express_no, + o.id AS order_id, + o.order_no AS order_no, + os.total_qty AS order_qty, + o.actual_amount AS order_amount_cents, + /* 盈亏:行价格×发货数量 − 订单实付金额(可根据口径调整) */ + COALESCE(sr.price, oi.price, p.price) * sr.quantity - o.actual_amount AS profit_loss_cents, + o.source_type AS order_source_type, + CASE o.source_type WHEN 1 THEN '商城直购' WHEN 2 THEN '抽奖票据' WHEN 3 THEN '其他' ELSE CONCAT('未知-', o.source_type) END AS order_source_text, + /* 垫付人:暂为空,后续从 remark 或新增字段填充 */ + NULL AS payer +FROM shipping_records sr +LEFT JOIN order_items oi ON oi.id = sr.order_item_id +LEFT JOIN products p ON p.id = COALESCE(sr.product_id, oi.product_id) +LEFT JOIN orders o ON o.id = sr.order_id +LEFT JOIN ( + SELECT order_id, SUM(quantity) AS total_qty + FROM order_items + GROUP BY order_id +) os ON os.order_id = o.id +LEFT JOIN user_addresses ua ON ua.id = COALESCE(sr.address_id, o.user_address_id) +WHERE sr.status IN (2,3) /* 已发货/已签收 */ + AND sr.shipped_at BETWEEN @start_date AND @end_date; +``` + +## 查询(导出) +```sql +/* 查询明细(元) */ +SELECT + product_name, + ROUND(product_price_cents/100, 2) AS product_price_yuan, + shipped_qty, + user_name, + user_address_text, + express_code, + express_no, + order_no, + order_qty, + ROUND(order_amount_cents/100, 2) AS order_amount_yuan, + ROUND(profit_loss_cents/100, 2) AS profit_loss_yuan, + order_source_text, + payer, + shipped_at +FROM ops_shipping_stats +WHERE shipped_at BETWEEN @start_date AND @end_date +ORDER BY shipped_at DESC; +``` + +## 口径与可调整项 +- 盈亏口径:当前为“行价格×发货数量 − 订单实付金额”;如需改为“订单行应付总额(`oi.total_amount`) − 订单实付金额”,可替换。 +- 下单数量:当前为订单维度总件数(聚合);如需改为行数量,改用 `oi.quantity`。 +- 垫付人:建议在 `shipping_records` 增加 `payer` 字段或在 `remark` 规范写入 `payer:xxx`,入库时解析填充。 + +## 后续实现建议 +- 管理后台增加导出按钮,直接查询 `ops_shipping_stats` 并输出 CSV/Excel。 +- 每日定时任务或发货事件触发增量写入,保证统计表实时/准实时更新。 \ No newline at end of file diff --git a/.trae/documents/发货统计创建表单与来源字段优化方案.md b/.trae/documents/发货统计创建表单与来源字段优化方案.md new file mode 100644 index 0000000..57316f0 --- /dev/null +++ b/.trae/documents/发货统计创建表单与来源字段优化方案.md @@ -0,0 +1,78 @@ +**问题理解** + +* 创建/编辑对话框单列过长,影响填写与可读性。 + +* 产品、用户应支持选择(远程搜索)而非手填。 + +* 订单来源限定为:淘宝/拼多多/京东/线下;来源类型与文本重复,应仅保留类型并在后端映射文本。 + +**前端改造** + +* 表单布局(左右分布): + + * 在 `web/admin/src/views/operations/shipping-stats/index.vue` 的编辑对话框使用 `el-row/el-col` 两列布局,将字段按分组排布: + + * 左列:发货时间、产品选择、单价、发货数量、订单来源类型(下拉)。 + + * 右列:用户选择、收件人、地址、快递公司、运单号、订单号。 + + * 必填项:`shipped_at`、`product_id`、`user_id`、`shipped_qty`。 + +* 产品选择: + + * 远程搜索下拉 `el-select filterable remote` 调用 `GET 'admin/products'`(已有路由:`internal/router/router.go:120`)。 + + * 选中后自动填充:`product_id`、`product_name`、`product_price_cents`。 + +* 用户选择: + + * 远程搜索下拉调用 `GET 'admin/users'`(已有路由:`internal/router/router.go:129`)。 + + * 选中后自动填充:`user_id`、`user_name`;`user_address_text`仍支持手动输入。 + +* 订单来源: + + * 将 `order_source_type` 改为下拉选择:`{1: 淘宝, 2: 拼多多, 3: 京东, 4: 线下}`。 + + * 移除前端表单中的 `order_source_text` 字段,仅展示文本列(由后端返回)。 + +* API 类型调整: + + * 在 `web/admin/src/api/shipping-stats.ts` 的 `CreateShippingStatRequest/UpdateShippingStatRequest` 中删除 `order_source_text` 字段。 + + * 列表表格仅显示 `order_source_text`;不显示 `order_source_type` 列(现有实现只显示文本,无需调整)。 + +**后端改造** + +* 文段位置:`internal/api/admin/shipping_stats_admin.go`。 + +* 文本映射策略: + + * 在创建与更新接口中,根据 `OrderSourceType` 自动映射 `OrderSourceText`: + + * `1→"淘宝"`、`2→"拼多多"`、`3→"京东"`、`4→"线下"`,其他值映射为`"未知"`。 + + * 移除对 `order_source_text` 的前端输入依赖: + + * `CreateShippingStat()` 忽略请求体中的 `order_source_text`,统一用映射结果赋值。 + + * `ModifyShippingStat()` 若 `OrderSourceType` 变更则同步更新映射文本。 + +* 其余字段逻辑不变;保留列表筛选条件与分页排序。 + +**交互与体验** + +* 远程搜索均支持关键字匹配与节流,选项标签为 `名称 (ID)`,用户便于确认。 + +* 表单左右分布减少滚动,分组更清晰;金额与运单等非必填项可留空。 + +**验收标准** + +* 新建/编辑对话框为两列布局,核心字段分组合理,必填校验生效。 + +* 产品与用户支持远程搜索选择,选中后自动填充相关字段。 + +* 订单来源仅以下拉类型选择,后端返回正确文本,前端只展示文本;不再出现“来源文本/来源类型重复”问题。 + +* 列表/详情/增改删功能在管理端登录后完整可用。 + diff --git a/.trae/documents/发货统计(ops_shipping_stats)前后端对接与CRUD设计.md b/.trae/documents/发货统计(ops_shipping_stats)前后端对接与CRUD设计.md new file mode 100644 index 0000000..ac279f4 --- /dev/null +++ b/.trae/documents/发货统计(ops_shipping_stats)前后端对接与CRUD设计.md @@ -0,0 +1,65 @@ +**后端 API 设计** +- 路径前缀:`/api/admin/ops_shipping_stats`(管理端鉴权)。参考路由注册与鉴权模式:`internal/router/router.go` 中 Admin 组;处理方式参照 `internal/api/admin/titles_admin.go:28/81/105/135`。 +- 接口列表: + - `GET /api/admin/ops_shipping_stats` 列表查询(分页/筛选/排序)。 + - `GET /api/admin/ops_shipping_stats/:id` 单条详情。 + - `POST /api/admin/ops_shipping_stats` 新增。 + - `PUT /api/admin/ops_shipping_stats/:id` 更新。 + - `DELETE /api/admin/ops_shipping_stats/:id` 删除。 +- 入参约定: + - 列表查询(Query):`page`、`page_size`、`shipped_start`、`shipped_end`、`product_id`、`product_name`、`user_id`、`user_name`、`express_code`、`express_no`、`order_id`、`order_no`、`order_source_type`、`payer`(字符串枚举),`keyword`(模糊匹配 `product_name/user_name/order_no/express_no`)。 + - 新增/更新(JSON):`shipped_at`(RFC3339)、`product_id`、`product_name`、`product_price_cents`、`shipped_qty`、`user_id`、`user_name`、`user_address_text`、`express_code`、`express_no`、`order_id`、`order_no`、`order_qty`、`order_amount_cents`、`profit_loss_cents`、`order_source_type`、`order_source_text`、`payer`。 +- 响应约定: + - 列表:`{ page, page_size, total, list: OpsShippingStats[] }`,默认排序 `shipped_at DESC, id DESC`。 + - 详情:`OpsShippingStats` 完整记录。 + - 新增/更新/删除:返回受影响记录或 `{ id }`。 +- 校验与错误: + - 参数绑定:`ShouldBindForm`/`ShouldBindJSON`,错误码用 `internal/code` 中 `ParamBindError`、`DatabaseError` 等。 + - 鉴权:`core.WrapAuthHandler(intc.AdminTokenAuthVerify)`。 +- DAO 使用: + - 读:`h.readDB.OpsShippingStats.WithContext(ctx).ReadDB().Where(...).FindByPage(...)`。 + - 写:`h.writeDB.OpsShippingStats.WithContext(ctx).Create/Save/Delete`。 + - 参考模型/DAO:`internal/repository/mysql/model/ops_shipping_stats.gen.go`、`internal/repository/mysql/dao/ops_shipping_stats.gen.go`;全局接入点:`internal/repository/mysql/dao/gen.go`。 +- 过滤实现: + - 时间范围:`Where(dao.OpsShippingStats.ShippedAt.Gte(shipped_start))` 与 `Lte(shipped_end)`。 + - 模糊匹配:`Like` 多字段 `Or` 组合;精确匹配:ID 字段 `Eq`。 + - 分页:`FindByPage(offset, limit)` 返回 `(list, total)`。 +- 索引建议(后续 DBA 执行): + - 组合索引:`(shipped_at DESC, product_id)`、`(user_id)`、`(order_no)`、`(express_code, express_no)`,提高查询性能。 + +**前端对接与页面** +- 菜单与路由: + - 本地模式:在 `web/admin/src/router/modules/operations.ts` 的 `children` 增加:`{ path: 'shipping-stats', name: 'ShippingStats', component: '/operations/shipping-stats', meta: { title: '发货统计', roles: ['R_SUPER','R_ADMIN'] } }`。 + - 接口模式(可选):通过系统菜单表 `menus` 新增对应子菜单;后端 `GET /api/v3/system/menus/simple` 返回;参考 `internal/api/admin/system_menu.go`。 +- API 客户端:`web/admin/src/api/shipping-stats.ts` + - `getList(params)` → `GET 'admin/ops_shipping_stats'`。 + - `getDetail(id)` → `GET 'admin/ops_shipping_stats/:id'`。 + - `create(data)` → `POST 'admin/ops_shipping_stats'`。 + - `update(id, data)` → `PUT 'admin/ops_shipping_stats/:id'`。 + - `remove(id)` → `DELETE 'admin/ops_shipping_stats/:id'`。 + - 复用 `src/utils/http/index.ts` 封装(自动附带 `Authorization`、统一错误处理)。 +- 页面视图:`web/admin/src/views/operations/shipping-stats/index.vue` + - 布局:顶部搜索(`ArtSearchBar`),表头工具(`ArtTableHeader`),数据表(`ArtTable`),分页。 + - 搜索项:时间范围、商品(ID/名称)、用户(ID/名称)、快递公司/单号、订单号、来源类型、付款人、关键字。 + - 列定义:`shipped_at`、`product_name`、`product_price_cents`(格式化为金额)、`shipped_qty`、`user_name`、`user_address_text`(截断+tooltip)、`express_code`、`express_no`、`order_no`、`order_qty`、`order_amount_cents`(金额)、`profit_loss_cents`(金额/颜色提示)、`order_source_text`、`payer`、操作列(查看/编辑/删除)。 + - 对话框:新建/编辑(表单项与后端 JSON 契约一致)。 + - 交互:列表加载用 `useTable`;增改删后按“软刷新/全量刷新”策略更新列表;统一消息提示。 + - 权限:`meta.roles` 控制访问;按钮权限按需要可用 `authList`。 + +**实施步骤** +- 后端: + - 在 `internal/api/admin` 新增 `shipping_stats_admin.go`,实现 5 个 Handler,仿照 `titles_admin.go` 的风格(入参绑定/读写分离/错误与响应)。 + - 在 `internal/router/router.go` 的 Admin 鉴权组注册 5 条路由。 +- 前端: + - 添加菜单路由项(本地或接口模式)。 + - 新建 `src/api/shipping-stats.ts`,封装 5 个方法。 + - 新建 `src/views/operations/shipping-stats/index.vue` 页面:搜索区、表格列、对话框、分页与刷新逻辑。 + +**验收标准** +- 管理端已登录用户可访问“运营管理/发货统计”。 +- 列表支持分页、筛选、排序(默认按发货时间倒序)。 +- 能创建/编辑/删除记录并实时刷新;可查看详情。 +- 后端输入校验与统一错误码生效;鉴权与日志记录正常。 +- 查询性能在典型数据量下良好(建议索引已列出)。 + +请确认以上方案,我将按该设计进行代码实现与联调。 \ No newline at end of file diff --git a/.trae/documents/后台订单管理完善方案.md b/.trae/documents/后台订单管理完善方案.md new file mode 100644 index 0000000..c84cede --- /dev/null +++ b/.trae/documents/后台订单管理完善方案.md @@ -0,0 +1,65 @@ +# 目标 +- 为管理后台补齐“订单管理”全套能力:全局订单列表/详情、筛选与搜索、状态流转(取消/发货/签收/履约)、退款、导出与对账入口;与现有用户维度订单查询保持一致风格。 + +# 现状与缺口 +- 已有:按用户查看订单、运营漏斗统计;无全局订单页与发货、退款、对账的管理操作。 +- 模型完备:`orders/order_items/shipping_records/user_points_ledger`,但缺少支付域表与真实退款流程;`CreateRefund`占位未挂路由。 + +# 后端接口设计 +- 订单列表(全局):`GET /api/admin/pay/orders` + - 筛选:`status(1/2/3/4)`、`source_type(1/2/3)`、`pay_time_range`、`created_range`、`user_id/order_no`、`is_consumed` + - 排序:`created_at/paid_at` 倒序 + - 返回:分页、订单聚合(含金额、状态、支付时间、用户信息摘要) +- 订单详情:`GET /api/admin/pay/orders/:order_no` + - 返回:订单主信息 + `order_items` + `shipping_records` + 记账流水摘要 +- 订单备注更新:`PUT /api/admin/pay/orders/:order_no/remark` +- 订单取消:`POST /api/admin/pay/orders/:order_no/cancel` + - 行为:若`status=1待支付`→置`status=3已取消`/写`cancelled_at`,幂等 +- 发货单列表:`GET /api/admin/pay/shipments` + - 筛选:`status(1待发/2已发/3已签收/4异常)`、`order_no/product_id/express_no`、时间范围 +- 创建发货:`POST /api/admin/pay/shipments` + - 入参:`order_no/order_item_id/address_id/quantity/express_code(optional)`;行为:生成`shipping_records`,将`status=1待发` +- 标记已发货:`PUT /api/admin/pay/shipments/:id/ship` + - 入参:`express_code/express_no`;行为:置`status=2已发/shipped_at` +- 标记已签收:`PUT /api/admin/pay/shipments/:id/receive` + - 行为:置`status=3已签收/received_at` +- 标记异常:`PUT /api/admin/pay/shipments/:id/abnormal` +- 履约完成(虚拟):`PUT /api/admin/pay/orders/:order_no/consume` + - 行为:`is_consumed=1`,用于虚拟资产核销;幂等 +- 退款(对接支付域): + - 创建退款:`POST /api/admin/pay/refunds`(保留当前占位实现,后续替换为真实支付退款API) + - 查询退款:`GET /api/admin/pay/refunds`、`GET /api/admin/pay/refunds/:refund_no` +- 导出:`GET /api/admin/pay/orders/export`(按筛选条件导出CSV/XLSX) +- 对账入口:`POST /api/admin/pay/bills/import`(后续结合支付域实现) + +# 服务层与数据层 +- 新增 `admin` 服务模块:封装订单聚合查询、详情组装、状态流转与发货操作;使用 Gorm 事务与条件更新保证幂等。 +- DAO 复用:`orders/order_items/shipping_records/user_points_ledger`;索引建议:`orders.order_no`唯一、`shipping_records.express_no`索引。 + +# 前端管理页 +- 新增“订单管理”菜单及页面: + - 订单列表页:筛选(状态、来源、时间、用户、订单号、履约)、表格列(订单号、用户、金额、状态、支付时间、来源、履约、备注)、操作(查看、取消、退款、履约、导出) + - 订单详情页:订单主信息、订单项、发货记录(创建/发货/签收/异常)、记账流水与日志 + - 发货管理页:发货单列表,支持搜索与状态更新 +- API 封装:在 `web/admin/src/api/` 新增 `pay-orders.ts` 与 `shipments.ts`,匹配后端接口 + +# 业务与校验 +- 金额与状态校验:取消仅允许`status=1`;履约仅允许`status=2`;发货需校验行项目数量与地址。 +- 幂等:状态流转采用条件更新;重复请求直接返回当前状态。 +- 审计:请求日志已入库(`dblogger`),增加关键操作的业务日志字段。 + +# 验收标准 +- 管理端可全局查看与筛选订单,支持详情、取消、履约、发货、签收、异常标记; +- 退款创建与查询可用(占位版本),后续升级为真实退款; +- 导出按筛选条件生成数据; +- 运营漏斗与新页面数据口径一致;编译与基础联调通过。 + +# 实施步骤 +1. 后端:新增订单与发货相关接口与服务;将退款占位路由挂载;完善查询与分页;幂等与事务。 +2. 前端:新增API文件与页面组件(列表/详情/发货);接入菜单与路由;UI按现有风格。 +3. 校验:编译与接口联调、权限校验、筛选与导出验证;漏斗口径确认。 +4. 留待增强:接入真实支付退款与对账流程;增加订单号唯一索引与支付幂等表。 + +# 风险与处理 +- 真实退款未接入:先保留管理操作占位,避免资金走错;上线前需联通支付域。 +- 并发与幂等:条件更新控制状态流;失败重试与告警结合现有日志。 \ No newline at end of file diff --git a/.trae/documents/增加采购价格与对比(ops_shipping_stats)方案.md b/.trae/documents/增加采购价格与对比(ops_shipping_stats)方案.md new file mode 100644 index 0000000..e9e3d9e --- /dev/null +++ b/.trae/documents/增加采购价格与对比(ops_shipping_stats)方案.md @@ -0,0 +1,41 @@ +**问题理解** +- 需要在发货统计中体现“采购价格”,用于与销售价格/订单金额做对比与分析。 +- 当前 `ops_shipping_stats` 仅存储销售单价 `product_price_cents`,无采购单价。 + +**数据库变更** +- 在 `ops_shipping_stats` 表新增字段: + - `purchase_price_cents BIGINT NOT NULL DEFAULT 0 COMMENT '采购单价(分)'` +- SQL(MySQL): + - `ALTER TABLE ops_shipping_stats ADD COLUMN purchase_price_cents BIGINT NOT NULL DEFAULT 0 COMMENT '采购单价(分)';` +- 生成代码:执行 gormgen 以更新 Model/DAO(`cmd/gormgen/main.go`,保持原有 DSN 与 `-tables` 参数)。 + +**后端改造** +- 控制器 `internal/api/admin/shipping_stats_admin.go`: + - 创建请求体 `CreateShippingStatRequest` 增加 `purchase_price_cents`(可选)。 + - 更新请求体 `UpdateShippingStatRequest` 增加 `purchase_price_cents`(可选)。 + - 保存/更新时写入 `purchase_price_cents`。 + - 盈亏策略:保持现有“盈亏=销售单价×发货数量 − 订单实付金额”; + - 额外提供两个对比维度(不入库,仅响应扩展字段,便于前端展示): + - `purchase_total_cents = purchase_price_cents * shipped_qty` + - `gross_profit_cents = (product_price_cents - purchase_price_cents) * shipped_qty` + - 若你希望持久化以上值,也可扩展表结构,但默认前端计算即可。 +- 列表与详情响应:保持原结构,新增返回 `purchase_price_cents`(其余对比指标前端计算)。 + +**前端改造** +- 创建/编辑表单(已两列布局): + - 左列补充“采购单价(分)”输入框,默认可为空(为空按0处理)。 + - 列表新增列: + - 采购单价(分→元格式化) + - 采购总额(分→元,前端用 `purchase_price_cents * shipped_qty` 计算) + - 毛利(分→元,前端用 `(product_price_cents - purchase_price_cents) * shipped_qty` 计算) + - 保持“盈亏”列为后端返回值显示(颜色提示不变)。 + +**验收标准** +- 新建/编辑支持录入采购单价,并正确写入与返回。 +- 列表能展示采购单价、采购总额和销售对比(毛利),并与现有盈亏并行显示。 +- 不影响既有筛选、分页与来源类型逻辑。 + +**后续可选** +- 若采购价格数据来源稳定(如采购单据),可支持远程选择或自动回填;当前先手动录入以快速上线。 + +请确认以上方案,通过后我将进行数据库字段添加、代码生成与前后端联调实现。 \ No newline at end of file diff --git a/.trae/documents/奖品管理操作栏统一为图标标签按钮的优化计划.md b/.trae/documents/奖品管理操作栏统一为图标标签按钮的优化计划.md new file mode 100644 index 0000000..efc033a --- /dev/null +++ b/.trae/documents/奖品管理操作栏统一为图标标签按钮的优化计划.md @@ -0,0 +1,13 @@ +## 目标 +- 将奖品管理页(Rewards)操作栏的“编辑/删除”文本按钮替换为统一的图标标签按钮(ArtButtonTable),与活动管理、期数管理保持一致风格。 + +## 实施点 +- 修改文件:`web/admin/src/views/activity/rewards/index.vue` +- 变更内容: + - 引入 `ArtButtonTable` 组件 + - 将 `actions` 插槽中的 `ElButton` 替换为 `ArtButtonTable type="edit"` 与 `ArtButtonTable type="delete"` +- 不改动后端接口与其它逻辑(保留删除确认与编辑弹窗)。 + +## 验收 +- 奖品管理列表操作栏显示统一的图标标签按钮,交互正常(编辑弹窗、删除确认)。 +- 前端构建通过。 \ No newline at end of file diff --git a/.trae/documents/完成支付体系全量完善的实施计划.md b/.trae/documents/完成支付体系全量完善的实施计划.md new file mode 100644 index 0000000..f41a020 --- /dev/null +++ b/.trae/documents/完成支付体系全量完善的实施计划.md @@ -0,0 +1,46 @@ +## 剩余完善项 +- 积分恢复比例与幂等:部分/全额退款的积分恢复算法与重复保护 +- 通知事件持久化与去重:payment_notify_events 入库、processed 标记、重复回调忽略 +- 退款列表与详情接口:基于 payment_refunds 的查询/分页/导出 +- 对账入口与差异列表:账单导入、生成 payment_bill_diff、差异查询与导出 +- 管理端详情字段完善:展示 transaction_id/refund_no/channel/支付方式、累计已退款与可退余额 +- 前端金额展示统一为元(两位小数),保留分用于接口与导出 +- 唯一索引与安全校验:核查并补齐关键唯一约束与条件更新 + +## 后端改造 +1) 积分恢复 +- 算法:恢复积分 = (订单积分抵扣分 × 退款分 ÷ 实付分) ÷ 100;累计不超过订单抵扣积分 +- 幂等:按订单维度累计已恢复积分对比目标;全额退款一次性恢复剩余积分 +2) 通知事件 +- 入库 payment_notify_events(notify_id,event_type,raw,processed=false);处理成功后置 processed=true;重复 notify_id 直接 ACK +3) 退款查询 +- GET /api/admin/pay/refunds:分页筛选(order_no/status/date/range) +- GET /api/admin/pay/refunds/:refund_no:详情(refund_no/amount/status/success_time/reason/raw) +4) 对账能力 +- POST /api/admin/pay/bills/import:上传/拉取账单入库 payment_bills +- GET /api/admin/pay/bills/diffs:查询差异 payment_bill_diff(缺失/金额不符/多余) +5) 订单详情聚合 +- 交易:最近一笔 payment_transactions(transaction_id/success_time/amount) +- 退款:payment_refunds 汇总(refund_no/amount/status/success_time/reason),计算累计已退与可退余额 + +## 前端改造 +- 金额格式化工具:统一元展示(两位小数),表格列与详情区全部使用 +- 详情页新增字段:transaction_id/refund_no/channel/支付方式;累计已退/可退余额(元) +- 退款记录表:元展示;状态中文映射 +- 对账入口页面:账单导入按钮、差异列表表格、导出 + +## 幂等与安全 +- 幂等键:order_no/out_trade_no/notify_id/refund_no/transaction_id +- 唯一索引:核查 payment_* 与 orders.order_no;接口层条件更新与金额校验 +- 日志与告警:验签失败、金额不一致、退款异常、对账差异 + +## 测试与验收 +- 单元:分↔积分换算、比例恢复、幂等重复、接口参数校验 +- 集成:预下单→通知→主动退款→详情一致;对账导入与差异生成 +- 前后端构建与联调通过;数据与展示口径一致 + +## 执行顺序 +1. 后端:积分恢复与幂等、通知事件入库与去重 +2. 后端:退款查询接口、对账入库与差异接口 +3. 前端:金额格式化、详情字段与退款/对账页面 +4. 测试与联调:全链路验证并修正细节 \ No newline at end of file diff --git a/.trae/documents/完成支付体系剩余执行计划.md b/.trae/documents/完成支付体系剩余执行计划.md new file mode 100644 index 0000000..4cae3e4 --- /dev/null +++ b/.trae/documents/完成支付体系剩余执行计划.md @@ -0,0 +1,16 @@ +## 剩余任务 +- 后端: + - 积分恢复比例与幂等:部分退款按比例恢复(累计不超抵扣积分)、全额一次性恢复;增加累计校验避免重复恢复 + - 通知事件入库与去重:payment_notify_events 入库(notify_id 唯一)、处理成功置 processed=true,重复直接 ACK + - 退款查询接口:GET /api/admin/pay/refunds(分页筛选),GET /api/admin/pay/refunds/:refund_no(详情) + - 对账接口:POST /api/admin/pay/bills/import(入库账单),GET /api/admin/pay/bills/diffs(差异查询与导出) + - 订单详情聚合:补充 transaction_id/success_time 与 payment_refunds 明细,计算累计已退与可退余额 +- 前端: + - 金额统一元格式化展示(两位小数),保留分用于接口与导出 + - 订单详情补充交易号/退款号/渠道/支付方式,展示累计已退与可退余额(元) + - 对账入口页面:账单导入与差异列表 +- 幂等与安全:核查唯一索引与条件更新,补充异常告警 + +## 验收 +- 覆盖单元与集成测试(换算、比例恢复、幂等、对账差异) +- 前后端构建与联调通过;数据持久化与展示口径一致 \ No newline at end of file diff --git a/.trae/documents/小程序支付与后台订单_退款设计.md b/.trae/documents/小程序支付与后台订单_退款设计.md new file mode 100644 index 0000000..8814703 --- /dev/null +++ b/.trae/documents/小程序支付与后台订单_退款设计.md @@ -0,0 +1,97 @@ +# 目标与范围 +- 目标:落地微信小程序(JSAPI)支付,完善后台订单管理与退款闭环,支持对账与运营漏斗统计。 +- 范围:预下单→前端调起→支付回调→订单状态推进→退款申请/执行→账单对账→后台运营管理。 + +# 背景与现状 +- 已有:订单/订单项/积分流水/工作台漏斗统计与请求日志。 + - 工作台漏斗接口:`internal/api/admin/dashboard_admin.go:754`;路由:`internal/router/router.go:72`。 + - 用户订单查询:`internal/service/user/orders_list.go:16/40`;系统发放订单与订单号生成:`internal/service/user/reward_grant.go:76/220`。 +- 缺失:微信支付接入(预下单、SDK、证书/签名)、支付回调与幂等、退款接口与模型、对账流程、订单唯一约束与支付幂等、防重复回调处理。 + +# 总体架构 +- 分层:Controller(HTTP) → Service(业务/状态机) → Repository(Gorm/DAO) → PayClient(微信支付SDK) → Infra(配置、证书、日志、幂等/锁)。 +- 关键域: + - 订单域:`orders/order_items`维持业务订单与行项目。 + - 支付域:新增`payment_preorders/payment_transactions/payment_refunds/payment_notify_events/payment_bills`。 + - 记账域:`user_points_ledger`扩展退款恢复落账。 + +# 数据模型设计 +- `payment_preorders` 预下单记录 + - `id`、`order_id`、`order_no`(唯一) 、`channel`(wechat_jsapi)、`prepay_id`、`out_trade_no`(唯一)、`amount_total`(分)、`payer_openid`、`status`(created/expired/paid)、`notify_url`、`created_at`、`expired_at`。 +- `payment_transactions` 支付成功交易 + - `id`、`order_id`、`order_no`(唯一) 、`channel`、`transaction_id`(微信流水号唯一) 、`amount_total`、`success_time`、`raw`(JSON) 、`created_at`。 +- `payment_refunds` 退款记录 + - `id`、`order_id`、`order_no`、`refund_no`(唯一) 、`channel`、`status`(submitted/success/abnormal/closed) 、`amount_refund`(分/支持部分退款) 、`reason`、`raw`(JSON) 、`created_at`、`success_time`。 +- `payment_notify_events` 回调事件 + - `id`、`notify_id`(唯一) 、`resource_type`、`event_type`、`summary`、`raw`(JSON) 、`processed`(bool) 、`created_at`。 +- `payment_bills`/`payment_bill_diff` 对账与差异 + - `bill_date`、`type`(tradebill/refundbill) 、`file_url`、`digest`、`imported`;差异记录`local_tx_id/wechat_tx_id/diff_type/diff_detail`。 +- 约束与索引:`orders.order_no`唯一索引;`payment_*`关键幂等字段唯一索引;`notify_id/out_trade_no/transaction_id/refund_no`均唯一。 + +# 接口设计 +- App(用户态) + - `POST /api/app/pay/wechat/jsapi/preorder`:入参`order_no/openid`,出参`timeStamp/nonceStr/package(pay=prepay_id)/signType/paySign`。 + - `GET /api/app/orders/:order_no/payment`:查询订单支付状态与交易详情。 +- Pay通知(平台回调) + - `POST /api/pay/wechat/notify`:验签/解密后入库`payment_notify_events`,幂等推进订单状态,写`payment_transactions`,更新`orders.status=2/paid_at`。 +- Admin(运营态) + - `GET /api/admin/pay/orders`:订单列表(筛选:状态、时间、渠道)。 + - `POST /api/admin/pay/refunds`:创建退款,入参`order_no/amount/reason`;支持全/部分退款。 + - `GET /api/admin/pay/refunds`/`GET /api/admin/pay/refunds/:refund_no`:查询退款状态。 + - `POST /api/admin/orders/:order_no/close`:关闭未支付订单(同时调用微信`close`)。 + - 漏斗统计继续沿用现有接口,支付口径改为“支付交易成功”(来源于transactions)。 + +# 业务流程 +- 预下单(JSAPI) + - 校验订单`status=1`与金额,绑定`openid`→调用`/v3/pay/transactions/jsapi`→落库`payment_preorders`并返回客户端调起参数。参考“JSAPI预下单与签名串”[微信支付文档]。 +- 前端调起 + - 小程序端`wx.requestPayment`使用服务端返回的`timeStamp/nonceStr/package/signType/paySign`。 +- 支付回调 + - 后端接收`notify`→验签(平台证书)与解密→事件幂等检查(`notify_id`)→查询交易状态或直接推进→更新`orders`为已支付,写`payment_transactions`与触发履约逻辑(虚拟商品使用`is_consumed`)。 +- 失败重试 + - 回调验签失败/解密失败→告警并拒收;订单未推进→后台定时查询`/v3/pay/transactions/out-trade-no/{out_trade_no}`兜底。 +- 退款 + - 管理端创建退款→调用`/v3/refund/domestic/refunds`→落库`payment_refunds`→回调/查询成功后更新为`success`并写`user_points_ledger(action=refund_restore)`与订单状态`4已退款`(支持部分退款:订单保持`2已支付`,以累计退款判断)。 +- 对账 + - 每日拉取交易/退款账单→入库`payment_bills`→与本地`payment_transactions/payment_refunds`比对→生成差异`payment_bill_diff`→运营报表与修复。 + +# 幂等与一致性 +- 订单:`order_no`唯一;预下单多次返回同一`prepay_id`;使用`out_trade_no=order_no`作为业务幂等键。 +- 回调:`notify_id`去重;状态推进使用原子事务与“当前状态→新状态”的校验。 +- 退款:`refund_no`唯一;重复请求直接返回现有状态。 +- 事务:Service层统一开启Gorm事务;对并发推进加行级乐观锁或显式`UPDATE ... WHERE status=...`。 + +# 配置与密钥管理 +- `.env`加载:`WECHAT_APPID/WECHAT_MCHID/WECHAT_SERIAL_NO/WECHAT_PRIVATE_KEY_PATH/WECHAT_API_V3_KEY/WECHAT_NOTIFY_URL`。 +- 证书:落盘PEM私钥与平台证书;SDK负责签名与应答验签。禁止将密钥提交仓库。 + +# SDK选型与规范 +- 后端Go采用官方`/wechatpay-apiv3/wechatpay-go`(API v3,支持签名/验签/回调解密)。 +- 按微信文档“JSAPI签名串四行格式与RSA签名”构造客户端参数。 + +# 安全与合规 +- 验签必做;来源白名单;请求体原样落库审计。 +- 金额单位统一“分”;防止小数误差;禁止信任前端金额。 +- 日志与告警:失败重试/签名失败/金额不一致必须告警。 + +# 测试与验收 +- 单元:签名参数构造、金额边界、幂等推进、退款部分/全额。 +- 集成:模拟回调事件、订单查询兜底、对账导入与差异比对。 +- 验收标准: + - 小程序端正常拉起并支付成功,后台订单状态正确推进; + - 管理端可发起退款并正确落账; + - 回调/对账全链路无漏处理且有幂等保障; + - 漏斗“支付用户/完成订单”口径与交易/发货一致。 + +# 实施步骤 +1. 引入微信支付Go SDK与配置加载,建立PayClient与证书管理。 +2. 建表与唯一索引:`orders.order_no`、`payment_*`系列关键字段唯一。 +3. 实现预下单接口,返回前端调起参数并记录`payment_preorders`。 +4. 实现支付回调路由:验签/解密→事件入库→幂等推进订单与交易记录。 +5. 实现退款接口与回调处理;联动`user_points_ledger`落账。 +6. 实现对账拉取与差异比对;补充运营漏斗统计数据来源为交易表。 +7. 覆盖测试与监控告警;上线灰度并观察。 + +# 参考文档(已核对) +- JSAPI预下单与签名串、调起参数与`prepay_id`有效期:`/websites/pay_weixin_qq_doc_v3` +- 官方Go SDK:`/wechatpay-apiv3/wechatpay-go` \ No newline at end of file diff --git a/.trae/documents/工会审核与成员管理完善.md b/.trae/documents/工会审核与成员管理完善.md new file mode 100644 index 0000000..1528f0c --- /dev/null +++ b/.trae/documents/工会审核与成员管理完善.md @@ -0,0 +1,58 @@ +## 现状与缺口 +- 审核未落地:`internal/service/guild/join.go:13-48` 加入逻辑固定写 `JoinStatus=2`(同意),未按 `join_mode=1` 走待审流程;接口仅 `JoinGuild/LeaveGuild`,无审批。 +- 管理端缺踢人:仅成员列表 `internal/api/admin/guild_members.go:42-73`;前端成员页无操作列 `web/admin/src/views/guild/members/index.vue:30-35`。 +- 角色字段未赋权:虽有 `owner/admin/member`(`internal/repository/mysql/model/guild_members.gen.go:19-23`),但未做权限绑定与校验。 + +## 目标与范围 +- 增加「加入申请与审核」能力:支持待审、通过、拒绝;仅当 `join_mode=1` 生效。 +- 增加「成员剔除」能力:管理端支持踢人,并记录操作日志。 +- 基础优化:完善角色权限(会长/官员)、分页/检索一致性、接口与 Swagger 描述对齐。 + +## 后端改造 +- 模型与数据流 + - 新增表 `guild_join_applications`(推荐方案):字段 `id/guild_id/user_id/apply_time/status(0待审,1通过,2拒绝)/reviewer_id/review_time/remark`。 + - `JoinGuild` 改造: + - 当 `join_mode=1` 时,仅写一条 `applications(pending)`,不立即写 `guild_members`;返回“申请已提交”。 + - 当 `join_mode=2` 时,维持现状直接入会(`JoinStatus=2`)。 + - 为兼容保留 24h 离开限制与重复加入校验。 + - 审批动作: + - 通过:写 `guild_members`(`status=1, role=member, start_time=now, join_status=2`),并更新申请为 `approved`;拒绝:仅更新申请为 `rejected`。 + - 踢人动作:新增服务 `KickMember(ctx, guildID, userID)`,仅将成员 `status=2` 并回写时间;禁止踢会长自身,需先转移会长。 +- 服务接口(`internal/service/guild/guild.go`) + - 新增:`ListApplications/ApproveApplication/RejectApplication/KickMember` 方法。 +- 控制器与路由(Admin) + - `GET /api/admin/guilds/:guild_id/applications` 列表(分页、按状态过滤)。 + - `POST /api/admin/guilds/:guild_id/applications/:id/approve`、`POST .../reject`。 + - `DELETE /api/admin/guilds/:guild_id/members/:user_id` 踢人。 + - 接口均校验:`IsSuper==1`(现行规则),并记录操作日志(`log_operation`)。 +- 控制器与路由(App,可选) + - `POST /api/app/guilds/:guild_id/applications` 提交申请;`GET /api/app/guilds/:guild_id/applications/:id` 查询状态。 +- 查询一致性 + - 成员列表 `internal/service/guild/members_list.go:9-29` 保持 `status=1` 过滤;无需受待审数据影响。 + +## 管理端前端改造 +- API 封装(`web/admin/src/api/guild.ts`) + - 增加 `fetchGetGuildApplications`、`approveGuildApplication`、`rejectGuildApplication`、`kickGuildMember`。 +- 成员页(`web/admin/src/views/guild/members/index.vue`) + - 增加操作列与「踢出」按钮;调用 `kickGuildMember`,成功后刷新当前页。 +- 新增「申请审核」页 + - 列表展示:用户ID、申请时间、状态;提供「通过/拒绝」操作;支持按状态筛选与分页。 +- 路由与菜单 + - 在 `web/admin/src/router/modules/guild.ts` 增加“工会申请审核”菜单。 + +## 权限与审计 +- 权限:管理端暂按 `IsSuper==1`;后续可拓展为“会长/官员”在 App 端可审与踢人(基于 `GuildMembers.Role` 校验)。 +- 审计:所有审批与踢人动作写入 `log_operation`,包含 guild_id/user_id/reviewer_id/动作/结果。 + +## 验收标准 +- 加入审核:当 `join_mode=1` 时,提交申请后在管理端可见;可通过/拒绝;通过后出现在成员列表。 +- 成员剔除:管理端成员页出现「踢出」操作,成功后该成员不再出现在成员列表;禁止踢会长。 +- 权限:非超管无法访问管理端相关接口;错误提示与状态码规范。 +- 文档:Swagger 增补新端点与模型;接口参数与响应示例完整。 + +## 风险与兼容 +- 数据迁移:新增 `guild_join_applications` 表(DDL 与 gorm/gen 代码生成);对现有 `guild_members` 无破坏性更改。 +- 并发与一致性:审批通过与重复申请的幂等处理;用户离开后重新申请的限制延续。 +- 回滚策略:所有写操作事务化;失败回滚不影响既有功能。 + +请确认以上方案,确认后我将分步骤提交后端与前端改造,并补齐测试与文档。 \ No newline at end of file diff --git a/.trae/documents/微信支付全量真实接入与后台订单_退款_对账方案.md b/.trae/documents/微信支付全量真实接入与后台订单_退款_对账方案.md new file mode 100644 index 0000000..011063e --- /dev/null +++ b/.trae/documents/微信支付全量真实接入与后台订单_退款_对账方案.md @@ -0,0 +1,92 @@ +## 目标 +- 以微信支付 API v3 的真实流程实现:小程序 JSAPI 预下单→前端调起→支付回调验签与解密→订单推进→退款(全额/部分)→账单拉取与对账→管理端完整的订单/退款展示与操作。 +- 严格按金额单位“分”、幂等与安全规范落地;所有敏感配置走环境变量与证书文件。 + +## 架构与分层 +- Controller:`internal/api/*`(app 用户态、admin 运营态、pay 通知态) +- Service:`internal/service/pay/*`(支付域核心:预下单、推进、退款、对账、状态机与幂等) +- Repository:`internal/repository/mysql/*`(新增支付域模型与 DAO,扩展索引) +- PayClient:`internal/pkg/pay/*`(微信官方 Go SDK client 初始化、回调验签与解密、账单下载) +- Infra:配置与证书、日志、告警、幂等键与重试策略。 + +## 数据模型(新增表与索引) +1) `payment_preorders` +- `id` PK、`order_id`、`order_no`(唯一)、`channel`(`wechat_jsapi`)、`prepay_id`、`out_trade_no`(唯一)、`amount_total`(分)、`payer_openid`、`status`(created/expired/paid)、`notify_url`、`created_at`、`expired_at` +- 索引:`order_no` 唯一、`out_trade_no` 唯一 +2) `payment_transactions` +- `id` PK、`order_id`、`order_no`(唯一)、`channel`、`transaction_id`(微信流水号唯一)、`amount_total`、`success_time`、`raw`(JSON)、`created_at` +- 索引:`transaction_id` 唯一、`order_no` 唯一 +3) `payment_refunds` +- `id` PK、`order_id`、`order_no`、`refund_no`(唯一)、`channel`、`status`(submitted/success/abnormal/closed)、`amount_refund`(分)、`reason`、`success_time`、`raw`(JSON)、`created_at` +- 索引:`refund_no` 唯一、`order_no` 索引 +4) `payment_notify_events` +- `id` PK、`notify_id`(唯一)、`resource_type`、`event_type`、`summary`、`raw`(JSON)、`processed`(bool)、`created_at` +- 索引:`notify_id` 唯一 +5) `payment_bills` / `payment_bill_diff` +- 账单拉取与差异记录,支持日级对账 +6) 订单表约束 +- `orders.order_no` 唯一索引;基于现有字段建立缺失索引 + +## 配置与证书 +- 环境变量:`WECHAT_APPID/WECHAT_MCHID/WECHAT_SERIAL_NO/WECHAT_PRIVATE_KEY_PATH/WECHAT_API_V3_KEY/WECHAT_NOTIFY_URL` +- 证书路径:`./configs/cert/apiclient_key.pem`(商户私钥);平台证书走 SDK 自动下载与缓存(`WithWechatPayAutoAuthCipher`),无需入库 +- 不提交任何密钥与证书文件到仓库 + +## SDK与真实流程 +1) 预下单(JSAPI): +- 初始化 client:`option.WithWechatPayAutoAuthCipher(mchid, serial_no, private_key, api_v3_key)` +- 调用 `services/payments/jsapi.JsapiApiService.Prepay`(或 `PrepayWithRequestPayment`)生成 `prepay_id` +- 使用四行签名串 RSA-SHA256 构造小程序 `wx.requestPayment` 参数 +- 落库 `payment_preorders` 与 `out_trade_no=order_no` 幂等 +2) 支付回调(notify): +- 使用 `notify.NewNotifyHandler(api_v3_key, NewSHA256WithRSAVerifier(certificateVisitor))` 验签并解密得到 `payments.Transaction` +- 幂等:`notify_id` 去重;如已处理直接 ACK +- 状态推进:`WHERE status=1` 条件更新订单为已支付,写入 `payment_transactions`,记录交易时间与原始报文 +- 失败场景兜底:按 `out_trade_no` 定时查询交易状态 +3) 退款(真实): +- 管理端创建退款:生成 `refund_no` 并调用 SDK `services/refunddomestic`(具体路径以官方包为准),成功回写 `payment_refunds` 与回调推进;支持部分/全额退款 +- 数据还原:积分恢复按策略记录 `user_points_ledger(action=refund_restore)`;订单全额退款置 `status=4`,部分退款保持 `status=2` 并维护累计退款 +- 幂等:`refund_no` 唯一;重复请求返回已有结果 +4) 对账: +- 下载交易/退款账单 `GET /v3/bill`;入库 `payment_bills`;比对本地 `payment_transactions/payment_refunds` 生成 `payment_bill_diff`;支持导出与修复流程 + +## 后端接口(最终版) +- App: + - `POST /api/app/pay/wechat/jsapi/preorder`(真实预下单,返回调起参数) + - `GET /api/app/orders/:order_no/payment`(查询支付状态) +- Pay通知: + - `POST /api/pay/wechat/notify`(真实验签与解密,推进订单与交易) +- Admin: + - 订单列表/详情/备注/取消/履约(已实现基础版,依据真实交易补充支付字段) + - `POST /api/admin/pay/refunds`(真实退款)/查询退款列表与详情 + - 导出与对账入口:`GET /api/admin/pay/orders/export`、`POST /api/admin/pay/bills/import` + +## 前端管理端(orders模块) +- 列表与详情中文枚举显示(已完成基础);补充:展示真实 `transaction_id`、`refund_no`、`channel`、`支付方式` +- 退款入口:支持部分退款(金额/原因),显示可退余额;展示退款记录(来源 `payment_refunds`) +- 对账入口:载入对账结果与差异列表 + +## 幂等与安全 +- 业务幂等键:`order_no`(预下单/订单推进)、`notify_id`(回调)、`refund_no`(退款) +- 事务:Service 层统一事务包裹;状态推进以条件更新控制并发 +- 日志与告警:回调验签失败、金额不一致、退款异常、对账差异均告警 + +## 测试与验收 +- 单元:签名参数构造、金额边界、幂等推进、退款全额/部分、回调解析 +- 集成:模拟回调事件、订单查询兜底、账单导入与差异比对 +- 验收标准: + - 小程序支付全链路通;订单与交易记录准确; + - 管理端退款成功并正确落账与还原; + - 对账拉取与差异比对可用; + - 漏斗“支付用户/完成订单”口径对齐真实交易。 + +## 实施阶段 +1) 数据库与DAO:新增 `payment_*` 表与索引;生成模型与DAO +2) SDK接入:统一初始化 client;预下单返回参数;notify 验签与解密;退款API封装 +3) 业务实现:订单推进、幂等、退款落账与数据还原 +4) 前端完善:展示交易/退款字段、可退余额、退款记录;对账入口 +5) 测试与联调:完成单元与集成测试;灰度上线观测 + +## 依赖 +- 官方 Go SDK:`github.com/wechatpay-apiv3/wechatpay-go`(API v3) +- 证书:商户私钥 PEM;平台证书自动下载与缓存 \ No newline at end of file diff --git a/.trae/documents/排查支付_退款入库异常与对账数据缺失.md b/.trae/documents/排查支付_退款入库异常与对账数据缺失.md new file mode 100644 index 0000000..4f03ba4 --- /dev/null +++ b/.trae/documents/排查支付_退款入库异常与对账数据缺失.md @@ -0,0 +1,28 @@ +## 你的现象与原因归纳 +- 你只看到 `payment_preorders`、`payment_notify_events` 有数据,`payment_refunds` 等为空: + - 支付回调写交易记录的地方使用了“静默写入”,失败不会报错:`internal/api/pay/wechat_notify.go:92`(`_ = ...Create(tx)`),导致有回调事件但可能没有交易入库。 + - 退款创建同样采用“静默写入”:`internal/api/admin/pay_refund_admin.go:79`,即使插入失败,接口仍返回“success”,所以你在前端看到提示,但表里没数据。 + - 对账管理的数据依赖“导入账单”后才生成差异,未导入则 `payment_bills`、`payment_bill_diff` 为空,属正常(`internal/api/admin/pay_reconcile_admin.go:37-75,154-182`)。 + +## 验证与定位步骤 +1. 查询订单详情聚合的退款:`GET admin/pay/orders/:order_no`(`internal/router/router.go:175`,实现 `internal/api/admin/pay_orders_admin.go:216-226`),确认 `refunds` 列表与 `refundable_amount` 是否合理。 +2. 查询全局退款列表:`GET admin/pay/refunds?page=1&size=20`(`internal/router/router.go:170`,实现 `internal/api/admin/pay_refund_admin.go:154-181`)。 +3. 验证 DB 表存在与权限:确认 `payment_refunds`、`payment_transactions` 表存在且可写;如果插入失败,当前代码不会抛错,需要看服务日志是否有“私钥/证书、权限、字段类型”等异常。 +4. 核验微信支付配置与回调:`configs/fat_configs.toml:34-41` 的 `wechatpay` 配置、回调地址与路由匹配 `internal/router/router.go:252`。 + +## 修复方案(代码层) +- 将静默写库改为显式错误处理并日志: + - 支付交易入库:`internal/api/pay/wechat_notify.go:92` 改为检查返回错误,失败则记录日志并返回错误,避免“有事件无交易”。 + - 退款入库:`internal/api/admin/pay_refund_admin.go:79` 改为显式处理插入错误;同时在后续积分流水与订单状态更新前确保退款记录已成功写入。 +- 接口返回语义修正:退款接口在任何一步失败时返回错误,不再统一 `success`,确保前端提示与真实入库一致。 + +## 对账管理的正确使用 +- 在“对账管理”导入每日账单(交易/退款)后再查看差异:前端入口 `web/admin/src/views/reconcile/diff/index.vue:45-76`;后端导入与差异查询分别为 `POST admin/pay/bills/import`、`GET admin/pay/bills/diff`。 + +## 交付与测试计划 +- 修改错误处理后,执行一次完整流程: + - 发起支付→收到回调→检查 `payment_transactions` 有记录。 + - 在订单详情发起退款→检查 `payment_refunds` 有记录,订单状态与积分流水一致。 + - 导入当天账单→检查 `payment_bill_diff` 能产生差异数据(如金额不一致、缺失条目)。 + +我将按以上步骤先进行只读验证(接口与日志路径),随后按修复方案更新入库错误处理并回归测试,确保你在“退款管理”与“对账管理”都能看到数据。 \ No newline at end of file diff --git a/.trae/documents/支付体系一致性整改计划.md b/.trae/documents/支付体系一致性整改计划.md new file mode 100644 index 0000000..99ff1e8 --- /dev/null +++ b/.trae/documents/支付体系一致性整改计划.md @@ -0,0 +1,44 @@ +## 审计结论摘要 +- 金额统一“分”,积分统一“元等值整数(1元=1积分)”;现有退款登记复用积分流水记录“金额/100”,语义混用。 +- 退款恢复积分在多次部分退款时可能重复恢复导致超恢复;预下单未持久化;前端金额展示直用“分”,体验不佳;订单积分抵扣落账缺失。 + +## 整改目标 +- 明确并统一金额/积分口径:分↔积分换算唯一、无混用。 +- 完善退款与积分恢复逻辑:部分退款按比例恢复、全额退款一次性恢复;幂等保护。 +- 持久化支付域关键数据:预下单/交易/退款/通知;订单详情展示真实字段。 +- 前端金额展示统一为“元”且格式化,枚举与文案一致。 + +## 具体改造项 +1) 数据结构与落账 +- 新增“货币退款流水”独立记录(或新增表`payment_refunds`并在订单详情聚合展示),将每笔退款金额以“分”记录;保留`user_points_ledger`仅记录“积分”类动作(如`refund_restore`)。 +- 在支付阶段补充“订单积分扣减流水”(action=`order_deduct`),并回填`orders.points_ledger_id`,与实际抵扣金额对应(分→积分换算)。 + +2) 退款恢复与幂等 +- 部分退款按比例恢复积分:`恢复积分 = 订单积分抵扣(分) × 本次退款金额(分) / 实付金额(分) ÷ 100`(四舍五入),累计不超过订单抵扣积分总额。 +- 全额退款一次性恢复剩余积分;为`refund_restore`增加去重幂等:唯一键`(order_no, action='refund_restore')`或累计校验。 +- 订单置“已退款”的条件与部分退款展示:保持`2已支付`,在详情中清晰展示“累计已退款金额(分)”和每笔明细。 + +3) 预下单与交易持久化 +- 引入并使用`payment_preorders/payment_transactions/payment_refunds/payment_notify_events`表: + - 预下单:落库`out_trade_no=order_no`与`prepay_id`,回填`orders.pay_preorder_id`。 + - 交易:回调落库`transaction_id/amount/success_time/raw`,订单推进。 + - 退款:真实调用后落库`refund_no/amount/status/success_time/raw`,并通过回调/查询推进状态。 + - 通知事件:记录`notify_id/event_type/raw/processed`作审计与去重。 + +4) 前端展示统一 +- 列表与详情金额展示统一格式化为“元”(保留两位小数),并保留原始分用于导出或接口。 +- 详情页展示:交易号`transaction_id`、退款号`refund_no`、渠道/支付方式;“可退余额(分/元)”与“累计已退款(分/元)”。 + +5) 安全与幂等 +- 业务幂等键:`order_no`(预下单/订单推进)、`notify_id`(回调)、`refund_no`(退款)。 +- 数据库唯一索引:`orders.order_no`、`payment_preorders.out_trade_no`、`payment_transactions.transaction_id`、`payment_refunds.refund_no`、`payment_notify_events.notify_id`。 + +## 验收与测试 +- 单元:分↔积分换算正确;部分/全额退款恢复积分计算;幂等重复请求不重复落账。 +- 集成:真实预下单→回调验证→管理端主动退款→退款落库与恢复积分→订单详情展示一致;账单对账拉取与差异比对基础验证。 + +## 执行顺序 +1. 数据表与DAO:新增支付域表与唯一索引;迁移脚本。 +2. 业务与服务:预下单持久化、回调入库、退款入库与恢复积分比例算法、幂等保护。 +3. 前端统一金额展示与详情字段扩展;导出口径对齐。 +4. 测试与联调:场景覆盖与灰度验证,文案与枚举一致。 \ No newline at end of file diff --git a/.trae/documents/支付体系最终完善—执行清单.md b/.trae/documents/支付体系最终完善—执行清单.md new file mode 100644 index 0000000..dc38a70 --- /dev/null +++ b/.trae/documents/支付体系最终完善—执行清单.md @@ -0,0 +1,19 @@ +## 后端 +- 通知事件:入库 payment_notify_events(notify_id 唯一、processed 标记)、重复回调直接 ACK +- 积分恢复:部分退款按比例恢复(累计不超订单抵扣积分)、全额退款一次性恢复;幂等累计校验 +- 退款查询:GET /api/admin/pay/refunds(分页筛选)、GET /api/admin/pay/refunds/:refund_no(详情) +- 对账接口:POST /api/admin/pay/bills/import(入库账单)、GET /api/admin/pay/bills/diffs(差异查询/导出) +- 详情聚合:补充 transaction_id/success_time 与 payment_refunds 明细,计算累计已退与可退余额 + +## 前端 +- 金额统一元格式化(两位小数);保留分供接口/导出 +- 详情展示:transaction_id/refund_no/channel/支付方式、累计已退与可退余额(元) +- 对账入口:账单导入与差异列表页面 + +## 幂等与安全 +- 幂等键与唯一索引核查:order_no/out_trade_no/notify_id/refund_no/transaction_id +- 条件更新控制并发;异常与差异告警 + +## 验收 +- 单元/集成:换算与比例恢复、幂等、预下单→通知→退款→详情一致、对账导入与差异生成 +- 前后端构建通过,联调验证无误 \ No newline at end of file diff --git a/.trae/documents/支付域持久化与服务对接执行方案.md b/.trae/documents/支付域持久化与服务对接执行方案.md new file mode 100644 index 0000000..7812274 --- /dev/null +++ b/.trae/documents/支付域持久化与服务对接执行方案.md @@ -0,0 +1,31 @@ +## 目标 +- 连接已生成的 payment_* 表与 Model/DAO,完成真实微信支付体系:预下单→通知→退款→对账;统一金额/积分;管理端与前端展示一致。 + +## 后端改造 +- 预下单(JSAPI): + - 在 `internal/api/user/pay_wechat_app.go` 调用 jsapi 预下单成功后,写入 `payment_preorders(order_id, order_no, out_trade_no=order_no, prepay_id, amount_total, payer_openid, notify_url, status, expired_at)` 并回填 `orders.pay_preorder_id`。 +- 通知验签与解密: + - 在 `internal/api/pay/wechat_notify.go` 验签解密后,写 `payment_notify_events(notify_id, event_type, raw, processed=false)` 并去重;写 `payment_transactions(order_id, order_no, transaction_id, amount_total, success_time, raw)`;条件更新 `orders.status:1→2`,写 `paid_at`。 +- 后台主动退款: + - 在 `internal/api/admin/pay_refund_admin.go` 调用微信退款后,写入 `payment_refunds(order_id, order_no, refund_no, amount_refund, status, success_time, raw, reason)`; + - 按比例恢复积分:`恢复积分 = (订单积分抵扣分 × 退款分 ÷ 实付分) ÷ 100`,累计不超过订单抵扣积分;全额退款一次性恢复剩余;全额时置 `orders.status=4`;部分退款保留 `2已支付`。 +- 对账: + - 新增服务在每日任务中拉取 `tradebill/refundbill` 入库 `payment_bills`;对比 `payment_transactions/payment_refunds` 生成 `payment_bill_diff`;导出差异。 + +## 管理端接口 +- 订单详情聚合:从 `payment_transactions/payment_refunds` 汇总展示 `transaction_id/success_time/amount_total` 与 `refund_no/amount_refund/status/success_time/reason`;计算可退余额 `actual_amount - 累计退款`。 +- 退款列表/详情:`GET /api/admin/pay/refunds`、`GET /api/admin/pay/refunds/:refund_no`。 +- 对账入口:`POST /api/admin/pay/bills/import`、`GET /api/admin/pay/bills/diffs`。 + +## 前端改造 +- 金额统一以“元”格式化展示(两位小数),接口仍用“分”; +- 订单详情增加真实字段(交易号、退款号、渠道/支付方式),展示累计已退与可退余额(元);退款记录按元显示。 + +## 幂等与安全 +- 使用唯一索引与幂等键:`order_no/notify_id/refund_no/transaction_id/out_trade_no`; +- 条件更新控制并发;异常与差异告警。 + +## 验收与测试 +- 单元:分↔积分换算、比例恢复、幂等重复; +- 集成:预下单→通知→主动退款→详情一致; +- 对账:拉取账单并生成差异;前后端构建与联调通过。 \ No newline at end of file diff --git a/.trae/documents/支付域持久化与退款幂等改造实施方案.md b/.trae/documents/支付域持久化与退款幂等改造实施方案.md new file mode 100644 index 0000000..e41e7a5 --- /dev/null +++ b/.trae/documents/支付域持久化与退款幂等改造实施方案.md @@ -0,0 +1,52 @@ +## 目标 +- 按真实微信支付流程完善一个完整的支付体系:持久化预下单/交易/退款/通知/对账,统一金额/积分口径,优化退款恢复与幂等,前后端一致展示。 + +## 数据库与模型 +- 新增表(均使用“分”为金额单位): + - payment_preorders(id, order_id, order_no[UNIQUE], channel, prepay_id, out_trade_no[UNIQUE], amount_total, payer_openid, status(created/expired/paid), notify_url, created_at, expired_at) + - payment_transactions(id, order_id, order_no[UNIQUE], channel, transaction_id[UNIQUE], amount_total, success_time, raw(JSON), created_at) + - payment_refunds(id, order_id, order_no, refund_no[UNIQUE], channel, status(submitted/success/abnormal/closed), amount_refund, reason, success_time, raw(JSON), created_at) + - payment_notify_events(id, notify_id[UNIQUE], resource_type, event_type, summary, raw(JSON), processed(bool), created_at) + - payment_bills(id, bill_date, type(tradebill/refundbill), file_url, digest, imported(bool), created_at) + - payment_bill_diff(id, bill_date, diff_type(missing/amount_mismatch/extra), local_tx_id, wechat_tx_id, detail, created_at) +- 订单唯一索引:orders.order_no[UNIQUE] +- 迁移:生成/应用 GORM 迁移脚本;保持向后兼容(不破坏现有数据)。 + +## Repository/DAO +- 生成 gorm/gen DAO 与 model 文件;在 internal/repository/mysql/dao, model 下新增 payment_* 的 gen 文件。 + +## Service 逻辑 +- 预下单: + - 校验订单 status=1、金额与归属;调用 jsapi.Prepay,落库 payment_preorders(out_trade_no=order_no, prepay_id),回填 orders.pay_preorder_id;返回调起参数(RSA-SHA256 四行签名)。 +- 支付通知: + - 验签解密 payments.Transaction;幂等(notify_id 去重)入库 payment_notify_events;写入 payment_transactions(transaction_id/raw/success_time);orders 条件推进 status:1→2,写 paid_at。 +- 退款: + - 管理端主动退款:生成 refund_no 并调用 refunddomestic.Create,入库 payment_refunds(refund_no/amount/status/raw); + - 积分恢复(统一口径): + - 部分退款按比例恢复:恢复积分 = (订单积分抵扣分 × 退款分 ÷ 实付分) ÷ 100,累计不超过订单抵扣积分;全额退款一次性恢复剩余积分;幂等校验(累计已恢复积分 vs 目标)。 + - 订单状态:全额退款置 4已退款;部分退款维持 2已支付,并维护累计已退款金额(聚合 payment_refunds)。 +- 对账: + - 每日下载交易/退款账单(依 SDK);入库 payment_bills;比对 payment_transactions/payment_refunds 生成 payment_bill_diff;提供导出与差异修复入口。 + +## Admin 接口 +- 订单详情:增加展示字段 transaction_id、累计已退款、每笔退款(refund_no/amount/status/时间/原因);可退余额=实付-累计退款。 +- 退款列表与详情:GET /api/admin/pay/refunds, GET /api/admin/pay/refunds/:refund_no; +- 对账:POST /api/admin/pay/bills/import(上传账单并入库);GET /api/admin/pay/bills/diffs(差异列表)。 + +## 前端 +- 金额统一格式化为“元”展示(两位小数),内部接口仍用“分”; +- 订单详情页增加 transaction_id/refund_no/channel/支付方式;展示累计已退款与可退余额(元); +- 退款记录表展示 refund_no/金额(元)/原因/时间; +- 保持中文枚举一致(状态/来源/发货/抽奖)。 + +## 幂等与安全 +- 幂等键:order_no(预下单/推进)、notify_id(回调)、refund_no(退款); +- 唯一索引:预下单/交易/退款/通知表关键字段; +- 条件更新:WHERE status=... 控制并发; +- 日志与告警:验签失败、金额不一致、退款异常、对账差异。 + +## 测试与验收 +- 单元:分↔积分换算、部分/全额退款恢复、幂等重复; +- 集成:真实预下单→回调→主动退款→详情一致; +- 对账:拉取账单并生成差异; +- 验收:管理端全链路可用、金额与积分口径一致、数据持久化完整。 \ No newline at end of file diff --git a/.trae/documents/澄清与验证「退款管理」与「对账管理」的数据可见性.md b/.trae/documents/澄清与验证「退款管理」与「对账管理」的数据可见性.md new file mode 100644 index 0000000..6933420 --- /dev/null +++ b/.trae/documents/澄清与验证「退款管理」与「对账管理」的数据可见性.md @@ -0,0 +1,22 @@ +## 用途说明 +- 退款管理:全局退款列表与详情,用于按条件检索所有订单的退款记录。前端 `web/admin/src/views/refunds/list/index.vue:21-31` 通过 `fetchAdminRefunds` 请求后端分页列表 `GET admin/pay/refunds`(`internal/router/router.go:169-171`;实现 `internal/api/admin/pay_refund_admin.go:154-181`)。 +- 对账管理:导入微信日账单后,对本地交易/退款与微信账单进行比对并生成差异明细。入口 `web/admin/src/views/reconcile/diff/index.vue:45-76`;后端 `POST admin/pay/bills/import` 与 `GET admin/pay/bills/diff`(`internal/router/router.go:172-173`;实现 `internal/api/admin/pay_reconcile_admin.go:37-75,154-182`)。 + +## 为什么你现在看到的现象成立 +- 订单管理能看到退款:订单详情直接按该订单号聚合退款记录 `internal/api/admin/pay_orders_admin.go:216-226`,来源同一表 `payment_refunds`。 +- 退款管理为空:通常由筛选条件或分页导致。该页面有 `order_no/status` 两个筛选项并在挂载时执行一次查询 `web/admin/src/views/refunds/list/index.vue:43-55`;若筛选为非空但不匹配当前退款,列表会显示为空。 +- 对账管理为空:只有在执行“导入账单”后才会计算并产生差异;未导入则 `payment_bill_diff` 自然为空(计算逻辑 `internal/api/admin/pay_reconcile_admin.go:77-107,109-138`)。 + +## 快速验证步骤 +1. 在“退款管理”清空筛选并点击“刷新”,或直接调用 `GET admin/pay/refunds`(默认分页 20 条)确认返回是否包含最近的退款;该接口由认证组保护,需管理员登录(`internal/router/router.go:169-171`)。 +2. 如仍为空,检查是否写入了 `payment_refunds`:创建退款的写库在 `internal/api/admin/pay_refund_admin.go:70-80`,列表读取在 `154-181`。 +3. 在“对账管理”点击“导入账单”:选择“交易账单”或“退款账单”,粘贴当日微信账单的 JSON(页面模型 `web/admin/src/views/reconcile/diff/index.vue:45-76`)。导入后查询 `GET admin/pay/bills/diff` 是否出现差异。 + +## 示例导入 JSON(退款账单) +- 格式示例:`[{"refund_no":"R202411010001","out_trade_no":"O202411010001","amount_refund":100}]` +- 交易账单示例:`[{"transaction_id":"4200001xxx","out_trade_no":"O202411010001","amount_total":100}]` + +## 接下来我将执行 +- 复核你当前管理端的筛选条件、分页与管理员登录态是否正常。 +- 指导你导入一份当日测试账单并确认差异计算是否可见。 +- 如需要,再定位具体退款记录的可见性(按 `order_no/status` 组合查询)。 \ No newline at end of file diff --git a/.trae/documents/玩家管理列表布局修复.md b/.trae/documents/玩家管理列表布局修复.md new file mode 100644 index 0000000..a987681 --- /dev/null +++ b/.trae/documents/玩家管理列表布局修复.md @@ -0,0 +1,24 @@ +## 问题定位 +- 操作列使用 `class="flex gap-2"` 包裹多个按钮(6个),但项目未引入 Tailwind,该类无效,导致按钮按块级元素竖排堆叠,视觉成“蓝色小方块纵向排布”。 + - 位置:`web/admin/src/views/player-manage/index.vue:202-234` +- 其它页普遍使用 `useSlot` 或 `ElSpace` 渲染操作列,避免换行与拥挤(如:`guild/manage/index.vue:185`、`operations/*`)。 + +## 修复方案 +- 将操作列容器从 `h('div', { class: 'flex gap-2' }, [...])` 改为 `h(ElSpace, { wrap: true, size: 6 }, [...])`,保证横向排列且自动换行。 +- 适当增大操作列宽度,避免拥挤(由 280 调整为 320)。 +- 如需更一致的实现,后续可将操作列改为 `useSlot: true` + `