feat: 新增抖音订单、游戏通行证、快照回滚、短信登录及管理后台功能,并优化支付、活动与用户服务模块,同时清理旧文档

This commit is contained in:
邹方成 2026-01-02 12:38:03 +08:00
parent 2cf9da6143
commit dc1b324aef
283 changed files with 116875 additions and 11172 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,97 +0,0 @@
name: Build docker and publish
run-name: The pipeline for docker build
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
# Docker
REPO: ${{ vars.REPO }}
DOCKER_USERNAME: ${{ vars.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ vars.DOCKER_PASSWORD}}
# Gitea
GIT_USERNAME: ${{ vars.GIT_USERNAME }}
GIT_PASSWORD: ${{ vars.GIT_PASSWORD }}
# Host SSH
SSH_HOST: ${{ vars.SSH_HOST }}
SSH_PORT: ${{ vars.SSH_PORT }}
SSH_USER: ${{ vars.SSH_USER }}
SSH_PASSWORD: ${{ vars.SSH_PASSWORD }}
# SMTP
SMTP_SERVER_ADDRESS: ${{ vars.SMTP_SERVER_ADDRESS }}
SMTP_USERNAME: ${{ vars.SMTP_USERNAME }}
SMTP_PASSWORD: ${{ vars.SMTP_PASSWORD }}
jobs:
linux:
runs-on: ubuntu-latest
strategy:
matrix:
# 使用gitea-tool-cache需要指定具体的版本号
go: ["1.24.5"]
steps:
- name: Checkout
uses: https://${{ env.GIT_USERNAME }}:${{ env.GIT_PASSWORD }}@${{ vars.DOMAIN_OF_GITEA}}/actions/checkout@v4
# 将.env环境变量配置文件拷贝致gitea runner容器
- name: copy env file to runner container
uses: https://${{ env.GIT_USERNAME }}:${{ env.GIT_PASSWORD }}@${{ vars.DOMAIN_OF_GITEA}}/actions/ssh-action@v0.1.10
with:
host: ${{ env.SSH_HOST }}
username: ${{ env.SSH_USER }}
password: ${{ env.SSH_PASSWORD }}
port: ${{ env.SSH_PORT }}
debug: true
script: |
mkdir -p /install/cicd_env_files_mini_chat
cd /install/cicd_env_files_mini_chat
docker cp ${{env.JOB_CONTAINER_NAME}}:${{gitea.WORKSPACE}}/deploy/.env ./.env
source ./.env
docker cp ${{env.JOB_CONTAINER_NAME}}:${{gitea.WORKSPACE}}/docs/${SERVICE_NAME}.json .
docker cp .env ${{ vars.RUNNER_CONTAINER_NAME }}:/.env
docker exec ${{ vars.RUNNER_CONTAINER_NAME }} /bin/bash -c "source /.env"
- name: Install Go environment
uses: https://${{ env.GIT_USERNAME }}:${{ env.GIT_PASSWORD }}@${{ vars.DOMAIN_OF_GITEA}}/actions/gitea-tool-cache@v5
with:
# 需要指定具体版本号!
go-version: ${{ matrix.go }}
- uses: https://${{ env.GIT_USERNAME }}:${{ env.GIT_PASSWORD }}@${{ vars.DOMAIN_OF_GITEA}}/actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- uses: https://${{ env.GIT_USERNAME }}:${{ env.GIT_PASSWORD }}@${{ vars.DOMAIN_OF_GITEA}}/actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Prepare GO environment
run: |
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GOPRIVATE="${{ vars.DOMAIN_OF_GITEA}}"
git config --global url."https://${{ env.GIT_USERNAME }}:${{ env.GIT_PASSWORD }}@${{ vars.DOMAIN_OF_GITEA}}/".insteadOf "https://${{ vars.DOMAIN_OF_GITEA}}/"
- name: Build and push docker image
run: |
source ${{gitea.WORKSPACE}}/deploy/.env
make docker
make publish-docker
make docker-run
- name: Build APIDOC docker image
run: |
source ${{gitea.WORKSPACE}}/deploy/.env
echo ${SERVICE_NAME}
echo ${APIDOC_CONTAINER_NAME}
echo ${SWAGGER_JSON}
make swagger-docker

View File

View File

@ -1,50 +0,0 @@
## 概览
- 后端以 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 文档与实际路由对齐,前后端调用一目了然。

View File

@ -1,143 +0,0 @@
## 项目架构对齐
- 后端框架:`gin``internal/pkg/core`),统一中间件与响应封装
- 路由分组:`internal/router/router.go` 下 APP 公开/鉴权分组可扩展 `products` 资源
- 鉴权APP 端采用 `core.WrapAuthHandler(intc.AppTokenAuthVerify)``Authorization: Bearer <token>`
- ORM/DB`gorm` + `gorm/gen``internal/repository/mysql/dao/Query`),读写分离 `GetDbR/GetDbW`
- Swagger通过注释生成`swaggo/swag`),挂载 `GET /swagger/*any`
- 商品相关模型:`product`SPU`sku``category``spec``spec_option``product_spec``sku_spec_value`、推荐位 `recommendation_slot/item`
## 接口设计
### 1) 商品列表
- 路径:`GET /api/app/products`
- 鉴权需要App Token
- 请求参数:
- `page`默认1`pageSize`默认20最大50
- `categoryId`(可选)
- `priceMin``priceMax`(可选,闭区间)
- `salesMin`(可选)
- `inStock`布尔可选默认true仅展示可售
- `sortBy``sales|price|createdAt``order``asc|desc`,默认 `desc`
- 业务规则:只返回 `status=上架` 的 SPU`inStock=true` 时需至少一个 SKU `stock-stock_locked>0`
- 响应结构:
```json
{
"total": 1234,
"currentPage": 1,
"pageSize": 20,
"list": [
{
"id": 1001,
"name": "羽绒服",
"mainImage": "https://.../xx.jpg",
"price": 299.00,
"sales": 5230,
"inStock": true,
"categoryId": 12
}
]
}
```
- 过滤/排序实现:
- 基于 `product``status/category_id/sales_count/created_at/price_min/price_max` 组合条件
- `inStock` 通过子查询/EXISTS 关联 `sku` 计算有效库存
### 2) 商品详情
- 路径:`GET /api/app/products/:id`
- 鉴权需要App Token
- 业务规则SPU `status=上架` 且可售;下架/缺货返回业务态错误码
- 响应结构:
```json
{
"id": 1001,
"name": "羽绒服",
"album": ["https://.../1.jpg", "https://.../2.jpg"],
"priceRange": {"min": 199.00, "max": 399.00},
"sales": 5230,
"stock": {"total": 235, "available": 220},
"specs": [
{"name": "颜色", "options": ["黑色", "白色"]},
{"name": "尺码", "options": ["M", "L", "XL"]}
],
"description": "高蓬松保暖...",
"service": ["7天无理由", "运费险"],
"recommendations": [
{"id": 1002, "name": "羽绒马甲", "mainImage": "...", "price": 199.00}
]
}
```
- 规格/相册/描述来源:
- 相册:`product.images_json` 或关联表(若存在),解析为数组
- 规格:`product_spec` + `spec` + `spec_option` 聚合;
- 描述/服务保障:从 `product` 富文本字段(或扩展表)读取
- 推荐逻辑:
- 优先使用 `recommendation_slot.scene='detail_related'` 的绑定数据
- 兜底:同类目类目内 `sales_count` Top N去重当前商品
## 路由与代码结构
- 路由注册:`internal/router/router.go`
- `appAuthGroup := router.Group("/api/app", core.WrapAuthHandler(intc.AppTokenAuthVerify))`
- `appAuthGroup.GET("/products", product.New(repo, logger).List)`
- `appAuthGroup.GET("/products/:id", product.New(repo, logger).Detail)`
- Handler`internal/api/product/list_app.go``internal/api/product/detail_app.go`
- 统一入参校验、容错与错误码
- Service`internal/service/product/service.go`
- 封装查询、库存计算、推荐聚合、缓存命中
- DAO查询使用 `dao.Query``GetDbR`
- DTO`internal/api/product/dto.go`(列表项与详情结构体)
## 鉴权与权限
- 使用现有 APP Token 验证中间件Swagger `@Security LoginVerifyToken`
- 接口动作无需RBAC面向APP用户但保留限流与风控扩展点
## 性能与优化500ms SLA
- 索引:
- `product(status, category_id, sales_count, created_at)` 复合索引
- `sku(product_id, status)``sku(stock, stock_locked)` 参与计算库存
- 查询:
- 列表:单次查询 + `EXISTS` 子查询校验库存;分页使用 `LIMIT/OFFSET`页码≤100
- 详情:读取 SPU + 聚合 `SUM(stock-stock_locked)` + 规格与相册多表查询
- 缓存:
- 本地 TTL 缓存:
- 列表:键 `list:{filters}:{page}:{pageSize}`TTL 30s命中后直接返回
- 详情:键 `detail:{id}`TTL 60s库存变更事件可主动失效后续扩展
- 并发合并:使用 `singleflight` 避免热点详情击穿
- 传输仅必要字段GZIP 已由中间件统一处理
- 限流:`/products` 每用户每秒 ≤10后续中间件实现可选
## 错误码与校验
- 下架:`PRODUCT_OFFSHELF`HTTP 200业务码提示“商品已下架”
- 缺货:`PRODUCT_OUT_OF_STOCK`
- 参数错误:`INVALID_PARAM`(页码、价格区间校验)
- 统一响应封装沿用 `internal/pkg/core/context.go`
## Swagger文档
- 在两个 Handler 顶部添加注释:
- `@Summary``@Description``@Tags products`
- `@Param`(列出查询参数与类型)
- `@Success 200 {object} ListResp` / `DetailResp`
- `@Router /api/app/products [get]``/api/app/products/{id} [get]`
- `@Security LoginVerifyToken`
- 生成后可在 `http://127.0.0.1:9991/swagger/index.html` 查看
## 单元测试
- Handler`httptest.NewServer` + 构造 `Authorization` 头,校验分页与过滤、错误码
- Service使用内存/模拟 DAO或事务性测试库覆盖库存计算、规格聚合、推荐兜底
- 覆盖率目标:核心逻辑 ≥70%
## 压力测试
- 工具:`hey``wrk`
- 示例:
- 列表:`hey -n 5000 -c 100 'http://localhost:9991/api/app/products?page=1&pageSize=20'`
- 详情:`wrk -t4 -c200 -d30s 'http://localhost:9991/api/app/products/1001'`
- 目标P95 ≤ 500ms错误率 < 0.1%
## 联调与交付
- 与前端约定字段与筛选参数;提供响应示例与错误码表
- 提供 Swagger 文档与可复用的 Postman/HTTP 文件
- 完成线上/测试环境联调回归
## 验收标准
- 两个接口完成并通过单元与压力测试
- Swagger 文档完整,含请求/响应示例
- P95 响应时间 ≤ 500ms测试环境数据量下
- 鉴权与状态校验生效;缓存命中率提升热点访问性能

View File

@ -1,25 +0,0 @@
## 是否变更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接口扩展并提供前端适配示例。

View File

@ -1,38 +0,0 @@
# Add Cost Analysis to Lottery Simulation
I will enhance the lottery simulation feature to include cost calculation and financial analysis.
## 1. Backend Changes (`internal/api/admin/lottery_admin.go`)
- **Update Response Structures**:
- Add `Cost` (int64) to `simulateRewardStat` (Unit cost in cents).
- Add `TotalCost` (int64) to `simulateRewardStat` (WonCount * Cost).
- Add `TotalSimulationCost` (int64) to `simulateIssueResponse`.
- Add `TotalSimulationRevenue` (int64) to `simulateIssueResponse` (TotalDraws * Activity.PriceDraw).
- Add `GrossProfit` (int64) to `simulateIssueResponse` (Revenue - Cost).
- Add `GrossProfitRate` (float64) to `simulateIssueResponse`.
- **Logic Update**:
- In `SimulateIssue`:
- Fetch the `Activity` details to get `PriceDraw`.
- Collect all `ProductID`s from the rewards.
- Batch query the `Products` table to get prices.
- Map product prices to rewards (if `ProductID > 0`).
- Calculate financial stats after the simulation loop.
## 2. Frontend Changes (`web/admin/src/views/operations/lottery-simulation/index.vue`)
- **Update API Type Definition**: Update `SimulateRewardStat` and `SimulateIssueResponse` interfaces in `api/operations.ts`.
- **UI Enhancement**:
- **Summary Cards**: Add a row of summary cards at the top of the results section showing:
- Total Revenue (总收入)
- Total Cost (总成本)
- Gross Profit (毛利润)
- Profit Margin (毛利率)
- **Table Columns**:
- Add "Unit Cost" (单价/成本) column.
- Add "Total Cost" (总发放成本) column.
## 3. Verification
- Verify compilation.
- (Self-Correction) Ensure `Price` is handled as integer (cents) and formatted correctly in frontend (divided by 100).

View File

@ -1,31 +0,0 @@
# Add Product Description Field for App Detail
## 1. Database Migration
We need to add a `description` column to the `products` table to store the product details.
**SQL Statement:**
```sql
ALTER TABLE products ADD COLUMN description TEXT COMMENT '商品详情';
```
## 2. Backend Model Update
Update the GORM model and DAO to reflect the database change.
* **Model**: Update `internal/repository/mysql/model/products.gen.go` to include the `Description` field.
* **DAO**: Update `internal/repository/mysql/dao/products.gen.go` to include the `Description` field definition and mapping.
## 3. Service Layer Update
Update `internal/service/product/product.go` to handle the new field.
* **Input Structs**: Add `Description` to `CreateProductInput` and `ModifyProductInput`.
* **Business Logic**:
* Update `CreateProduct` to save the description.
* Update `ModifyProduct` to update the description.
* Update `GetDetailForApp` to populate the `Description` field from the database instead of returning an empty string.
## 4. Admin API Update
Update the Admin API to allow creating and editing products with a description.
* **File**: `internal/api/admin/product_create.go`
* **Request Structs**: Add `description` field to `createProductRequest` and `modifyProductRequest`.
* **Handlers**: Pass the `description` from the request to the service layer.
## 5. App API Verification
* **File**: `internal/api/app/product.go`
* The `GetProductDetailForApp` function already returns a `Description` field in the response. By updating the service layer to populate this field, the App API will automatically return the correct data.

View File

@ -1,38 +0,0 @@
# 渠道管理与用户来源统计实施计划
针对您的需求,我将把现有的“小程序二维码生成”功能升级为完整的“渠道管理”系统,并增加用户来源统计。
## 1. 数据库变更 (Schema Changes)
* **新增 `channels` 表**:
* 用于定义推广渠道。
* 字段: `id` (主键), `name` (渠道名称), `code` (唯一标识, 用于二维码参数), `type` (类型: 如抖音/微信/线下), `remarks` (备注), `created_at`, `updated_at`
* **修改 `users` 表**:
* 新增 `channel_id` (BIGINT) 字段,建立与 `channels` 表的关联。
* (注意:现有 `douyin_id` 字段保留,用于业务逻辑,但统计归因将统一使用 `channel_id`)。
## 2. 后端开发 (Backend)
* **API 开发 (`internal/api/admin`)**:
* 实现渠道管理的 CRUD 接口:`List`, `Create`, `Update`, `Delete`
* **统计逻辑**: 在 `List` 接口中,同步查询 `users` 表,统计每个 `channel_id` 下的用户数量。
* **登录/注册逻辑优化 (`internal/service/user`)**:
* 修改 `LoginWeixin` (微信登录) 逻辑。
* 除了现有的 `invite_code``douyin_id`,新增支持 `channel_code` 参数。
* 逻辑: 当用户注册时,如果检测到 `channel_code`,查找对应的 `Channel` 记录,并将 `channel.ID` 写入用户表的 `channel_id` 字段。
## 3. 前端开发 (Frontend)
* **新增页面**: `运营管理` -> `渠道管理` (`web/admin/src/views/operations/channels/index.vue`)。
* **列表页**: 展示渠道名称、唯一标识(Code)、**累计注册用户数**、创建时间。
* **操作栏**: 提供“编辑”、“删除”以及 **“查看二维码”** 功能。
* **二维码生成优化**:
* 点击“查看二维码”时,自动调用生成接口,参数中自动带上当前渠道的 `code`
* (替代原有的纯手动输入二维码生成页面,或将其保留为“自定义工具”)。
## 4. 验证计划
1. **功能验证**: 在后台创建一个测试渠道,生成二维码。
2. **流程验证**: 模拟新用户携带该渠道参数登录。
3. **数据验证**: 检查数据库 `users` 表中新用户的 `channel_id` 是否正确。
4. **统计验证**: 刷新渠道管理页面,确认“注册用户数”是否+1。

View File

@ -1,36 +0,0 @@
# 修复一番赏策略库存扣减竞态问题
经检查,`internal/service/activity/strategy/ichiban.go` 中的 `GrantReward` 方法存在严重的竞态条件风险。当前实现采用“先查询库存,再更新库存”的方式,在高并发下可能导致超卖。
## 修复计划
我将参照 `default.go``reward_grant.go` 中的正确实现,对 `ichiban.go` 进行以下修改:
1. **重构 `GrantReward` 方法**
* 移除原有的 `First()` 查询逻辑。
* 改为使用**乐观锁**原子更新:
* 查询条件增加 `Quantity > 0`
* 使用 `UpdateSimple` 执行 `Quantity - 1`
* 根据 `RowsAffected` 判断扣减是否成功。
## 预期代码变更
```go
func (s *ichibanStrategy) GrantReward(ctx context.Context, userID int64, rewardID int64) error {
// 使用乐观锁原子扣减库存
result, err := s.write.ActivityRewardSettings.WithContext(ctx).Where(
s.write.ActivityRewardSettings.ID.Eq(rewardID),
s.write.ActivityRewardSettings.Quantity.Gt(0),
).UpdateSimple(s.write.ActivityRewardSettings.Quantity.Add(-1))
if err != nil {
return err
}
if result.RowsAffected == 0 {
return errors.New("sold out or reward not found")
}
return nil
}
```
此修改将彻底解决库存扣减的并发安全问题。

View File

@ -1,40 +0,0 @@
## 范围
- 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 文档与接口列表
- 简要测试记录(通过用例与关键接口返回示例)

View File

@ -1,73 +0,0 @@
## 概述
为 APP 端提供“用户背包”能力,覆盖用户资产的六类数据:积分消费明细、优惠券、道具卡、头衔、订单、中奖资产(背包)。在保留现有分项查询接口的基础上,新增缺失的 APP 端接口与一个聚合概要接口,统一鉴权与分页规范,并更新 API 文档。
## 新增接口
- GET `/api/app/users/{user_id}/inventory`
- 功能:用户背包列表(中奖资产),按时间倒序,支持分页
- 返回:`InventoryWithProduct` 列表(含商品名称、图片、等级/备注、关联活动/奖励ID
- 鉴权:`LoginVerifyToken`
- GET `/api/app/users/{user_id}/titles`
- 功能:用户头衔列表,支持分页
- 返回:`UserTitleItem` 列表称号ID、名称、来源/获得时间、可选效果)
- 鉴权:`LoginVerifyToken`
- GET `/api/app/users/{user_id}/backpack`
- 功能:聚合概要接口,返回六类资产的“计数+最近N条”用于首页/概览展示
- 查询参数:`limit=5`(各类最近条数),`include=points,coupons,item_cards,titles,orders,inventory`(可选,默认全包含)
- 返回:
- `points: {count, recent: UserPointsLedger[]}`
- `coupons: {count, recent: CouponItem[]}`
- `item_cards: {count, recent: ItemCardWithTemplate[]}`
- `titles: {count, recent: UserTitleItem[]}`
- `orders: {count, recent: Orders[]}`
- `inventory: {count, recent: InventoryWithProduct[]}`
- 鉴权:`LoginVerifyToken`
## 复用现有 APP 接口(保持不变)
- 积分明细GET `/api/app/users/{user_id}/points`(已存在)
- 积分余额GET `/api/app/users/{user_id}/points/balance`(已存在)
- 优惠券GET `/api/app/users/{user_id}/coupons`(已存在)
- 道具卡GET `/api/app/users/{user_id}/item_cards`、GET `/api/app/users/{user_id}/item_cards/uses`(已存在)
- 订单GET `/api/app/users/{user_id}/orders`(已存在)
## 数据模型(响应结构)
- `InventoryWithProduct``{ id, product_id, title, images[], activity_id, reward_id, order_id, level, created_at }`
- `UserTitleItem``{ user_title_id, title_id, title_name, acquired_at, effects?: [{key, value, unit}], expires_at? }`
- `CouponItem`(沿用现有):`{ id, name, amount, valid_start, valid_end, status, rules }`
- `ItemCardWithTemplate`(沿用现有):含模板字段与剩余次数/有效期
- `Orders`(沿用现有):按现有模型返回
- `UserPointsLedger`(沿用现有):按现有模型返回
## 技术实现
- 目录与文件:
- `internal/api/user/inventory_app.go``ListUserInventoryForApp()`(调用 `usersvc.ListInventoryWithProduct`
- `internal/api/user/titles_app.go``ListUserTitlesForApp()`(新增 usersvc 方法或复用 admin 服务逻辑)
- `internal/api/user/backpack_app.go``GetUserBackpackOverview()`各服务查询计数与最近N条
- 路由:`internal/router/router.go`
- APP 认证组注册:`/users/:user_id/inventory``/users/:user_id/titles``/users/:user_id/backpack`
- Service 层:`internal/service/user`
- `ListInventoryWithProduct(ctx, userID, page, pageSize)`(已有)
- `ListUserTitles(ctx, userID, page, pageSize)`(新增):查询用户称号关联与系统称号名称/效果
- 计数方法:各类 `CountXxx(ctx, userID)`(供概要接口聚合)
## 鉴权与约束
- 所有接口使用 `LoginVerifyToken`
- `user_id` 为路径参数,但服务端以会话 `SessionUserInfo().Id` 校验一致性,防越权
- 分页统一:`page``page_size`(默认 1/20最大 100`created_at` 倒序
## Swagger 文档
- 为新增 3 个接口添加注解:`@Summary``@Description``@Tags APP端.用户``@Security LoginVerifyToken``@Param``@Success`/`@Failure``@Router`
- 模型定义共用已有结构;为新结构 `UserTitleItem``InventoryWithProduct` 增加定义
- 执行脚本生成文档:`scripts/swagger.sh`
## 测试与验收
- 单元/集成:
- 伪造用户会话,验证分页与鉴权;数据空集场景返回空数组
- 概要接口在 `include` 过滤时仅返回所选分组
- 手工验证:
- 分别对六类接口 `curl` 测试分页与计数一致性
- Swagger 文档能正确展示并可试用
## 验收标准
- 三个新增接口可用且鉴权正确;分页与计数正确
- 概要接口耗时可控(<200ms 在百条内数据量可通过 `include` 控制
- 文档完整,前端对接字段明确;无越权与泄露

View File

@ -1,53 +0,0 @@
# 添加抽奖模拟功能 (Lottery Simulation)
我将实现一个抽奖模拟功能,允许管理员在后台模拟抽奖过程并分析概率分布,**全过程仅在内存中进行,不会修改数据库中的真实数据**。
## 1. API 接口设计
**接口地址**: `POST /api/admin/lottery/issues/:issue_id/simulate`
**请求参数 (Body)**:
```json
{
"num_users": 100, // 模拟人数
"draws_per_user": 1 // 每人抽奖次数 (总抽奖次数 = 人数 * 次数)
}
```
**返回结果**:
```json
{
"total_draws": 100, // 总模拟次数
"rewards": [
{
"reward_id": 1,
"name": "IPhone 15",
"level": 1, // 奖品等级
"original_qty": 10, // 初始库存
"won_count": 2, // 模拟中奖数
"remaining_qty": 8,// 模拟剩余库存
"actual_prob": 0.02, // 实际中奖率 (2%)
"theoretical_prob": 0.01 // 理论概率 (基于权重的 1%)
}
]
}
```
## 2. 实现细节
### A. 新增处理器 `internal/api/admin/lottery_admin.go`
我将添加 `SimulateIssue` 函数,执行以下逻辑:
1. **读取配置**: 从数据库获取指定期号 (Issue) 和奖品配置 (Reward Settings)。
2. **内存模拟**:
- 在内存中创建奖品库存的副本。
- 运行加权随机算法 `N` 次(总抽奖次数)。
- 扣减内存中的库存并记录中奖数据。
- **关键**: 绝对不会写入数据库或修改真实库存。
3. **统计计算**:
- 计算每个奖品的实际中奖率与基于权重的理论概率进行对比。
### B. 注册路由 `internal/router/router.go`
- 在 `adminAuthApiRouter` 分组下添加新路由。
- `POST /lottery/issues/:issue_id/simulate` -> `adminHandler.SimulateIssue()`
## 3. 验证计划
- **功能验证**: 使用现有的活动/期号调用模拟接口,输入不同的人数和次数,验证返回的概率分布是否符合预期(例如:高权重奖品中奖率更高,库存耗尽后不再中奖)。

View File

@ -1,27 +0,0 @@
# 故障排查与修复计划
针对您提出的四个问题,我制定了以下修复计划。我将使用中文进行回答和执行。
## 1. 虚拟发货问题 (Virtual Shipment)
* **现状**: 后端代码中已存在 `UploadVirtualShipping` 调用,但在支付回调中如果上传失败,目前是忽略错误的(`_ = ...`),导致无法确定是否成功。
* **修复**: 修改 `internal/api/pay/wechat_notify.go`,增加错误日志记录。如果虚拟发货上传失败,将在后台日志中打印详细错误原因,方便排查(如 `transaction_id` 错误或配置问题)。
## 2. 订单管理与退款 (Refund Button)
* **现状**: 您提到的“履约”可能是指发货,但对于虚拟商品,通常需要的是“退款”。目前后台订单列表缺少直观的“退款”按钮。
* **修复**:
* **前端**: 在 `web/admin/src/views/pay/orders/index.vue` 中添加“退款”按钮。
* **后端**: 对接现有的 `CreateRefund` 接口。该接口已包含完整的逻辑:调用微信退款、更新订单状态、**自动恢复用户积分**和**自动恢复优惠券**。
## 3. 道具卡添加不生效 (Item Card Issue)
* **原因**: 经查代码 `internal/service/user/item_card_add.go`,当道具卡模板不存在或状态为“未启用”时,代码直接返回 `nil` (成功),但实际上没有给用户添加卡片。这就是“不生效”且无报错的原因。
* **修复**: 修改服务层代码,当道具卡不可用时,明确返回错误(如 `errors.New("item card not found or disabled")`),让接口报错,提示您具体原因。
## 4. 优惠券添加不生效 (Coupon Issue)
* **原因**: 同上,`internal/service/user/coupon_add.go` 在优惠券模板未启用时也静默返回成功。
* **修复**: 修改服务层代码,当优惠券不可用时,明确返回错误,确保您能看到操作失败的提示。
## 执行步骤
1. **后端**: 修复道具卡和优惠券的“静默失败”逻辑,使其报错。
2. **后端**: 在支付回调中增加虚拟发货的错误日志。
3. **前端**: 在订单管理页面增加“退款”按钮并对接接口。
4. **验证**: 通过 curl 或界面操作验证修复效果。

View File

@ -1,54 +0,0 @@
# 一番赏活动创建表单优化计划
用户反馈在创建一番赏活动时,选择即时/定时开奖的页面逻辑不对。通过分析代码 `web/admin/src/views/activity/wizard/index.vue`发现目前表单的逻辑允许用户在“一番赏”模式下选择“定时开奖”或“即时开奖”但某些字段的显示逻辑可能不够清晰或存在冲突。一番赏Ichiban Kuji通常有其特定的开奖规则需要对表单进行以下优化。
## 1. 需求分析与对齐 (Align)
* **当前问题**:
* 在选择 `play_type='ichiban'` 时,表单依然展示通用的 `draw_mode`(定时/即时)选项,且后续的定时参数(如 `min_participants`, `scheduled_time` 等)可能没有正确联动。
* 一番赏的核心逻辑是“即买即开”(即时开奖),但也可能存在“定时开奖”的特殊玩法(如凑够多少人开一箱)。需要明确一番赏是否支持所有开奖模式。
* **假设**: 用户提到的“逻辑不对”可能是指一番赏**只能**是即时开奖,或者一番赏的定时开奖不应该显示某些不相关的字段(如 `min_participants` 在一番赏中可能不适用,因为一番赏是消耗库存的)。
* **修正策略**:
* 当选择“一番赏”时,默认锁定或限制 `draw_mode` 的选项。
* 如果是“一番赏”,通常是**即时开奖**(用户购买后立即知道结果)。
* 如果一番赏确实支持定时(例如:全员购买完后统一开奖),则需要明确字段。
* **根据用户语境“即时 定时的 页面逻辑不对”**,推测可能是:切换一番赏时,应该自动处理 `draw_mode`,或者某些字段不该显示。
* **优化方案**:
1. **联动逻辑**: 当 `play_type` 切换为 `ichiban` 时,强制或默认 `draw_mode``instant`(即时开奖),并禁用 `scheduled`(除非业务明确支持一番赏定时)。
2. **或者**:如果一番赏支持定时,需要隐藏不适用的字段(如 `min_participants`,因为一番赏是固定库存,可能不需要“最低参与人数”,而是“售罄即开”或“固定时间”)。
3. **UI 调整**: 简化一番赏模式下的表单,隐藏不必要的复杂定时配置。
**经确认(自我推演)**: 一番赏的典型玩法是用户购买后立即获得确定的赏品(即时)。但也存在“整箱全开”的定时玩法。但最常见的问题是:**一番赏不应该显示“最低参与人数”等凑单逻辑,因为它基于固定数量的签**。
**决定**:
* 当 `play_type === 'ichiban'` 时:
* **锁定** `draw_mode``instant`(即时开奖),并**隐藏**开奖模式选择框(或禁用)。
* **隐藏**所有定时相关的配置(`min_participants`, `time_mode`, `interval_minutes` 等)。
* 这样能避免用户配置出“定时开奖的一番赏”这种非典型且可能逻辑冲突的活动(除非后端明确支持)。
## 2. 架构设计 (Architect)
* **前端逻辑调整 (`wizard/index.vue`)**:
* 监听 `scheduledForm.play_type` 的变化。
* 如果 `play_type === 'ichiban'`
* 自动设置 `scheduledForm.draw_mode = 'instant'`
* `scheduledForm.min_participants = 0`
* `scheduledForm.interval_minutes = 0`
* 在模板中,使用 `v-if` 控制字段显示:
* 如果 `play_type === 'ichiban'`,隐藏 `draw_mode` 选择框(或者显示为只读的“即时开奖”)。
* 隐藏所有 `scheduled` 相关的输入框。
## 3. 任务拆解 (Atomize)
1. **Modify Frontend (`wizard/index.vue`)**:
* 添加 `watch` 监听 `scheduledForm.play_type`
* 修改模板中的 `v-if` 条件,针对 `ichiban` 隐藏不必要的字段。
* 或者直接在 `play_type` 选择器下方添加提示:“一番赏模式下默认为即时开奖”。
## 4. 执行步骤 (Automate)
1. **Step 1**: 修改 `web/admin/src/views/activity/wizard/index.vue`
* 在 `script setup` 中添加 `watch` 逻辑。
* 调整 `template` 中的表单项可见性。

View File

@ -1,77 +0,0 @@
## 背景与现状概要
- 技术栈:后端 GoGin 封装于 `internal/pkg/core`、GORM、Viper、Zap、Swagger前端 Vue3 + Vite + TypeScript + Pinia + Element Plus。
- 核心入口:`main.go`(服务启动与任务调度);路由中心:`internal/router/router.go`;拦截与鉴权:`internal/router/interceptor/*`
- 业务分层:处理器 `internal/api/{admin,activity,app,user,pay,common}`;服务 `internal/service/**`;数据访问 `internal/repository/mysql/{dao,model}`
- 前端管理后台:`web/admin/src/**`,接口定义集中在 `web/admin/src/api/**`
## 目标与交付物
- 代码结构梳理报告:目录分层说明 + 模块职责 + 依赖关系图Mermaid
- 无用代码清理:列出候选清理清单、逐项验证、提交可回滚的变更方案。
- API 文档(前后端):
- 后端 REST 端点清单(方法、路径、鉴权、处理器、请求/响应示例、错误码、分页约定)。
- 前端调用约定基地址、拦截器、Token 注入、模块函数到后端端点的映射)。
- 更新与补充项目根文档:`docs/说明文档.md`(规划、实施方案、进度记录)。
## 实施步骤
### 1. 架构梳理Align/Architect
- 枚举代码目录:识别后端、前端、配置、构建、脚本、生成器与产物目录。
- 生成架构图描绘后端分层Router → Interceptor → API → Service → Repo、前端分层视图 → Store → API
- 输出《代码结构总览.md》说明关键路径与职责、跨模块依赖、构建/部署要点。
### 2. 路由与端点收敛Atomize
- 解析 `internal/router/router.go` 的路由注册,枚举所有端点(含分组与中间件)。
- 关联处理器方法(如 `admin.*``activity.*` 等),抽取鉴权要求:`AdminTokenAuthVerify``AppTokenAuthVerify`、RBAC `RequireAdminAction`
- 标准化约定:分页键(`page``page_size`)、通用响应包(`code``message``data``request_id`)、错误码体系。
### 3. API 文档编制
- 后端文档生成《API文档-后端.md》
- 列表:方法、路径、处理器、鉴权、中间件、请求参数、响应示例、错误码、注意事项。
- 支付/回调、系统健康、上传等特殊端点单独章节。
- 前端文档生成《API文档-前端.md》
- 说明 Axios 基础配置BaseURL、超时、拦截器、Token 注入)。
- 列出 `web/admin/src/api/**` 模块函数到后端端点映射、入参/出参、调用示例。
### 4. 无用代码清理策略
- 判定规则:
- 未被任何文件 `import`/调用;
- 未在路由或启动流程中引用;
- 构建产物(如 `build/resources/admin/**`)、运行日志(`logs/**`
- 演示/测试脚本(如 `miniapp/pay-test/**`)、一次性生成器产物。
- 候选清单(初版):
- `internal/metrics/**`(若未启用 Prometheus
- `internal/repository/mysql/testrepo_sqlite.go`(未检索到引用);
- `cmd/**`(工具/生成器,保留或迁移到 dev-only
- `scripts/swagger.*`(构建脚本,非运行时);
- `build/resources/admin/**``logs/**`(产物与输出)。
- 清理流程:
- 逐项交叉检索引用关系 → 标注“安全删除”/“需保留”;
- 对可能未来使用的模块改为禁用配置或注释式保留,避免功能回退风险;
- 产物与日志转移到忽略或发布流程之外(完善 `.gitignore` 与构建管线)。
### 5. 文档与规范同步
- 更新 `docs/说明文档.md`:规划、实施方案、节点记录(按用户规范)。
- 在 `docs/api/` 目录落地《代码结构总览.md》《API文档-后端.md》《API文档-前端.md》。
- 所有函数在新增代码中补充函数级注释(功能、参数、返回值)。
### 6. 验收与验证Assess
- 后端:`go build`、路由完整性检查、Swagger 校验(非生产)、关键端点手测。
- 前端:`vite build`、ESLint/Stylelint、页面 API 调用冒烟测试。
- 部署:非生产环境验证 PProf、CORS、静态资源路由回退检查 `.env` 与证书安全。
## 输出物清单
- `docs/api/代码结构总览.md`(含架构图)
- `docs/api/API文档-后端.md`REST 列表与约定)
- `docs/api/API文档-前端.md`(调用契约与示例)
- 可回滚的清理变更(提交前附清单与影响评估)
## 依赖与约束
- 保留生成器/工具目录(`cmd/**`)除非确认迁移;
- 配置与证书不改动业务值,仅完善文档与忽略策略;
- 如需补充 Swagger 注解,遵循现有 `swaggo` 用法并保持最小侵入。
## 下一步
- 确认本计划后:
1) 输出架构梳理文档;
2) 生成端点清单并编制前后端 API 文档;
3) 提交清理候选与验证报告,执行安全清理。
- 如需额外约定(错误码字典、分页/排序统一规范、RBAC 角色映射),我将在文档中补充并与现有实现对齐。

View File

@ -1,52 +0,0 @@
## 痛点定位
- 新用户接口在单次请求内对每个用户做多次单表查询(资产、道具卡、优惠券、称号、最后在线时间),形成 N+1 查询,延迟随列表大小线性增加(`internal/api/admin/dashboard_admin.go``DashboardNewUsers()`)。
- 实时抽奖动态接口对每条抽奖日志逐条联查用户/期/活动/奖品,亦为 N+1 查询(`DashboardDrawStream()`)。
## 后端优化方案
- 批量聚合替代逐条查询NewUsers
- 第一步:一次查出当前页的 `user_id` 列表
- 第二步:分别对各表做分组聚合(单次查询):
- 资产:`SELECT user_id, COUNT(*) FROM user_inventory WHERE user_id IN (...) GROUP BY user_id`
- 道具卡:`SELECT user_id, COUNT(*) FROM user_item_cards WHERE status=1 AND user_id IN (...) GROUP BY user_id`
- 优惠券:`SELECT user_id, COUNT(*) FROM user_coupons WHERE user_id IN (...) GROUP BY user_id`
- 称号:`SELECT ut.user_id, st.id, st.name FROM user_titles ut LEFT JOIN system_titles st ON st.id=ut.title_id WHERE ut.user_id IN (...)`
- 最后在线:分别取各行为表 `MAX(time)``user_id` 聚合,再在内存求最大值
- 积分余额:改为批量查 `user_points` 有效积分 `GROUP BY user_id`,或接入预聚合表(见下)
- 第三步:用 `map[user_id]value` 合并到用户列表,避免每行多次往返数据库
- 连接查询替代逐条补全DrawStream
- 单条 SQL 联查:`activity_draw_logs` LEFT JOIN `users``activity_issues``activities``activity_reward_settings`,一次性返回 `nickname/activityName/issueNumber/prizeName`
- 保留 `since_id + limit` 增量拉取;避免循环内 `First()` 调用
- 预聚合与缓存
- 建议增加 `user_stats` 表(或 Redis 缓存)维护:`points_balance``inventory_count``item_card_count``coupon_count``title_list``last_online_at`
- 更新策略:
- 同步:在相关写入路径(发放积分/道具卡/优惠券/称号、抽奖、下单)更新统计
- 异步crontab 每 1-5 分钟增量刷新
- 实时抽奖:为最近 50 条结果加 3-5 秒内存缓存LRU 或 Redis
- 限流与分页
- 新用户默认 `page_size=20`,最大 50实时抽奖 `limit<=100`
- 对于“今年”范围下分页检索控制页大小,避免一次返回过多用户
## 数据库与索引
- 新建/确认索引:
- `users(created_at)``users(id)`
- `activity_draw_logs(id DESC, user_id, issue_id, reward_id, created_at)`
- `user_inventory(user_id)``user_item_cards(user_id,status)``user_coupons(user_id)``user_titles(user_id,title_id)`
- `user_points(user_id, valid_end)``user_points_ledger(user_id, created_at)`
- 可选:为 `log_request(path, created_at)` 增加 `user_id` 字段与索引,精确“最后在线时间”
## 前端协同优化
- 新用户页签切换时:防抖 200ms保留上次结果并显示加载骨架避免空白闪烁
- 实时抽奖轮询:保持 5s追加条目后裁剪到 100-200 条以保证 DOM 轻量;使用 `ref` 持有列表(已改)
- 宽度问题:动态项允许换行并分两行展示(已改),避免不可见
## 验收指标
- 新用户接口:在 `page_size=20` 时 P95 响应时间 < 200ms本地数据量下
- 实时抽奖接口:在 `limit=50` 时 P95 响应时间 < 150ms每轮轮询端到端显示时间 < 300ms
## 下一步实现内容(获批后执行)
1) 重写 `DashboardNewUsers()` 为批量聚合与合并映射
2) 重写 `DashboardDrawStream()` 为单次 LEFT JOIN 联查
3) 添加必要索引迁移脚本
4) 可选:落地 `user_stats` 预聚合与写路径刷新机制
确认后我将按上述方案逐条落地并提供压测数据与对比报告。

View File

@ -1,37 +0,0 @@
## 使用指导(立即可用)
- 叠加策略stacking_strategy
- 对“双倍概率”effect_type=6当前不生效代码仅按“概率累加+统一封顶”处理internal/service/activity/draw_with_effects.go:133-149
- 只有一个双倍效果时,保持默认即可(推荐填 1“累加封顶”或 0“最大值”不影响现状
- 统一封顶cap_value_x1000
- 约束“多个双倍效果合并后的总概率”的上限(单位千分)。
- 只有一个效果:填 0不封顶或填业务上限如 1000=100%)。
- 多个效果:按业务上限填写,如最多 30% 则填 300避免叠加超过 30%)。
## 作用范围简化(改造目标)
- 将前端“作用范围”改为:
- 下拉选择“活动 activity_ids”多选
- 依赖选择“期 issue_ids”多选基于已选活动加载期
- 可选“排除期 exclude.issue_ids”多选
- 不再要求手写 ID不展示分类/时间等无关选项。
## 前端改造
- EffectEditDialog.vue
- 为 effect_type=6 添加三组下拉:活动、期、排除期。
- 提交时构造简化版 scopes_json`{ activity_ids, issue_ids, exclude: { issue_ids } }`
- 在 UI 上为 stacking_strategy/cap_value_x1000 提供简短说明(不改后端字段)。
- EffectManagerDialog.vue
- 在列表新增“作用范围”列,展示活动/期/排除期标签(已完成范围列,可保留)。
## 数据来源与接口(不改后端)
- 活动/期下拉数据:
- 如果已有活动与期的接口:直接请求填充;
- 如暂无接口:先用静态选项或从现有管理页可获取的列表填充,下轮再对接接口。
- 不改后端:效果保存仍通过 `admin/system_titles/:title_id/effects`,携带 `params_json` 与简化 `scopes_json`
## 验收
- 在管理端为“双倍概率”新增/编辑效果:
- 可用下拉选择活动与期,不用手写;
- 保存后列表显示范围标签;
- 抽奖按选定的期过滤生效draw_with_effects.go:85-103
确认后我将实施前端下拉改造与表单说明文本,并保持后端兼容。

View File

@ -1,20 +0,0 @@
## 问题
现有漏斗将“主要流失环节”选在“完成订单”因为完成率为0%。但该模块目标是识别支付瓶颈,主要应关注“访问→下单”“下单→支付”,履约(完成订单)不应参与支付瓶颈判断;整体转化率也应以支付为准。
## 调整方案(前端)
- 文件:`web/admin/src/views/dashboard/console/modules/order-funnel.vue`
- 指标计算:
- **整体转化率**改为:`payments / visitors * 100`;新增“履约完成率”可选展示 `completions / payments * 100`
- **主要流失环节(支付链路)**只在两段中比较:
- `访问→下单` 流失率 = `1 - orders / visitors`
- `下单→支付` 流失率 = `1 - payments / orders`
- **主要流失率**对应上述最大值(保留一位小数)
- 现有四阶段显示不变;“完成订单”的比率与流失仅用于展示,不参与“支付瓶颈”指标
## 验证
- 数据示例访问723下单10支付10完成0
- 支付整体转化率:`10/723≈1.38%`
- 两段流失率:`访问→下单≈98.6%``下单→支付=0%` → 主要流失环节:下单用户
- 履约完成率:`0/10=0%`(可作为履约提示,不影响支付瓶颈判断)
确认后我将直接修改该组件的计算逻辑并构建验证。

View File

@ -1,53 +0,0 @@
## 问题概览
- 缺失模块:`@/mock/temp/commentDetail` 导致 TS2307src/components/business/comment-widget/index.vue:46
- 事件类型不匹配:`emit('search', ...)` 未在 `defineEmits` 中声明载荷activity-search、player-search
- 缺少类型声明:`crypto-js/md5` 导致 TS7016login
- 返回类型不一致:登录接口不含 `refreshToken` 导致 TS2339login
- 表格数据类型为 `unknown[]` 与组件期望 `Record<string, any>[]` 不匹配guild、banner、product
## 修复方案(不改接口协议,最小改动)
- 缺失模块补齐:新增 `web/admin/src/mock/temp/commentDetail.ts`,导出 `Comment` 类型与 `commentList: Ref<Comment[]>`
- 使 `src/components/business/comment-widget/index.vue:46` 的导入可解析。
- 事件类型声明完善:在以下文件的 `defineEmits` 中为 `search` 声明载荷类型 `Props['modelValue']`
- `src/views/activity/manage/modules/activity-search.vue:93-101,148-151`
- `src/views/player-manage/modules/player-search.vue:82-90,136-139`
- 为 `crypto-js/md5` 提供类型:新增 `web/admin/src/types/shims-crypto-js.d.ts` 内容:`declare module 'crypto-js/md5';`
- 保证 `src/views/auth/login/index.vue:117` 类型可解析。
- 登录返回类型与使用一致化:修改登录页不解构 `refreshToken`,仅解构 `token` 并调用 `userStore.setToken(token)`
- `src/views/auth/login/index.vue:223-236`;当前后端返回结构为 `{ token, is_super }`(参考后端 `internal/service/admin/login.go:22-25,62-63` 与前端 `src/api/auth.ts:8-14`)。
- 表格数据类型注解:在各视图的 `apiFn` 显式标注返回类型为 `Promise<Api.Common.PaginatedResponse<...>>`,使 `useTable` 能推导出 `TRecord`,从而 `data` 类型为记录数组:
- Guild 列表:`src/views/guild/manage/index.vue:137-173`,返回 `PaginatedResponse<{ id; name; owner_id; ... }>`
- Banner 列表:`src/views/operations/banner/index.vue:77-98`,返回 `PaginatedResponse<BannerItem>``src/api/banner.ts:3-10`)。
- Product 分类:`src/views/product/categories/index.vue:80-102`,返回 `PaginatedResponse<{ id; name; parent_id; status }>`
- Product 列表:`src/views/product/list/index.vue`(同样模式)。
- 若不方便标注,临时方案:在模板传参处显式断言 `:data="data as Record<string, any>[]"`(不推荐,先按注解方案处理)。
## 具体改动明细
- 新增 `src/mock/temp/commentDetail.ts`
- 导出 `export interface Comment { id:number; author:string; content:string; timestamp:string; replies: Comment[] }`
- `export const commentList = ref<Comment[]>([])`(保持与组件 `comments.value.push(...)` 一致)。
- 更新事件声明:
- `activity-search.vue` `defineEmits<Emits>()` 中将 `(e: 'search'): void` 改为 `(e: 'search', value: Props['modelValue']): void`
- `player-search.vue` 同步调整。
- 新增类型声明文件:`src/types/shims-crypto-js.d.ts` 写入 `declare module 'crypto-js/md5';`
- 登录页使用调整:
- 改为 `const { token } = await fetchLogin(...)`,并调用 `userStore.setToken(token)`
- useTable 返回类型注解:在上述 4 个视图 `apiFn` 的返回 `then` 中显式声明返回值类型为 `Api.Common.PaginatedResponse<...>`
## 验证步骤
- 安装依赖后(若需要):`npm i --save-dev @types/crypto-js`(可省略,因已加 shims
- 执行 `npm run build`expect无 TS 错误;资源正常打包。
- 运行本地:`npm run dev`,检查各页面:
- 评论组件能正常发布与回复。
- 搜索模块能正常触发,父组件收到带载荷的 `search` 事件。
- 登录页能正常登录并存储 `token`
- Guild、Banner、Product 列表渲染正常,分页操作无类型报错。
## 影响面与风险
- 均为类型与前端视图层改动,不涉及后端接口或数据结构变更。
- `crypto-js` 类型通过 shims 解决,不强依赖 `@types` 包。
- `useTable` 类型注解只提升类型推导,不改变运行时行为。
## 后续优化建议
- 统一在 `api/*` 层将后端分页响应封装为 `Api.Common.PaginatedResponse<T>`,视图层的 `apiFn` 直接返回该类型,减少重复 `then` 转换与类型注解。
- 为常见搜索子组件抽取 `Emits` 模板,避免事件签名遗漏。

View File

@ -1,34 +0,0 @@
## 问题分析
- 组件 `coupon-dialog.vue``setup` 中使用了:
```ts
watch(() => props.data, (newData) => { ... else { resetForm() } }, { immediate: true })
```
- `resetForm` 在源码中以 `const resetForm = () => { ... }` 形式定义在 `watch` 之后。由于 `immediate: true` 会在初始化阶段立即执行回调,导致在 `resetForm` 尚未初始化之前被调用,抛出 `ReferenceError: Cannot access 'resetForm' before initialization`
## 修复方案
1) 将 `resetForm` 改为函数声明并上移到 `watch` 之前:
```ts
function resetForm() {
form.value = { name:'', coupon_type:1, discount_type:1, discount_value:0, valid_days:30, status:1, remark:'' }
}
```
- 函数声明具备提升hoisting不会出现“未初始化”问题。
- 位置调整到 `rules``dialogTitle` 定义之后、`watch(props.data, ...)` 之前。
2) 保留 `immediate: true`(业务期望首次打开时即填充/重置),无需改动;如需更稳健可补充 `flush: 'post'`,但此处核心是函数提升顺序问题。
3) 可选强化
- 如同时监听 `visible`,建议统一在 `watch` 中使用已声明的 `resetForm``fillFormFromProps(newData)` 两个函数,避免将逻辑直接写在回调内。
- 若依赖 `formRef.resetFields()` 的时机,考虑在 `handleClose` 使用 `nextTick` 保证 DOM 与表单实例可用。
## 变更清单
- 文件:`web/admin/src/views/operations/coupons/modules/coupon-dialog.vue`
- 改动:
- 将 `resetForm` 改为函数声明并移动到 `watch` 之前。
- (可选)在 `watch` 的第三参数加入 `flush: 'post'`
## 验收
- 打开/切换数据源时不再出现 `ReferenceError`
- 创建/编辑模式下首次加载和重置行为保持一致(`immediate: true` 生效)。
确认后我将按上述方案修改代码并验证。

View File

@ -1,34 +0,0 @@
**问题定位**
- 触发点在 `internal/pkg/timeutil/timeutil.go:13-22``time.LoadLocation("Asia/Shanghai")``init` 中执行,失败则 `panic`,并将 `time.Local` 设为中国时区。
- 在 Windows 无系统 IANA tzdata、且以 `CGO_ENABLED=0` 交叉编译的静态二进制中,`time` 包找不到 `Asia/Shanghai`,导致启动时报错。
**解决方案(按优先级)**
- 方案A推荐嵌入 tzdata 到二进制
- 在应用入口 `main.go` 增加空导入:`_ "time/tzdata"`,使 `time` 包使用内嵌 IANA 数据库。
- 或在打包时添加构建标签:`-tags timetzdata`(效果等同),无需改代码。
- 方案B无代码变更随程序分发 `zoneinfo.zip`
- 复制 `$GOROOT/lib/time/zoneinfo.zip` 到可执行文件同目录,运行前设置环境变量 `ZONEINFO` 指向该文件,例如:`set ZONEINFO=%CD%\zoneinfo.zip`
- 方案C防御补充去除 `panic` 并提供容错
- 在 `timeutil` 初始化失败时,回退到固定东八区:`time.FixedZone("CST", 8*3600)`,并设置 `time.Local`,避免启动失败。
**打包命令更新**
- 将 README 中 Windows 打包命令更新为:
- `CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -tags timetzdata -trimpath -o build/bindbox.exe .`
- 若采用方案B`build` 目录放置 `zoneinfo.zip`,并在启动脚本/说明中加入 `set ZONEINFO=%~dp0zoneinfo.zip`
**验证计划**
- 本地编译并运行单测:覆盖 `internal/pkg/timeutil` 的格式化与解析函数,确认 `Asia/Shanghai` 可加载且不崩溃。
- 人工验证:在 Windows 主机上执行 `bindbox.exe` 并访问 `http://127.0.0.1:9991/system/health`,字段 `time` 正常返回中国时区时间(参考 `internal/pkg/core/core.go:418-441``timeutil.CSTLayoutString()`)。
**将实施的具体改动**
- 若选A
- 在 `main.go` 增加 `_ "time/tzdata"` 导入。
- 更新 README 的 Windows 构建命令,加入 `-tags timetzdata`
- 若选C可选增强
- 修改 `internal/pkg/timeutil/timeutil.go`,失败时不 `panic`,改为固定时区回退并日志记录。
**回滚与兼容**
- 方案A仅增加约 ~800KB 体积,无行为回滚风险。
- 方案C为防御性修改兼容现有逻辑若需恢复原严格行为可还原为 `panic`
请确认采用的方案推荐选A可附加选C增强健壮性。确认后我将按上述步骤实施并提交更改、完成打包与验证。

View File

@ -1,40 +0,0 @@
## 问题定位
- 奖品页 `web/admin/src/views/activity/rewards/index.vue` 在工具栏下方无条件渲染了批量抽奖对话框:`<BatchDrawDialog :activity="currentActivity" />`
- 该子组件内部以 `props.activity !== null` 控制 `v-model`,而当前页的 `currentActivity` 始终返回对象(非 null导致对话框初始即打开表现为点击“新增奖励”时出现“批量抽奖”。
## 修复方案
- 直接移除奖品页中的 `BatchDrawDialog` 渲染(该页仅做奖励 CRUD模拟批量抽奖逻辑属于活动管理页/模拟页)。
- 若保留(备选):使用受控显示状态,改为 `<BatchDrawDialog v-model="showBatchDrawDialog" :activity="showBatchDrawDialog ? currentActivity : null" />`,并提供单独按钮触发。默认 `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`
- 删除 `<BatchDrawDialog :activity="currentActivity" />` 与相关 `currentActivity` 计算属性。
- 在 `columns` 增加 `剩余/总量``期望概率` 两列(期望概率通过本地计算得到)。
- 在顶部新增汇总区域(使用 `computed` 基于 `data` 计算)。
- 增加简单的本地过滤控件(`ElInput`/`ElSelect`),对展示数据做 `computed` 过滤与排序。
- 删除与批量抽奖相关的无用状态(如 `showSimDialog`)。
- 删除按钮文案更明确:`批量新增奖励``新增奖励`
- 不改后端接口;所有优化为前端表现层。
## 验收
- 进入“奖励”页,点击“新增奖励”只弹奖励编辑对话框,不再出现批量抽奖对话框。
- 列表显示“剩余/总量”、“期望概率”,汇总信息正确;过滤与排序生效。
- 构建通过,交互校验正常,删除有确认提示。
## 风险与回滚
- 风险低,仅前端视图改动;如需回滚,保留原组件引用并恢复初始布局即可。

View File

@ -1,18 +0,0 @@
## 问题诊断
- 前端 `active-user.vue` 将图表数据 `xAxisLabels/chartData` 改为普通数组,未使用 Vue 响应式;页面初始为空数组且后续赋值不触发渲染,导致“用户概览没有数据”。
- 指标列表 `list` 同为普通数组,数值更新不触发视图刷新。
- 后端 `GET /api/admin/dashboard/user_overview` 正常返回,但前端未正确展示。
## 修复方案
- 将 `xAxisLabels``chartData` 改为 `ref<string[]>/ref<number[]>`,用 `.value` 填充;模板自动解包可直接绑定。
- 将指标 `list` 改为 `reactive<UserStatItem[]>`,更新项时触发视图刷新。
- 增加兜底:接口异常或空数据时显示“暂无”与零值,避免空白。
## 验证
- 后端:`curl -H 'Authorization: <token>' 'http://localhost:8000/api/admin/dashboard/user_overview?rangeType=30d'` 返回含 `chart/metrics`
- 前端:刷新工作台,“用户概述”条形图与四个指标显示数据;空数据时显示零与“暂无”。
确认后我将:
1) 更新 `web/admin/src/views/dashboard/console/modules/active-user.vue` 响应式实现;
2) 运行后端构建校验;
3) 提供前端验证说明(构建目前受其他文件类型错误影响不影响本模块联调)。

View File

@ -1,38 +0,0 @@
## 问题定位
- 现象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_*`)。确认后我将按上述方案实现代码与配置改动,并完成联调与验证。

View File

@ -1,10 +0,0 @@
## 原因推测
- 抽奖动态列表使用 `reactive<DrawStreamItem[]>([])`,在数组 `unshift/splice` 更新时可能未触发渲染;改用 `ref<DrawStreamItem[]>([])` 更稳妥。
- 文案需更明确:“昵称 在 活动名-期号 中奖 奖品名”。
## 修复项
1) 将列表改为 `ref` 并按 `.value` 更新;模板自动解包无改动。
2) 文案改为纯文本:“中奖 {{ prizeName }}”,去掉标签以避免样式干扰。
## 验证
- 首次加载立即显示返回的列表项;后续轮询追加新项;行内容符合“谁在哪一个活动中了什么奖品”。

View File

@ -1,49 +0,0 @@
## 问题确认
- 症状:在“创建活动”向导中点击“下一步”后,出现“批量抽奖”界面,用户认为逻辑错误。
- 初步研判:批量抽奖来自 `BatchDrawDialog`src/views/activity/rewards/modules/batch-draw-dialog.vue。该组件在管理页src/views/activity/manage/index.vue中常驻渲染可能因不当条件渲染或状态误触发而在向导流程中弹出。
- 路由侧现状:列表页有 `router.push({ name: 'ActivityWizard' })`,但路由模块未注册对应路由,存在跳转异常风险。
## 目标
1. 保证向导“下一步”仅在步骤间顺序前进:活动 → 期数 → 奖品,绝不触发批量抽奖。
2. 统一界面风格为项目全局规范:颜色、圆角、阴影、按钮、表单、布局与动画。
3. 保留现有功能,避免回归,完成自测与构建验证。
## 技术方案
### 1. 逻辑修复
- 向导页面(两处)严格限制导航:
- `src/views/activity/wizard/index.vue``next()` 仅在成功创建活动后 `active.value = 1`,在成功创建期数后 `active.value = 2`,移除或校验任何非最终提交阶段的 `router.push` 与其他弹窗触发。
- `src/views/activity/manage/index.vue``nextWizard()` 仅递进 `wizardActive`。确保 `BatchDrawDialog``v-model` 只由 `openBatchDraw()` 显式开启。
- 隔离批量抽奖弹窗:
- 在管理页将 `<BatchDrawDialog v-model="showBatchDrawDialog" ... />` 增加条件渲染 `v-if="!showWizard"`,确保向导弹窗期间不渲染批量抽奖组件,从根本杜绝误触发。
- 路由补全:在 `src/router/modules/activity.ts` 注册 `ActivityWizard` 路由path `/activity/wizard`component `'/activity/wizard'`),避免列表页“创建活动”跳转异常导致意外页面状态。
### 2. 风格统一
- 去除自定义渐变与玻璃拟态,改为全局样式:
- 主容器采用 `art-card` 与项目标准阴影、圆角(卡片 16px按钮/输入 12px
- 步骤指示器使用项目的圆形数字样式与轻量过渡;颜色使用主题色 `#5D87FF` 及状态色变量。
- 表单与按钮风格遵循项目统一尺寸、高度与交互36px 高度,标准阴影与过渡)。
- 统一 CSS 变量与工具类:
- 使用项目内的 CSS 变量(颜色、圆角、间距),移除局部硬编码样式。
- 采用现有 `art-card`、栅格与间距体系,保证一致性。
### 3. 验证与测试
- 开发验证:
- 在两处向导中执行完整流程(创建活动→创建期数→配置奖品),确认“下一步”不触发 `BatchDrawDialog`
- 构建检查:`npm run build` 无 TS/构建错误。
- 回归验证:
- 管理页列表的“模拟(批量抽奖)”按钮仍可正常打开 `BatchDrawDialog`
- 列表页“创建活动”跳转到向导路由正常。
## 交付物
- 修复后的向导逻辑代码(两处),隔离批量抽奖弹窗的条件渲染。
- 新增路由项 `ActivityWizard`
- 去除非统一风格的样式,替换为项目全局风格实现。
- 构建与运行验证结果说明。
## 验收标准
- 向导点击“下一步”始终进入下一步骤,不出现批量抽奖。
- 界面风格与全局统一(颜色、圆角、阴影、按钮、表单风格一致)。
- 列表页“创建活动”跳转向导正常;管理页“模拟”按钮打开批量抽奖正常。
- 构建通过,无新错误。
如确认以上方案,我将开始实施并同步每一步的修复与验证结果。

View File

@ -1,22 +0,0 @@
## 问题原因
- 数据库报错 `Invalid JSON text: "The document is empty."`,说明往 `system_titles.obtain_rules_json` 写入了空字符串 `""`
- 在 MySQL JSON 列中,空字符串不是合法 JSON需使用 `"{}"``"[]"` 等合法文本。
- 代码位置:`internal/api/admin/titles_seed.go` 初始化 `SystemTitles` 时将 `ObtainRulesJSON``ScopesJSON` 设为了空字符串。
## 修复方案
- 修改 `internal/api/admin/titles_seed.go` 的种子逻辑:
- 将 `ObtainRulesJSON` 默认值改为 `"{}"`(或 `{"type":"manual"}` 作为占位规则)。
- 将 `ScopesJSON` 默认值改为 `"{}"`(空作用域表示全局生效)。
- 保持 `SystemTitleEffects.ParamsJSON` 使用 `json.Marshal(d.Params)`(已为合法 JSON
## 执行步骤
1. 更新代码:将空字符串替换为合法 JSON 默认值 `"{}"`
2. 编译检查:`go build ./...`
3. 运行初始化:`POST /api/admin/system_titles/seed_default`,预期返回 6 个称号(新建或已存在)。
4. 验证列表:登录后调用 `GET /api/admin/system_titles?page=1&page_size=20`,应返回 `total=6`
## 风险与回滚
- 仅修改种子默认值,不影响既有数据;若此前插入失败,无需回滚。
- 如你希望 `obtain_rules_json` 有具体规则,我可以将默认值改为 `{"type":"manual"}` 或按你的要求填充。
确认后我将立即修复代码、重新初始化并验证页面数据。

View File

@ -1,32 +0,0 @@
## 问题与修复方案
- 新用户切换“本月/上月/今年”无变化:后端 `new_users` 未支持时间范围;前端未传范围参数
- 实时抽奖动态文案未达成“谁在哪一个活动中了什么奖品”
- 待办事项仍展示绑定/公会;未展示“未抽奖用户”
## 后端调整
1) 扩展 `GET /api/admin/dashboard/new_users`
- 新增参数 `period=month|last_month|year`
- 按 `users.created_at` 过滤对应范围
2) 抽奖动态数据完整化
- 已返回 `activityName/issueNumber/prizeName`;确保空值处理
3) 待办事项
- 保持返回 `taskType='undrawn'``taskLabel='从未参与抽奖'`
## 前端调整
1) 新用户模块
- `fetchNewUsers(page,pageSize,period)` 支持传 `period`
- `new-user.vue` 监听单选切换,映射“本月/上月/今年”→`month/last_month/year`
2) 实时抽奖动态
- 行文改为:`{{ nickname }} 在 {{ activityName }}-{{ issueNumber }} {{ isWinner ? '中奖 ' + prizeName : '参与' }}`
3) 待办事项
- 使用接口返回的 `taskLabel``taskType`,标签统一为 `info`,文案显示“从未参与抽奖”
## 验证
- 后端编译通过;
- 切换单选范围数据刷新;
- 抽奖动态行文本符合“谁在哪一个活动中了什么奖品”;
- 待办列表展示“未参与抽奖”的用户

View File

@ -1,49 +0,0 @@
## 问题归因
- 活动/期号为空后端仅在存在抽奖日志时填充活动信息未抽取或刚支付时为0。参考 internal/api/admin/pay_orders_admin.go:167-191。
- 中奖字段与等级:前端直接展示“是否中奖”,但应改为“中奖等级”并基于 `activity.level`/`reward_id` 显示;未抽取时应显示“待开奖”。
- 支付信息不全:只显示 `ActualAmount`,未分解“积分抵扣金额/积分数量”;`PayPreorderID` 为0时未做断言处理。
- 商品价格与金额为0抽奖订单常无商品明细或价格未赋值可根据奖励商品填充价格或以“抽奖价格×次数”为明细。
## 后端改造GetPayOrderDetail
- 文件internal/api/admin/pay_orders_admin.go
- 变更点:
1) 无抽奖日志时,解析 `order.Remark``lottery:activity:<aid>|issue:<iss>|count:<N>` 填充 `activity.activity_id/issue_id`,查询 `issue.issue_number``activity.activity_name`
2) 扩展支付信息:`payment.points_amount`(订单积分抵扣金额,单位分)、`payment.points_used`(抵扣积分数量=points_amount/10`payment.total_amount`(订单总额,单位分)。
3) 返回 `activity.count`(抽次数);若有奖励商品,补充 `activity.product_price`(商品价格,单位分)。
4) 若订单无 `order_items`,在响应中提供 `computed_items`:基于“抽奖价格×次数”构造一条展示用明细(前端优先显示 `computed_items`)。
5) `payment.pay_preorder_id` 为0时仍返回前端据此显示“-”。
## 前端改造(订单详情抽屉)
- 文件web/admin/src/views/…(订单详情抽屉组件,使用 fetchGetOrderDetail
- 变更点:
1) 活动:显示 `activity.activity_name`;下方附 `issue_number`;不显示 `(ID:0)`
2) 状态区:移除“是否中奖”;改为“中奖等级”显示 `activity.level`,若 `reward_id` 为空则显示“待开奖”。
3) 支付区:
- “实付”显示:`payment.actual_amount/100`
- 新增“积分抵扣”:`payment.points_used` 积分(约 `points_amount/100` 元)
- 新增“订单总额”:`payment.total_amount/100`
- “预订单ID”显示 `payment.pay_preorder_id`为0则显示“-”
4) 明细表:
- 优先显示 `computed_items` 的“单价/金额”,否则显示 `items`
- 若有中奖商品,显示 `activity.reward_name``activity.product_price`
## 路由与API契约
- 前端调用不变:`GET admin/pay/orders/:order_no`
- 响应新增字段:
- `activity.count``activity.product_price`
- `payment.points_amount``payment.points_used``payment.total_amount`
- `computed_items: [{name, quantity, unit_price, amount}]`
## 实施步骤
1) 后端:更新 GetPayOrderDetail 填充解析与新增字段;构造 `computed_items`
2) 前端:更新详情组件的数据映射与展示逻辑;删改“是否中奖”,补充字段渲染与空值处理。
3) 验证:
- 无抽奖日志的订单仍能显示活动名称与期号
- 积分全额支付场景显示积分抵扣与总额实付为0预订单ID为“-”
- 即时模式支付后,轮询显示中奖等级与商品信息
- 明细表显示正确单价/金额
## 验收标准
- 所有问题项均有正确数据或合理占位显示
- 即时/定时、积分/金额支付四种组合下展示正确
- 架构不破坏现有接口:前端仅增量使用新增字段

View File

@ -1,127 +0,0 @@
## 目标
* 全面清理未用代码、注释废弃块、空文件与无用测试
* 识别并重构重复代码重复率≥80%
* 保持现有功能稳定,构建与测试全部通过
* 输出对比报告与文档更新
## 范围
* 后端:`internal/**``cmd/**``migrations/**`
* 前端管理:`web/admin/**`Vue/TS/样式与公共组件)
* 通用资源:`docs/**`、脚手架与配置(不更改生产配置)
## 清理策略与工具
* 未用与死代码检测
* Go`golangci-lint`unused、deadcode、revive`go vet`
* TS/Vue`tsc --noEmit`(类型与未用导出)、`eslint`no-unused-vars/no-dead-code
* 注释废弃块识别
* 规则Grep 检索注释中出现代码结构(`func|class|export|<template>`),人工确认后删除
* 空文件/无用测试
* Glob + Read 识别空/仅注释文件;移除未被引用的测试(无匹配运行入口或全跳过)
* 重复代码检测
* 跨语言:`jscpd`Vue/TS/Go`dupl`Go
* 阈值:重复度 ≥ 80% 且行数 ≥ 20 行
* 重构原则
* 后端:抽取到 `internal/pkg/common` 或现有包的工具单元;避免交叉包循环依赖
* 前端:抽取到 `web/admin/src/components/common``utils`,保持现有风格与命名
## 执行步骤
1. 基线采集
* 读取项目结构与关键模块,记录当前构建状态(不修改)
* 运行只读分析:语义搜索/正则/Grep收集疑似未用项、注释废弃块、空文件、重复片段清单
1. 未用代码清理
* 逐文件比对引用关系Grep/语义搜索),将“未被任何入口引用”标记为候选
* 生成候选清单(含文件路径与符号名),按模块批次删除;每次删除后执行增量构建验证
1. 注释废弃块删除
* 扫描 `//``/* */``<!-- -->` 中含可编译结构的片段,人工确认后删除
* 对 SFC 中注释的 `<template>/<script>/<style>` 片段严谨处理,避免结构破坏
1. 空文件与无用测试
* 移除 0 字节/仅注释文件;对测试:无法被测试运行器加载、或所有用例被跳过的文件移除
1. 重复代码重构
* 跑相似度分析,生成报告(位置、重复度、建议合并点)
* 抽取公共方法/组件,替换调用方;保持 API 不变,变更点最小化
1. 依赖与引用更新
* 后端:修复 import前端修复路径别名与组件引用保证编译通过
1. 验证与回归
* 构建验证:`go build``tsc --noEmit`、前端 `npm run build`(或等价)
* 单/集成测试:运行现有测试;若缺失,补最小冒烟测试(关键模块)
* 功能回归:登录、活动管理、一番赏映射、承诺生成/摘要、开奖与订单查询
## 安全保障
* 分批次清理,每批次后执行构建与核心用例回归
* 只删除“未引用/重复/注释废弃/空”的候选核心路径API、路由、策略、DAO、视图谨慎处理
* 所有变更均记录到临时报告与文档
## 交付物
* 代码对比报告:删除/变更列表(文件路径、符号名、原因)
* 重复代码检测报告:重复片段与重构前后引用图
* 验收文档:构建输出、测试结果、回归清单
* 文档更新:在 `docs/代码清理` 目录建立说明与进度记录
## 本次已执行清理项2025-12-08
- 移除抽奖策略注册表冗余(保留接口类型),不影响默认策略与一番赏流程
- 删除 Guild 相关 DAO/Model 生成文件(项目内无引用,运行不受影响)
- 移除管理端 batch_users 接口与路由挂载(前端无调用)
- 移除管理端 IssueUserToken 路由挂载(前端无调用;代码文件暂保留为未引用状态)
- 构建验证:`go build ./...` 通过;`go test ./...` 存在历史用例失败(外部依赖与软删列缺失),与本次变更无关
## 文档与规范对齐
* 创建:`docs/代码清理/ALIGNMENT_代码清理.md`(范围/边界/不确定点)
* 共识:`docs/代码清理/CONSENSUS_代码清理.md`(验收标准与方案)
* 设计与任务拆分:`docs/代码清理/DESIGN_代码清理.md``TASK_代码清理.md`
* 执行与评估:`docs/代码清理/ACCEPTANCE_代码清理.md``FINAL_代码清理.md``TODO_代码清理.md`
## 后续执行说明
* 获批后:按上述步骤使用只读扫描确定候选清单→分批次提交清理补丁→每批次构建与回归验证→汇总报告与文档更新。

View File

@ -1,31 +0,0 @@
## 现象与猜测
- 运营端“称号管理”页面为空,极可能是尚未调用初始化接口或请求被鉴权拦截。
## 执行步骤
- 后端健康检查
- 确认服务已启动且数据库连接正常(启动日志不报错)。
- 管理员登录获取 Token
- 账号:`admin`;密码:`chat2025` → 先做 MD5`39f841df6f9a4768330b00d3ab9f1b4d`
- `POST /api/admin/login`,记录返回 `token`
- 初始化 6 个称号与效果
- `POST /api/admin/system_titles/seed_default`
- 期望返回:`{ created: N, exists: M, ids: [...] }`(总数 6
- 菜单补齐(后端动态菜单模式)
- `POST /api/menu/ensure_titles`
- 期望返回:`{ ensured: true, parent_id: <Operations>, menu_id: <Titles> }`
- 验证后端数据
- `GET /api/admin/system_titles?page=1&page_size=20`,请求头加 `Authorization: Bearer <token>`
- 期望:`list` 含 6 条数据,`total=6`
- 前端验证
- 进入“运营管理 → 称号管理”,若仍为空:
- 打开浏览器网络面板检查 `admin/system_titles` 响应码与返回体401 表示未登录200 且 `list=[]` 表示数据未初始化或失败(重试初始化)。
## 注意点
- 初始化接口在非认证组,便于快速拉起;列表接口需要登录。
- 之前的路由重复注册已修复(避免 Gin panic
- 若你希望把占位参数(券模板 `template_id`、奖品 `target_prize_ids`)替换为真实运营配置,我将更新 `params_json` 并再次初始化。
## 验收标准
- 初始化后,`GET /api/admin/system_titles` 返回 6 条记录;前端列表显示 6 条。
确认后我将按照以上步骤执行初始化与验证,确保页面数据正常展示。

View File

@ -1,113 +0,0 @@
## 现状与问题
* 当前 APP 抽奖接口:`POST /api/app/activities/:activity_id/issues/:issue_id/draw`,处理函数 `internal/api/activity/draw_app.go:27-46`,服务侧使用 `internal/service/activity/draw_execute.go:12-89` 的可验证随机抽样HMAC-SHA256 + 拒绝采样)。
* 现有实现按期issue维度抽取奖励已具备收据与随机性证明但不同活动类型分类/玩法)未来可能有差异化前置校验、选择规则与奖励发放流程。
* 需求:我们将有很多活动,接口到底通用还是分活动?
## 结论选择
* 接口保持通用(单一端点),后端采用策略模式按活动类别/配置差异化实现逻辑。这样:
* 前端保持统一调用与响应结构,降低复杂度与版本碎片。
* 后端可插拔扩展不同活动的校验、选择、奖励发放与效果管道,避免复制粘贴与接口膨胀。
* 与现有 `Receipt` 结构兼容,保留可验证性。
## 技术方案
* 保留通用端点:`POST /api/app/activities/:activity_id/issues/:issue_id/draw`
* 引入“活动策略”接口(示例命名):
* `ActivityDrawStrategy`
* `PreChecks(ctx, activity, issue, user) error`(余额/订单/库存/状态/时间窗/限频)
* `SelectItem(ctx, issue, baseSelector) (selectedRewardID, proof)`(可复用当前 HMAC 随机选择器作为 `baseSelector`,策略可注入权重或过滤规则)
* `GrantReward(ctx, user, rewardID) error`(扣减数量、落库收据/日志、发放道具/实物/积分/券)
* `PostEffects(ctx, user, activity, issue, rewardID)`(道具效果、称号、积分等联动)
* 策略注册与路由:
* 基于 `activity.activity_category_id` 或活动配置选择策略;默认策略沿用当前实现(完全兼容)。
* 效果管道:
* 抽奖前置/后置效果处理如限时加权、Boss 限制、用户卡片效果),设计为中间件式管道,便于组合。
* 并发与一致性:
* 引入期级别乐观扣减或分布式锁(后续落地),确保高并发下数量与日志一致。
* 支持 `X-Idempotency-Key` 防重(后续扩展),避免重复下单/重复抽取。
* 监控与审计:
* 统一埋点:抽奖尝试、成功、失败原因、库存变化、用户画像。
* 审计日志与追踪 ID 与收据关联。
## API 契约
* 请求:保持不变;可新增可选头 `X-Idempotency-Key`
* 响应:保持 `receipt` 字段结构;策略仅影响内部选择/发放过程,不破坏契约。
* 错误码:标准化前置校验失败(余额不足、活动未开始、库存不足、限频)与系统错误。
## 数据与规则
* 数量规则:`original_qty` 为期初总数,`quantity` 当前剩余;不限量为 `-1`,选择与发放逻辑需正确处理。
* 收据:继续包含随机性证明与所选项快照,具备可验证性。
* 扣减与发放:在 `GrantReward` 中统一实现(现阶段代码只返回收据,发放与扣减建议纳入策略实现)。
## 变更范围
1. 新增 `internal/service/activity/strategy/` 策略接口与默认实现;注册表按分类 ID 选择策略。
2. 在 `ExecuteDraw()` 增强:
* 解析活动与用户上下文,调用策略 `PreChecks``SelectItem``GrantReward``PostEffects`
* 默认策略复用当前 `draw_execute.go` 的随机选择器。
3. `draw_app.go` 维持统一端点,增加对用户上下文的传递(如需要),契约不变。
## 分阶段实施
1. 引入策略接口与默认策略(不改变行为)。
2. 接入活动类别路由与注册(读取 `ActivityCategoryID`)。
3. 将扣减与发放迁移到策略中,补充事务与并发控制。
4. 增强效果管道,接入道具卡、称号、积分的已有模块。
5. 增加监控与限频、幂等,完善错误码。
6. 编写单元测试与集成测试:
* 权重选择正确性与拒绝采样无偏。
* 不限量与零库存边界。
* 并发扣减一致性。
* 幂等与限频。
## 验收标准
* 单一抽奖端点适配 2+ 类活动策略,无需前端切换接口。
* 收据结构与随机性证明保持一致,验证脚本通过。
* 库存扣减与奖励发放在并发下无不一致;关键路径有审计日志。
* 测试覆盖关键流程与边界条件。
## 后续扩展
* 按活动类型开放“策略配置”与“玩法参数”在管理端,可动态调整权重、效果、限频。
* 如某玩法确有完全不同交互(例如合成/多次抽/队列结算),再考虑单独端点,但仍优先在通用端点层面通过参数与策略实现。

View File

@ -1,86 +0,0 @@
## 背景
- 当前不启用数据库外键约束FK删除由业务逻辑驱动。
- 现有删除实现多为单表删除,未做级联:如删除活动 `internal/service/activity/activity_delete.go:9`、删除期 `internal/service/activity/issue_delete.go:9`、删除用户 `internal/service/user/batch_user.go:32`
- GORM 初始化未声明外键或级联:`internal/repository/mysql/mysql.go:97`
## 目标
- 在业务层实现“逻辑级联删除”,保证删除主实体时,同步清理其关联业务数据,且性能与可控性优于 FK 级联。
## 级联清单
### 删除活动Activities.ID
- 删除活动期:`activity_issues``activity_id``internal/repository/mysql/model/activity_issues.gen.go:18`
- 期下配置与承诺:
- 奖励配置 `activity_reward_settings``issue_id``internal/repository/mysql/model/activity_reward_settings.gen.go:18`
- 活动承诺字段:`activities.commitment_*`(活动维度,已统一使用活动级承诺,不存在期级承诺表)
- 期下抽奖相关:
- 抽奖日志 `activity_draw_logs``issue_id``internal/repository/mysql/model/activity_draw_logs.gen.go:18`
- 抽奖效果 `activity_draw_effects``draw_log_id`/`issue_id``internal/repository/mysql/model/activity_draw_effects.gen.go:17,29`
- 抽奖凭据 `activity_draw_receipts``draw_log_id``internal/repository/mysql/model/activity_draw_receipts.gen.go:17`
- 活动范围效果与道具:
- 用户道具使用记录 `user_item_cards``used_activity_id` / `used_issue_id` / `used_draw_log_id``internal/repository/mysql/model/user_item_cards.gen.go:24,25,23`
- 抽奖效果快照 `activity_draw_effects``activity_id`/`issue_id``internal/repository/mysql/model/activity_draw_effects.gen.go:28,29`
- 资产与日志:
- 用户资产 `user_inventory``activity_id``internal/repository/mysql/model/user_inventory.gen.go:21`
- 公会贡献日志 `guild_contribute_logs``activity_id` / `issues_id``internal/repository/mysql/model/guild_contribute_logs.gen.go:19,20`
- 系统模板:
- 系统道具卡 `system_item_cards``activity_id` / `issue_id``internal/repository/mysql/model/system_item_cards.gen.go:23,24`
- 系统优惠券 `system_coupons``activity_id``internal/repository/mysql/model/system_coupons.gen.go:20`
- 最后删除活动主表:`activities``internal/repository/mysql/model/activities.gen.go:15`
### 删除用户Users.ID
- 用户身份与权益:
- 头衔 `user_titles``user_id``internal/repository/mysql/model/user_titles.gen.go:16`
- 领取型权益限流 `user_title_effect_claims``user_id``internal/repository/mysql/model/user_title_effect_claims.gen.go:16`
- 用户账户与地址:
- 地址 `user_addresses``user_id``internal/repository/mysql/model/user_addresses.gen.go:18`
- 积分余额 `user_points``user_id``internal/repository/mysql/model/user_points.gen.go:18`
- 积分流水 `user_points_ledger``user_id``internal/repository/mysql/model/user_points_ledger.gen.go:17`
- 用户订单与优惠:
- 订单 `orders``user_id``internal/repository/mysql/model/orders.gen.go:18`
- 用户优惠券 `user_coupons``user_id``internal/repository/mysql/model/user_coupons.gen.go:18`
- 用户资产与道具:
- 用户资产 `user_inventory``user_id``internal/repository/mysql/model/user_inventory.gen.go:18`
- 用户道具卡 `user_item_cards``user_id``internal/repository/mysql/model/user_item_cards.gen.go:18`
- 与抽奖相关:
- 抽奖效果 `activity_draw_effects``user_id` 或关联 `draw_log_id``internal/repository/mysql/model/activity_draw_effects.gen.go:18,17`
- 抽奖凭据 `activity_draw_receipts` 关联 `draw_log_id``internal/repository/mysql/model/activity_draw_receipts.gen.go:17`
- 抽奖日志 `activity_draw_logs``user_id``internal/repository/mysql/model/activity_draw_logs.gen.go:17`
- 公会关联:
- 公会成员 `guild_members``user_id``internal/repository/mysql/model/guild_members.gen.go:18`
- 公会贡献日志 `guild_contribute_logs``user_id``internal/repository/mysql/model/guild_contribute_logs.gen.go:18`
- 履约/发货:
- 发货记录 `shipping_records``user_id``internal/repository/mysql/model/shipping_records.gen.go:18`
- 运营发货统计 `ops_shipping_stats``user_id``internal/repository/mysql/model/ops_shipping_stats.gen.go:21`
- 最后软删用户:`users.deleted_at``internal/repository/mysql/model/users.gen.go:20`
## 删除顺序(事务内)
- 统一采用“从叶到根”的顺序:
1) 以日志/效果/凭据等子表为先(`activity_draw_effects``activity_draw_receipts``activity_draw_logs`
2) 再清理资产/权益/模板(`user_inventory``user_item_cards``user_titles``system_*`
3) 清理期与期下配置(`activity_issues``activity_reward_settings`
4) 删除根实体(`activities` 或软删 `users`
- 全过程包裹在单事务中,任何一步失败则回滚。
## 实现策略
- 新增业务服务方法:
- `DeleteActivityCascade(ctx, activityID)`:按“删除活动”清单顺序删除
- `DeleteUserCascade(ctx, userID)`:按“删除用户”清单顺序删除
- 技术要点:
- 批量删除使用分批(如 5k/批)避免长事务与大锁;必要时按时间/ID 片段迭代
- 统一 `WHERE` 条件与索引列(`user_id`/`activity_id`/`issue_id`/`draw_log_id`)确保扫描性能
- 软删与硬删:保留用户软删(合规与审计),其余按当前表定义硬删;如需统一软删,可后续逐表补充 `gorm.DeletedAt`
- 幂等性:每个子删除操作按条件删除,无记录时直接通过;重复调用不报错
- 审计记录操作日志操作者、对象ID、影响行数便于回溯
## 验收标准
- 删除活动时,任一期及其抽奖日志/效果/凭据、奖励配置、承诺、资产、贡献日志与相关模板均被清理;根活动删除成功。
- 删除用户时,地址/订单/优惠券/积分(余额+流水)/资产/道具/抽奖相关/公会关系/发货记录及统计均被清理;根用户软删成功。
- 全流程事务保障、失败回滚;批量删除性能稳定,无显著锁表或超时。
## 需要改造的现有入口(参考)
- 活动删除入口:`internal/service/activity/activity_delete.go:9` → 升级为调用 `DeleteActivityCascade`
- 期删除入口:`internal/service/activity/issue_delete.go:9` → 被 `DeleteActivityCascade` 内部调用(或保留独立级联)
- 用户删除入口:`internal/service/user/batch_user.go:32` → 升级为调用 `DeleteUserCascade`
## 后续动作
- 我将基于以上清单与顺序,补充两套事务级联删除实现,并为关键入口替换调用;同时补充单元测试覆盖正常/边界/异常三类用例,验证幂等与性能。

View File

@ -1,46 +0,0 @@
## 字段与行为详解(以 effect_type=6 双倍概率为例)
- 目标IDtarget_prize_ids
- 含义:指定哪些奖品可触发双倍;为空数组表示对所有奖品生效。
- 应用代码:合并目标集 `doubleTargets`internal/service/activity/draw_with_effects.go:143-145
- 概率chance_x1000
- 含义双倍触发的概率千分制1000=100%、200=20%)。多个效果的概率会累加,并受统一封顶限制。
- 应用代码:累加并按 `CapValueX1000` 统一封顶internal/service/activity/draw_with_effects.go:146-149最终判定199-207
- 周期内次数上限period_cap_times
- 业务含义一个统计周期内最多能触发几次双倍例如自然日内最多1次
- 当前实现字段已解析internal/service/activity/draw_with_effects.go:137但暂未做持久化计数与强制限制建议后续以“用户+周期”维度计数DB/Redis命中后递减或阻断。
- 叠加策略stacking_strategy
- 业务含义多效果如何合并0最大值/1累加封顶/2首个匹配
- 当前实现对“概率加成type=5”生效internal/service/activity/draw_with_effects.go:117-132对“双倍概率type=6”未使用合并逻辑仅累加+封顶)。
- 统一封顶cap_value_x1000
- 含义:对累计概率(或加成)的统一上限(单位:千分)。
- 应用代码双倍概率封顶internal/service/activity/draw_with_effects.go:147-149概率加成默认分支封顶127-131
- 包含期issue_ids/排除期exclude.issue_ids
- 含义:限定效果只在指定期生效,或在某些期不生效。
- 应用代码抽奖路径按期过滤internal/service/activity/draw_with_effects.go:85-103更全面的范围匹配在效果解析器internal/service/title/effects_resolver.go:133-155
- 包含活动activity_ids
- 含义:限定效果只在指定活动生效。
- 应用代码效果解析器支持internal/service/title/effects_resolver.go:60-69, 133-155抽奖路径当前只按期(issue)做简化过滤。
- 排序sort
- 含义:效果的展示与应用顺序;不同策略可能受顺序影响(如取最大值时先后无影响,叠加时顺序影响可忽略)。
- 应用代码:读取效果按 `Sort` 排序internal/api/admin/titles_admin.go:163internal/service/activity/draw_with_effects.go:47-49
## 作用范围简化(仅“活动”和“期”)
- 目标:将前端“作用范围”精简为:
- 包含活动 `activity_ids`
- 包含期 `issue_ids`
- 排除期 `exclude.issue_ids`(可选)
- 不再展示/保存分类category_ids、时间/地区等复杂范围。
## 实施步骤(前端改造)
1. 效果编辑对话框EffectEditDialog.vue
- 保留 effect_type=6 的参数:`target_prize_ids/chance_x1000/period_cap_times`
- 精简作用范围面板,仅保留:`activity_ids``issue_ids``exclude.issue_ids`
- 提交时 `scopes_json` 只包含上述三个字段。
2. 效果列表EffectManagerDialog.vue
- 参数展示映射只显示:目标奖品、概率、周期上限;范围只显示包含/排除期与活动。
3. 文档更新
- 在《说明文档.md》中标注“作用范围简化”并更新字段速查表。
## 注意与后续
- 周期计数未实现:若您需要严格的“周期内次数上限”,我将增加一次触发计数的服务(用户+周期维度),并在抽奖路径强制限制。
- 抽奖路径按期过滤:如需按活动过滤,请在抽奖服务中引入效果解析器返回的范围匹配结果,或将 `issue``activity` 进行绑定映射。

View File

@ -1,109 +0,0 @@
## 模型目标
- 为运营提供“发货统计”独立表,脱离业务在线查询复杂度,支持导出与审计。
- 字段覆盖:产品、价格、发货数量、用户收件信息、物流、订单聚合、盈亏、来源、垫付人、时间。
## 表结构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。
- 每日定时任务或发货事件触发增量写入,保证统计表实时/准实时更新。

View File

@ -1,78 +0,0 @@
**问题理解**
* 创建/编辑对话框单列过长,影响填写与可读性。
* 产品、用户应支持选择(远程搜索)而非手填。
* 订单来源限定为:淘宝/拼多多/京东/线下;来源类型与文本重复,应仅保留类型并在后端映射文本。
**前端改造**
* 表单布局(左右分布):
* 在 `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)`,用户便于确认。
* 表单左右分布减少滚动,分组更清晰;金额与运单等非必填项可留空。
**验收标准**
* 新建/编辑对话框为两列布局,核心字段分组合理,必填校验生效。
* 产品与用户支持远程搜索选择,选中后自动填充相关字段。
* 订单来源仅以下拉类型选择,后端返回正确文本,前端只展示文本;不再出现“来源文本/来源类型重复”问题。
* 列表/详情/增改删功能在管理端登录后完整可用。

View File

@ -1,65 +0,0 @@
**后端 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` 页面:搜索区、表格列、对话框、分页与刷新逻辑。
**验收标准**
- 管理端已登录用户可访问“运营管理/发货统计”。
- 列表支持分页、筛选、排序(默认按发货时间倒序)。
- 能创建/编辑/删除记录并实时刷新;可查看详情。
- 后端输入校验与统一错误码生效;鉴权与日志记录正常。
- 查询性能在典型数据量下良好(建议索引已列出)。
请确认以上方案,我将按该设计进行代码实现与联调。

View File

@ -1,50 +0,0 @@
## 目标
- 在管理端实时查看某一期每个 `slot_index` 对应的奖品与当前状态(名称、图片、等级、原始数量/剩余数量、是否已占用),支持分页与筛选。
## 数据来源与算法
- 奖励配置:`activity_reward_settings`name/level/product_id/original_qty/quantity/sort
- 奖品图片:`products.image`(如为实物奖关联商品)
- 位置占用:`issue_position_claims`(唯一记录已占用 slot
- 承诺种子:`activities.commitment_seed_master`HMAC-SHA256 确定性洗牌,活动级)
- 映射生成:
1. 构造长度 N 的 `slots` 列表:按各奖励 `original_qty` 重复填入 `reward_id`
2. FisherYates + `HMAC-SHA256(seed, "shuffle:"+i+"|issue:"+issueId)` 前 8 字节取模生成交换索引
3. 得到稳定映射 `slot_index → reward_id`;该映射在期内不变
## 后端接口(只读)
- 组:`/api/admin`(需登录与 RBAC`ichiban:slots:view`
- `GET /api/admin/ichiban/activities/:activity_id/issues/:issue_id/slots`
- 入参:`page``page_size`(默认 1/50`claimed`(可选 true/false 过滤已占用状态)
- 返回:
- `total_slots`N
- `list[]`{`slot_index`,`reward_id`,`reward_name`,`level`,`product_image`,`original_qty`,`remaining_qty`,`claimed`}
- `seed_version`:活动承诺版本号,`rule``level_desc_sort_asc_id_asc`
- 实现:按上述算法生成映射,联表/查询实时数量与占用状态;服务端分页
- `GET /api/admin/ichiban/issues/:issue_id/slot/:slot_index`
- 返回单个位置详情与计算证明HMAC 输入与结果),用于抽样核验
## 服务与性能
- Service`internal/service/activity/ichiban_slots_service.go`
- `BuildMapping(issueID)`读取奖励配置与种子生成映射1000+ 在 2s 内);可引入本地 LRU 缓存TTL 60s
- `QuerySlots(issueID, page,size, claimed?)`:分页拼装响应,`remaining_qty` 直接读 `activity_reward_settings.quantity`
- 性能优化:
- 映射缓存 + 仅在奖励配置或种子变更时失效;可选将完整映射预生成入表以应对更大规模(保留扩展)
- `issue_position_claims(issue_id,slot_index)``reward_id` 索引保障查询速度
## 前端管理页面
- 路由:`/operations/ichiban/slots`
- 组件:表格布局(分页、排序标签、序号列),筛选(活动/期、占用状态),图片懒加载
- 列:序号、奖品图、名称、等级标签、原始数量/剩余数量、占用状态;顶部显示 `seed_version` 与排序规则说明
## 权限与审计
- 仅管理员可访问(`RequireAdminRole` + `RequireAdminAction("ichiban:slots:view")`
- 查询操作写入操作日志(模块/期次/分页参数)
## 验收标准
- 1000+ 奖品分页加载 ≤2s映射稳定一致显示字段完整占用状态实时筛选与分页正常
## 交付内容
- 管理端两条 GET 接口与 Service
- 管理页面与 API 封装
- 单元测试:映射稳定性、分页正确性、占用过滤
- 文档:算法与接口说明

View File

@ -1,73 +0,0 @@
# 后台管理接口审计与无用代码清理
## 目标
- 全面梳理管理端(`/api/admin/*`)后端接口与前端页面调用关系,排除 App 端接口
- 标注未被前端使用的管理端接口与文件,制定安全清理方案
## 范围
- 后端:`internal/router/router.go` 管理端分组与 `internal/api/admin/*`
- 前端:`web/admin/src/api/*``web/admin/src/views/*` 中使用的管理端接口
- 排除:`/api/app/*` 与模板系统管理接口(`/api/user/*`, `/api/role/*`, `/api/v3/system/menus/simple`
## 关键发现
- 路由注册位置:`internal/router/router.go` 管理端非鉴权与鉴权分组
- 非鉴权:
- `GET /api/admin/license/status`(内联处理器)· `internal/router/router.go:53`
- `POST /api/admin/login``Login()` · `internal/router/router.go:57``internal/api/admin/login.go`
- 鉴权组主要接口(节选,完整见路由文件):
- 活动与期次/奖励/随机承诺/抽奖:`/api/admin/activities/*` · `internal/router/router.go:72-90`
- 抽奖凭据:`GET /draw_receipts/:draw_id``GET /draw_receipts/log/:log_id` · `internal/router/router.go:91-92`
- 批量用户:`POST/DELETE /batch_users` · `internal/router/router.go:93-94`
- 工会:`/guilds/*` · `internal/router/router.go:96-100`
- 商品与分类:`/product_categories/*``/products/*` · `internal/router/router.go:103-110`
- 轮播图:`/banners/*` · `internal/router/router.go:113-116`
- 玩家:`/users/*`(邀请、订单、优惠券、积分、库存、道具卡、奖励发放)· `internal/router/router.go:119-129,141`
- 系统道具卡/优惠券:`/system_item_cards/*``/system_coupons/*` · `internal/router/router.go:132-140`
- 示例后端处理函数位置:
- 抽奖凭据:`GetDrawReceipt()``GetDrawReceiptByLogID()` · `internal/api/admin/draw_receipt.go:31-75,77-121`
- 前端管理端接口使用(以 `VITE_API_URL=/api` 为前缀,`url: 'admin/...'``/api/admin/...`
- 登录:`POST admin/login` · `web/admin/src/api/auth.ts:11`
- 活动/期次/奖励/随机承诺/抽奖:`admin/activities/*` · `web/admin/src/api/adminActivities.ts:13,29,33,37,47,57,68,75,93,112,133,140,146,159,172,188,210,247,274,299,323,347`
- 批量用户:`admin/batch_users` · `web/admin/src/api/adminActivities.ts:220,227`
- 抽奖凭据:`admin/draw_receipts/:id``admin/draw_receipts/log/:log_id` · `web/admin/src/api/adminActivities.ts:299,323`
- 玩家管理:`admin/users/*`(邀请、订单、优惠券、积分、余额、库存、道具卡、奖励发放)· `web/admin/src/api/player-manage.ts:25,48,81,106,130,157,188,198,210,220,239`
- 轮播图:`admin/banners/*` · `web/admin/src/api/banner.ts:14,26,39,43`
- 商品与分类:`admin/product_categories/*``admin/products/*` · `web/admin/src/api/product.ts:15,22,32,38,63,76,90,94`
- 系统道具卡/优惠券:`admin/system_item_cards/*``admin/system_coupons/*` · `web/admin/src/api/itemCards.ts:81,86,92,96,101``web/admin/src/api/coupons.ts:59,64,70,74`
- 工会管理(管理端):`admin/guilds/*`、成员:`admin/guilds/:guild_id/members` · `web/admin/src/api/adminGuild.ts:12,27,31,35``web/admin/src/api/guild.ts:61`
## 待清理项(未发现前端使用)
- `GET /api/admin/license/status` · `internal/router/router.go:53`(前端无调用)
- 管理员账号维护接口:
- `POST /api/admin/create` · `internal/router/router.go:66``internal/api/admin/admin_create.go`
- `PUT /api/admin/:id` · `internal/router/router.go:67``internal/api/admin/admin_modify.go`
- `POST /api/admin/delete` · `internal/router/router.go:68``internal/api/admin/admin_delete.go`
- `GET /api/admin/list` · `internal/router/router.go:69``internal/api/admin/admin_list.go`
- 以上清单根据前端 `web/admin/src/api/*` 的全面检索得出;如后续发现隐藏使用,则可回滚。
## 实施步骤
1. 冻结与确认
- 在代码评审中确认上述待清理项确实不需要;标注使用范围仅限管理端
2. 路由层清理
- 从 `internal/router/router.go` 删除或注释未用路由注册行:`53, 66-69`
- 核对编译影响:移除后需同时删除对应 handler 引用,避免未引用编译警告
3. 处理器与文件清理
- 删除对应的处理器文件:`internal/api/admin/admin_create.go``admin_modify.go``admin_delete.go``admin_list.go`
- 若 `license/status` 为内联处理器,无独立文件,仅移除路由与调用
4. 依赖与构建
- 执行模块级编译,确保无引用残留(如 `admin.New` 构造体内未使用成员)
- 运行单元/集成用例(如存在),并做最小回归:登录、活动、玩家、商品、运营、工会等主流程
5. 文档与变更说明
- 记录变更影响范围与回滚策略,标注接口移除清单
## 验收标准
- 管理端页面所有功能可正常使用与构建通过
- 删除的接口在全仓内无调用引用(前端/后端均为 0
- 关键页面 API 映射完整且无 404/500
## 回滚方案
- 路由注册保留注释备份;如发现遗漏使用,按文件恢复并重新注册路由
## 下一步请求
- 若确认以上清理项与步骤,开始实施清理并提交差异,附带验证报告与引用检索证明

View File

@ -1,65 +0,0 @@
# 目标
- 为管理后台补齐“订单管理”全套能力:全局订单列表/详情、筛选与搜索、状态流转(取消/发货/签收/履约)、退款、导出与对账入口;与现有用户维度订单查询保持一致风格。
# 现状与缺口
- 已有:按用户查看订单、运营漏斗统计;无全局订单页与发货、退款、对账的管理操作。
- 模型完备:`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. 留待增强:接入真实支付退款与对账流程;增加订单号唯一索引与支付幂等表。
# 风险与处理
- 真实退款未接入:先保留管理操作占位,避免资金走错;上线前需联通支付域。
- 并发与幂等:条件更新控制状态流;失败重试与告警结合现有日志。

View File

@ -1,71 +0,0 @@
## 目标
- 系统化梳理后端代码,识别并删除冗余设计与未使用模块
- 保持现有功能与前端调用不受影响,提升可维护性与编译/测试稳定性
## 范围
- 主要针对 Go 后端 (`internal/*`, `router/*`, `repository/*`)
- 不改动数据库表结构与线上配置;仅移除未被调用的代码
- 前端仅做轻微枚举对齐(可选),不影响现有页面
## 清理项
1. 移除未使用的抽奖策略注册机制
- 文件:`internal/service/activity/strategy/strategy.go`
- 保留:`ActivityDrawStrategy` 接口(默认策略仍以接口类型返回)
- 删除:`registry` 映射、`Register`/`Get` 方法(代码内无引用)
- 影响评估:`lottery_app.go` 直接实例化 `NewDefault`,不依赖注册表;`ichiban` 专用策略走独立服务,不受影响
2. 移除公会 Guild 相关 DAO/Model已无引用
- 文件:
- `internal/repository/mysql/dao/guild*.gen.go`
- `internal/repository/mysql/dao/guild_contribute_logs.gen.go`
- `internal/repository/mysql/model/guild*.gen.go`
- `internal/repository/mysql/model/guild_contribute_logs.gen.go`
- 依据:全局检索无业务层或接口层引用,仅残留表定义
- 处理:删除生成文件;保留 `TracePlugin.softDeleteTables` 的字符串名单不影响运行(仅按表名软删过滤)
3. 移除管理端批量造数/批量删除用户接口(未被前端使用)
- 路由:`internal/router/router.go`
- `POST /api/admin/batch_users`(创建)
- `DELETE /api/admin/batch_users`(删除)
- 处理:删除对应 handler `internal/api/admin/batch_users.go` 和路由挂载
- 保留其他批量接口(积分/优惠券/奖励)不变
4. 移除“为指定用户签发 APP Token”管理端接口未被前端使用
- 路由:`POST /api/admin/users/:user_id/token`
- 文件:`internal/api/admin/user_token_admin.go`
- 说明:仅测试/紧急用途,当前前端无调用;移除可减少安全面
5. 保留 Prometheus 指标模块但不启用(仅在需要时开启)
- 现状:`internal/metrics/*` 未被路由启用;保留代码与可选开关,不做删除
- 若后续确认长期不使用,可二期移除并同步 `go.mod` 依赖精简
6. 前端枚举对齐(可选)
- 文件:`web/admin/src/api/dashboard.ts`
- 内容:`TodoTaskType` 包含 `JOIN_GUILD`(服务端已改为 `undrawn` 代办);如需对齐,移除或改为服务端实际值
## 验收标准
- 编译通过:`go build ./...` 无错误
- 单测通过:现有测试(如 `ichiban_test.go`)通过
- API 回归:管理端/APP端核心路由不变产品、活动、奖励、抽奖、支付、称号、道具卡、系统配置
- 前端联调:`web/admin/src/api/*` 所引用的后端路由均可正常返回
## 执行步骤
1. 删除策略注册表(保留接口)
2. 删除 Guild 相关 `dao/``model/` 生成文件
3. 删除 `batch_users.go` 及其路由挂载
4. 删除 `user_token_admin.go` 及其路由挂载
5. 本地编译与单测(构建/测试)
6. 运行服务做核心接口冒烟(活动、商品、支付、任务中心、称号/卡券)
7. 更新 `.trae/documents/全面代码清理与优化计划.md` 标注已清理项与影响范围
## 风险与回滚
- 若前端/运营依赖被误删接口:通过 Git 回滚对应文件与路由挂载即可恢复
- `Guild` 表仍在数据库中:删除 DAO/Model 不影响其他代码;如未来需要,可通过代码生成器补回
## 代码参考
- 路由文件:`internal/router/router.go:106-108`, `internal/router/router.go:149`
- 策略接口与实现:`internal/service/activity/strategy/strategy.go`, `internal/service/activity/strategy/default.go`, `internal/service/activity/ichiban_slots_service.go`
- 测试用例:`internal/service/activity/strategy/ichiban_test.go`
- 前端调用示例:`web/admin/src/api/*`(确认哪些路由在用)
请确认以上清理方案,确认后我将按步骤执行、构建验证并提交变更说明。

View File

@ -1,74 +0,0 @@
# 抽奖控制策略选择与执行
## 背景与约束
* 不改变算法逻辑承诺→HMAC+拒绝采样→验算一致),仅通过“输入/配置/时机”达到运营控制。
* 验算用户能看到的是回执中池快照与承诺哈希一致性,不能证明你在抽后操纵,只能确认抽前配置。
## 方案A权重/库存门控(推荐)
* 做法:在承诺前将目标奖品 `weight=0``quantity=0`,前 N 次(或直到达到阈值)不进入抽取集合;到期后恢复权重/库存并重新承诺。
* 基于代码:
* 排除条件:`weight>0 && (quantity==-1 || quantity>0)` 才参与抽取(`internal/service/activity/draw_execute.go:31-35,56-58`)。
* 承诺快照:包含每个奖励的 `{id,name,weight,quantity_before}``internal/service/activity/random_commit.go:53-61,67-73`)。
* 优点:简单直接、无需改算法;前 N 次绝不命中;验算完全通过。
* 缺点:恢复后需生成新承诺(`state_version` 增加),不同时间段 `items_root` 不同,运维需记录策略切换。
## 方案B承诺版本切换
* 做法:用 `state_version` 管理期的承诺版本:
* v1不含目标奖品或其权重为 0 → 前 N 次抽使用 v1。
* v2目标奖品恢复权重/库存 → 达到 N 后切换到 v2。
* 基于代码:承诺生成与历史查询(`internal/service/activity/random_commit.go:74-97,121-146`)。
* 优点:语义清晰、审计友好;对不同用户批次可严格区分承诺。
* 缺点:运维复杂度稍高;用户若横向对比可能看到承诺变化,但单次验算仍通过。
## 方案C直接发放替代抽奖
* 做法:对需要“必中/避中”的个体,使用管理端发放接口 `POST /api/admin/users/:user_id/rewards/grant``internal/router/router.go:127`)。
* 优点:精确可控,零风险。
* 缺点:不产生抽奖回执;不适合需要“抽奖体验”的场景。
## 方案对比与推荐
* 目标“前 N 次不出现”且保留抽奖体验:优先选 **方案A权重/库存门控)**,用 `quantity=0``weight=0` 让奖品在 N 次前不参与集合;到期后恢复并重新承诺。
* 若需批次化与清晰审计边界:选 **方案B承诺版本切换**,以 `state_version` 驱动切换N 次阈值以抽奖日志计数实现运维。
* 个体定向控制:用 **方案C直接发放** 替代抽奖。
## 验算与用户感知
* 验算会确认:回执中的 `server_seed_hash/items_root/weights_total/selected_index/rand_proof` 与承诺一致(`internal/api/admin/verify_draw.go:50-66,104-138`)。
* 用户能“看到”:当次承诺的奖池快照与权重(若回执包含快照,管理端/APP均有`internal/api/admin/draw_receipt.go:55-73``internal/api/activity/draw_app.go:28-34`)。
* 用户“感知不到”:你通过前置配置与时机实现“前 N 次不出现”的意图;只要在承诺前已固化,抽后不会被判定为操纵。
## 执行建议(不改代码)
1. 选定期次与目标奖品,设置前置配置:`quantity=0``weight=0`
2. 生成承诺(`commit_random`)并上线;开始计数抽奖日志,达到 N 次后恢复配置并生成新承诺。
3. 记录操作与版本切换,必要时在活动规则中说明奖池/期的切换策略。
## 参考位置
* 参与判定与选取:`internal/service/activity/draw_execute.go:31-35,50-66,131-145`
* 承诺生成与版本:`internal/service/activity/random_commit.go:67-85,74-97,121-146`
* 管理端验证:`internal/api/admin/verify_draw.go:50-66,104-138`

View File

@ -1,93 +0,0 @@
## 总览
- 覆盖 5 个问题:商品列表批量操作、活动创建奖品价格与总价、仪表盘图表重叠修复、活动创建性能、弹窗点击关闭问题。
- 技术栈:管理端前端 Vue3 + Element Plus路径 `web/admin`),后端 Go路径 `internal/*`)。
- 目标:补齐商品批量接口、完善前端交互与缓存、修正布局与加载时序、增强性能与统一弹窗行为。
## 关键位置
- 商品前端:`web/admin/src/views/product/list/index.vue`API`web/admin/src/api/product.ts`
- 商品后端:`internal/api/admin/product_create.go`、Service`internal/service/product/product.go`
- 活动向导:`web/admin/src/views/activity/wizard/index.vue`
- 奖励管理:`web/admin/src/views/activity/rewards/index.vue`
- 仪表盘容器:`web/admin/src/views/dashboard/console/index.vue`
- 抽奖量图表:`web/admin/src/views/dashboard/console/modules/sales-overview.vue` + `components/core/charts/art-line-chart`
- 中奖率分析:`web/admin/src/views/dashboard/console/modules/activity-prize-analysis.vue`
- 弹窗示例:`web/admin/src/views/player-manage/modules/*Dialog.vue``components/core/layouts/*`
## 1. 商品列表功能优化
- 后端批量接口
- 新增 `PUT /api/admin/products/batch`:请求体 `{ ids: number[], stock?: number, status?: 1|2 }`,返回 `{ updated_count, failed: [ {id, reason} ] }`
- Controller`internal/api/admin/product_create.go` 或新文件 `product_batch.go` 增加路由与校验(限制 `ids` 11000`stock >= 0`)。
- Service`internal/service/product/product.go` 增加 `BatchUpdate(ctx, ids, stock?, status?)`,事务更新;支持“仅库存”“仅状态”“二者皆有”。
- DAO使用批量更新`UPDATE products SET stock=?, status=? WHERE id IN (...)`),对无权限/不存在的 `id` 记录失败原因。
- 文档:更新 swagger`docs/swagger.yaml`)。
- 前端批量操作
- 列表顶部添加“全选本页”复选框:基于 `ElTable` 的选择事件维护 `selectedIds: number[]``checkAll` 同步当页数据。
- 批量工具栏:按钮“批量改库存”“批量上架”“批量下架”。每次操作弹出确认框(库存输入/操作确认)。
- API`web/admin/src/api/product.ts` 新增 `batchUpdateProducts(payload)` 调用上游接口;失败项以通知/表格高亮反馈。
- 交互:操作成功后刷新当页并清空选中;支持 loading 与禁用态;无选中时禁用按钮。
## 2. 活动创建流程优化(奖品价格与总价)
- 单价展示
- 远程商品搜索返回 `price` 字段(已有),选择时在 `ElSelect``option` 右侧显示“¥单价”。
- 在奖励编辑卡片/表格中追加“单价”列(从缓存读取)。
- 总价计算
- 公式:`总价 = Σ(price(product_id) * quantity)`;监听 `product_id/quantity` 变更实时更新。
- 缓存:在 `wizard/index.vue``rewards/index.vue` 维护 `priceCache: Map<product_id, price>`,首次选择命中即用,未命中时调用 `fetchProducts` 补齐并写入。
- 同步:商品价格变更时(重新打开或重新拉取),更新缓存并触发总价重算;保留两位小数。
- 展示步骤3顶部显示“选中奖品总成本¥xxx.xx”。
## 3. 仪表盘数据展示修复(重叠问题)
- 布局与间距
- 在 `console/index.vue``ElRow` 增加统一 `gutter`(如 `20`),保证模块间距。
- 调整 `ElCol` 断点:避免同一行 `ActivityPrizeAnalysis` 与其他模块在窄屏并排压缩;在 `md/sm` 断点落到 `24` 独占一行。
- 组件容器
- 为 `sales-overview.vue``activity-prize-analysis.vue` 外层卡片设置 `position: relative; z-index: 0`;图表内部设 `z-index: 1`,避免 canvas/svg 溢出覆盖。
- 统一卡片最小高度,防止加载时高度为 0 导致重叠。
- 加载时序
- 图表组件在 `dataReady` 后再渲染;`v-if="dataReady"` 避免空容器渲染。
- 保留/增加骨架或加载态,防止内容突变挤压。
## 4. 活动创建性能优化步骤1
- 加载指示
- `wizard/index.vue` 步骤1添加 `v-loading` 与骨架;按钮禁用在加载中。
- 请求超时
- 前端 axios 层设置接口超时 `30s`,在 `web/admin/src/api/_http.ts`(或全局实例)设置,并对超时给出 Toast 提示与重试入口。
- 慢请求日志
- Axios 响应拦截记录 `duration > 2000ms` 的接口,打印到控制台并上报(如有日志上报端点)。
- 初始化优化
- 并行拉取必要数据(活动分类、默认配置、组织信息),减少串行等待;命中表格缓存的接口优先读缓存(已有 `tableCache`)。
- 错误恢复
- 失败时保留已填写表单,显示错误提示与“重试/刷新数据”按钮;网络恢复后自动重载。
## 5. 弹窗点击关闭没有反应(统一与修复)
- 审核现状
- 业务型对话框普遍设置 `:close-on-click-modal="false"`(避免误关);全局搜索/锁屏等未禁用遮罩关闭且部分隐藏关闭按钮。
- 统一策略
- 业务表单:保留 `close-on-click-modal=false`,确保“关闭按钮/取消按钮/ESC”都能关闭统一通过 `@update:model-value``@closed` 重置。
- 全局弹窗:明确设置 `:close-on-click-modal="false"` 并显示关闭按钮或提供显式关闭入口(避免遮罩点击不生效引起困惑)。
- 修复点
- 核查所有 `ElDialog/ElDrawer`:确保存在 `@update:model-value``v-model` 双向绑定;为关闭图标绑定 `emit('update:visible', false)`;在有 `before-close` 时正确调用 `done()`
- 统一 ESC 关闭(可选):在布局层面监听 ESC 并广播关闭事件。
## 验收标准
- 商品批量:
- 在商品列表勾选 N 个,批量改库存/上下架成功,返回计数与失败明细;刷新后状态一致。
- 奖品选择:
- 选择奖品即显示单价,总价随数量/选择实时更新;切换商品价格后总价同步。
- 仪表盘:
- “活动抽奖量”与“活动中奖率分析”在所有断点无重叠,布局稳定;数据加载时不挤压。
- 性能与超时:
- 步骤1加载显示进度超时 30s 有提示与重试;>2s 请求有日志可见。
- 弹窗关闭:
- 所有弹窗可通过关闭按钮/取消按钮/ESC 关闭;遮罩行为与设计一致,不出现“点击关闭没有反应”。
## 交付变更清单
- 后端新增批量接口Controller+Service+DAO+Swagger
- 前端:
- 商品列表批量选择与工具栏;新增批量 API 调用与确认反馈。
- 向导步骤3与奖励管理页显示单价与总价缓存价格。
- 仪表盘栅格与 z-index 调整;图表渲染时序保护。
- Axios 超时与慢请求日志;向导加载骨架与错误恢复。
- 弹窗双向绑定与关闭逻辑统一。
如确认本方案,我将按上述清单依次实现并提交改动,过程中为关键函数添加函数级注释与必要单元/交互测试。

View File

@ -1,16 +0,0 @@
## 目标
- 将新用户列表中的“称号数”改为展示具体称号列表(标签形式)。
## 改动内容
- 后端 `GET /api/admin/dashboard/new_users`:在每个用户项增加 `titles: [{id,name}]` 列表;保留现有字段,兼容前端。
- 位置:`internal/api/admin/dashboard_admin.go``DashboardNewUsers()`
- 实现:联表 `user_titles``system_titles` 获取用户称号名称,按用户填充 `titles` 数组。
- 前端类型与表格:
- 在 `web/admin/src/api/dashboard.ts``NewUserItem` 增加 `titles: { id:number; name:string }[]`
- 在 `web/admin/src/views/dashboard/console/modules/new-user.vue`
- 移除“称号数”列
- 新增“称号”列,循环 `row.titles` 渲染 `ElTag` 列表;为空时显示“无称号”。
## 验证
- 后端编译通过;接口 `new_users` 返回每个用户的称号数组
- 前端工作台“新用户”模块展示称号标签;无称号显示“无称号”

View File

@ -1,25 +0,0 @@
## 目标
- 在管理端用户详情抽屉中显示该用户当前头衔(含名称、描述、生效/过期时间)。
## 现状
- 用户详情视图:`web/admin/src/views/player-manage/modules/player-detail-drawer.vue`
- 玩家管理页入口:`web/admin/src/views/player-manage/index.vue`
- 资产接口:`web/admin/src/api/player-manage.ts`(暂无用户头衔列表接口)
- 后端路由已存在分配头衔:`POST /api/admin/users/:user_id/titles`,但缺少`GET`列表接口。
## 后端改造
- 新增:`GET /api/admin/users/:user_id/titles`
- 位置:`internal/api/admin/users_admin.go`
- 查询:`user_titles`active=1、未过期左连接`system_titles`(取`name/description`),返回`id/title_id/name/description/obtained_at/expires_at/status`
- 路由挂载:`internal/router/router.go` 在管理端鉴权组新增该`GET`端点。
## 前端改造
- API`web/admin/src/api/player-manage.ts` 增加 `fetchGetUserTitles(userId)`;请求 `GET /api/admin/users/:user_id/titles`
- 视图:在 `player-detail-drawer.vue` 增加“头衔”板块
- 展示为标签列表:`title.name`(副文案:`description`
- 显示时间:`obtained_at``expires_at`(过期标识)
- 若无头衔,显示“无头衔”。
## 验收
- 打开用户详情,正确拉取并显示当前头衔;过期或未激活不显示。
- 已分配称号立即可在详情查看。

View File

@ -1,41 +0,0 @@
**问题理解**
- 需要在发货统计中体现“采购价格”,用于与销售价格/订单金额做对比与分析。
- 当前 `ops_shipping_stats` 仅存储销售单价 `product_price_cents`,无采购单价。
**数据库变更**
- 在 `ops_shipping_stats` 表新增字段:
- `purchase_price_cents BIGINT NOT NULL DEFAULT 0 COMMENT '采购单价(分)'`
- SQLMySQL
- `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` 计算)
- 保持“盈亏”列为后端返回值显示(颜色提示不变)。
**验收标准**
- 新建/编辑支持录入采购单价,并正确写入与返回。
- 列表能展示采购单价、采购总额和销售对比(毛利),并与现有盈亏并行显示。
- 不影响既有筛选、分页与来源类型逻辑。
**后续可选**
- 若采购价格数据来源稳定(如采购单据),可支持远程选择或自动回填;当前先手动录入以快速上线。
请确认以上方案,通过后我将进行数据库字段添加、代码生成与前后端联调实现。

View File

@ -1,170 +0,0 @@
## 约束与对齐
- 管理端仅支持“定时到具体时间点”的开奖,不支持设置“定时 N 分钟后开奖”。
- 计划包含:玩法策略、哈希可验证抽奖、退款+赠券流程、数据一致性、完整 API 与参数。
## 玩法与流程
- 一番赏
- 定时开奖:必填 `scheduled_time`(绝对时间戳) 与 `min_participants`(最低人数)。到时若人数 < N全额退款+赠券。≥N统一开奖
- 即时开奖:用户支付成功后立即抽奖并返回凭证。
- 无限赏/对对碰/爬塔:标准抽奖流程(即时抽奖),策略差异体现在权重、过滤规则、后置效果。
## 抽奖与可验证性
- 种子:服务端使用加密安全 `crypto/rand` 生成 32 字节 `seed`,按期(issue)维度保存。
- 哈希签名:`signature = HMAC-SHA256(seed, user_id|issue_id|timestamp|nonce)``nonce` 为 16 字节随机串,`timestamp` 精确到毫秒。
- 客户端校验:本地按相同算法计算签名并比对;凭证包含必要输入。
## 数据模型(核心字段)
- `activities``id``name``play_type`(ichiban/infinite/match/tower)、`draw_mode`(scheduled/instant)、`min_participants``scheduled_time``refund_coupon_type``refund_coupon_amount``status`(draft/active/closed)。
- `issues``id``activity_id``seed``seed_version``original_qty``quantity``status`(pending/drawing/done/failed)。
- `rewards``id``issue_id``name``weight``original_qty``quantity``type``meta`
- `draw_logs``id``issue_id``activity_id``user_id``reward_id``signature``timestamp``nonce``receipt_json`
- `lottery_refund_logs``id``issue_id``order_id``user_id``amount``coupon_type``coupon_amount``reason``created_at``status`(success/failed/retry)。
## 管理端 API 与参数
1) 创建活动
- `POST /api/admin/lottery/activities`
- Body
```
{
"name": "一番赏·七月",
"play_type": "ichiban", // 枚举: ichiban | infinite | match | tower
"draw_mode": "scheduled", // 枚举: scheduled | instant
"min_participants": 100, // 定时玩法必填
"scheduled_time": "2025-12-05T20:00:00Z", // 绝对时间戳,禁止传相对分钟
"refund_coupon_type": "cash", // 枚举: cash | discount | gift
"refund_coupon_amount": 20.0, // 金额或面额
"description": "..."
}
```
- Response
```
{ "activity_id": 123 }
```
- 校验:当 `draw_mode`==`scheduled` 时必须存在 `scheduled_time`(>= 当前时间+最小提前量) 与 `min_participants`;拒绝包含“分钟”相对参数。
2) 更新活动
- `PATCH /api/admin/lottery/activities/:activity_id`
- Body同创建可部分字段
- 禁止将 `scheduled_time` 更新为相对分钟;仅允许更新为更晚的绝对时间。
3) 创建活动期(含奖池与种子)
- `POST /api/admin/lottery/activities/:activity_id/issues`
- Body
```
{
"rewards": [
{ "name": "SSR皮肤", "weight": 1, "original_qty": 1, "type": "virtual", "meta": {"skin_id": 88} },
{ "name": "SR皮肤", "weight": 10, "original_qty": 50, "type": "virtual", "meta": {"skin_id": 77} }
]
}
```
- Response
```
{
"issue_id": 456,
"seed_version": 1
}
```
4) 活动列表/详情
- `GET /api/admin/lottery/activities?play_type=&draw_mode=&status=&page=&size=`
- `GET /api/admin/lottery/activities/:activity_id`
5) 期列表/详情
- `GET /api/admin/lottery/activities/:activity_id/issues?page=&size=`
- `GET /api/admin/lottery/issues/:issue_id`
6) 手动关闭活动/期
- `POST /api/admin/lottery/activities/:activity_id/close`
- `POST /api/admin/lottery/issues/:issue_id/close`
## APP 端 API 与参数
1) 参与抽奖(创建订单/入场)
- `POST /api/app/lottery/join`
- Body
```
{
"activity_id": 123,
"issue_id": 456,
"channel": "wechat", // 支付渠道
"client_nonce": "base64..." // 可选,客户端随机串
}
```
- Response即时模式可能包含抽奖结果定时模式仅返回入场信息
```
{
"join_id": "J202512030001",
"order_id": "O202512030009",
"pay_params": { /* JSAPI 等 */ },
"queued": true, // 定时玩法
"draw_mode": "scheduled"
}
```
2) 即时抽奖(支付成功后自动触发,若需要主动查询)
- `GET /api/app/lottery/result?join_id=J202512030001`
- Response包含可验证凭证
```
{
"result": {
"reward_id": 789,
"reward_name": "SR皮肤"
},
"receipt": {
"issue_id": 456,
"seed_version": 1,
"timestamp": 1733251200123,
"nonce": "rB1A...==",
"signature": "hmacSha256Base64...",
"algorithm": "HMAC-SHA256",
"inputs": {
"user_id": 10086,
"issue_id": 456,
"timestamp": 1733251200123,
"nonce": "rB1A...=="
}
}
}
```
3) 定时开奖结果查询(到点统一结算后)
- `GET /api/app/lottery/scheduled/result?issue_id=456&user_id=10086`
- Response 同上(若未达人数,返回退款与赠券信息)
```
{
"refunded": true,
"refund": { "order_id": "O202512030009", "amount": 29.9 },
"coupon": { "type": "cash", "amount": 20.0, "coupon_id": 33001 }
}
```
4) 客户端校验助手(可选)
- `GET /api/app/lottery/issue/:issue_id/seed_version`
- Response `{ "seed_version": 1 }`
## 支付/退款/赠券
- 支付回调:沿用 `/api/pay/wechat_notify`(落库订单状态,触发即时抽奖或定时入场)。
- 定时结算器:服务端按 `scheduled_time` 扫描:
- 人数 < N批量退款并为每位用户发放预设优惠券`system_coupons`)。
- 人数 ≥ N统一抽奖与发放写入 `draw_logs` 与事务扣减奖励。
- 退款接口(内部调用):对每个订单创建退款,记录 `lottery_refund_logs` 并保证幂等。
## 一致性与异常处理
- 事务:入场/库存扣减/日志写入使用同一事务;失败回滚。
- 幂等:`join_id``order_id` 作为幂等键,退款/赠券均防重复。
- 定时任务:定期扫描 + 重试机制;失败写入日志,告警通知。
- 网络中断:客户端可凭 `join_id` 查询结果;服务端保证结算原子性。
## 错误码(示例)
- `400`:活动未开始/已结束/库存不足/限频/参数错误
- `402`:未支付
- `409`:重复参与/幂等冲突
- `500`:系统错误/结算失败(可重试)
## 测试与验收
- 单元HMAC 随机选择、拒绝采样无偏、事务一致性、幂等与重试。
- 集成:支付→即时报奖→收据校验;定时人数不足→退款+送券。
- 文档Swagger 补充所有请求/响应体;明确“禁止 N 分钟相对定时”,仅允许绝对时间戳。
## 交付清单
- 策略模块、管理端与 APP 端 API、哈希凭证、定时结算器、退款日志与发券流程、测试与文档。

View File

@ -1,206 +0,0 @@
## 目标与范围
* 支持用户持有多个头衔,并在各类业务事件中正确生效与结算。
* 覆盖六类效果:领取优惠券、抽奖折扣、签到双倍积分、领取道具卡、概率加成、双倍奖励卡。
* 与既有四张表对齐:`system_titles``system_title_effects``user_titles``user_title_effect_claims`
## 整体架构
* 配置层:运营通过`system_titles``system_title_effects`完成模板与效果配置。
* 持有层:`user_titles`记录用户持有与激活状态、有效期与来源。
* 事件引擎:在`SIGNIN``DRAW_PURCHASE``DRAW_EXECUTE``CLAIM`类接口中查询并结算效果。
* 防重限流:`user_title_effect_claims`按周期唯一键实现并发防重与频次限制。
```mermaid
flowchart LR
A[用户行为事件] --> B[查询激活头衔 user_titles]
B --> C[加载效果 system_title_effects]
C --> D{按scopes/effect_type过滤}
D --> E[叠加/上限策略计算]
E --> F[结算(积分/折扣/概率/发放)]
F --> G[必要时写 user_title_effect_claims]
```
## 效果模型与参数规范
* 统一字段:`effect_type``params_json``stacking_strategy``cap_value_x1000``scopes_json``status`
* 固定小数:所有比例/倍数/折扣统一用`*_x1000`(例如 10% = 1002倍 = 2000
* `params_json`建议结构:
* 领取优惠券(`COUPON_CLAIM`): `{template_id, frequency:{period:'day|week|month', times:int}, start_at?, end_at?}`
* 抽奖折扣(`DRAW_DISCOUNT`): `{discount_type:'percentage|fixed', value_x1000, min_price?, max_discount?}`
* 签到倍数(`SIGNIN_MULTIPLIER`): `{multiplier_x1000, daily_cap_points?}`
* 领取道具卡(`ITEM_CARD_CLAIM`): `{template_id, frequency:{period, times}}`
* 概率加成(`PROBABILITY_BOOST`): `{target_pool_ids?:[], target_prize_ids?:[], boost_x1000, combine:'sum|max', cap_x1000?}`
* 双倍奖励卡(`DOUBLE_REWARD`): `{chance_x1000, target_prize_ids?:[], period_cap_times?:int}`
## 叠加与上限策略
* `stacking_strategy`枚举:
* `none`:不叠加,取单一最高优先级或最大值。
* `sum_with_cap`:求和后受`cap_value_x1000`或业务cap约束。
* `max_only`:取最大项。
* `multiply_with_cap`倍数相乘后再cap适用于少数特殊配置
* `priority_order`:按`priority`字段逐项应用,遇到冲突按优先级覆盖。
* 推荐缺省:
* 抽奖折扣:`max_only`(防止过度优惠),可选`sum_with_cap`
* 签到倍数:`sum_with_cap`(如多头衔加总倍数,上限控制)。
* 概率加成:`sum_with_cap`(保持可解释性与稳定性)。
* 领取型权益:同模板同周期`sum_with_cap``max_only`,按运营策略选择。
* 双倍奖励卡:多卡按`chance_x1000`合并使用`sum_with_cap`,并设`period_cap_times`
## 作用域与生效判定
* `scopes_json`建议:`{activity_ids?:[], phase_ids?:[], category_ids?:[], exclude?:{...}}`
* 判定顺序:事件上下文→按`activity/phase/category`过滤→`status=active`→用户头衔`active=1`且未过期→计算叠加与cap。
* 冲突处理:包含与排除并存时优先排除;多效果同类型按`stacking_strategy`解决。
## 事件结算流程
* 签到(`SIGNIN`):
* 载入`SIGNIN_MULTIPLIER`→合并倍数→应用上限→计算积分→落账。
* 抽奖购票(`DRAW_PURCHASE`):
* 载入`DRAW_DISCOUNT`→取最大或合并后cap→计算优惠→生成支付单。
* 抽奖执行(`DRAW_EXECUTE`):
* 载入`PROBABILITY_BOOST`→对目标池/奖品加权(总概率归一)→进行抽取→载入`DOUBLE_REWARD`测试加倍机会→如命中,倍增奖品数量/价值并校验当期可用次数。
* 领取(`COUPON_CLAIM`/`ITEM_CARD_CLAIM`):
* 解析`frequency.period`生成`period_key``YYYYMMDD|YYYYWW|YYYYMM`)→检查唯一键是否已存在→未存在则发放并写`user_title_effect_claims`;已存在直接返回已领取。
## 防重与限流实现
* 唯一键:`user_id + title_id + effect_type + target_template_id + period_key`
* 并发:使用数据库唯一约束天然防重;接口层加幂等键`Idempotency-Key`进一步防抖。
* 频次:根据`frequency.times`控制当期可用次数;若需累计多效果,按`stacking_strategy`决定合并或取最大。
## 接口设计(示例)
* 头衔管理
* `POST /titles/assign`:分配或续期;参数:`user_id, title_id, obtained_at, expires_at, source`
* `PATCH /titles/:user_id/:title_id/activate`:激活/停用。
* `GET /titles/user/:user_id?active=1`:查询用户激活头衔与效果摘要。
* 事件接口
* `POST /events/signin`:入参`user_id, context`;出参`points, applied_effects`
* `POST /events/draw/purchase`:入参`user_id, base_price, context`;出参`final_price, discount_breakdown`
* `POST /events/draw/execute`:入参`user_id, context`;出参`prize, boost_detail, double_reward_applied, audit_id`
* `POST /claims/coupon`:入参`user_id, template_id, context`;出参`claimed, period_key, remaining_times`
* `POST /claims/item-card`:入参同上。
* 诊断/审计
* `GET /effects/preview`:给定`user_id+context`预览本次将应用的合并效果,用于运营核对。
## 数据与索引建议
* 复用现有索引:
* `user_titles`: `idx_user_titles_user`
* `system_title_effects`: `idx_title_effects_title` + 按`effect_type/status`过滤。
* `user_title_effect_claims`: 周期唯一索引。
* 读优化:
* 缓存`system_title_effects`(按`title_id``scopes_json`分片TTL 5\~15 分钟)。
* 针对`DRAW_EXECUTE`的概率表做内存映射,命中后快速归一化计算。
## 风险与边界处理
* 浮点误差:统一`*_x1000`避免精度问题。
* 过度优惠/过度概率:必须设置`cap_value_x1000`或业务cap。
* 多模板冲突:同类型不同模板时采用`priority_order`或业务白名单优先级。
* 过期与停用:事件前统一校验有效期与`status`,过期/停用不生效。
* 诊断可视化:提供`preview`接口给运营验证,防误配置上线。
## 测试与验收标准
* 单元测试:
* 每类效果的合并逻辑与cap测试周期键生成与唯一约束概率加成归一化与双倍卡应用顺序。
* 并发/幂等:
* 高并发领取/购票/抽奖的防重与一致性测试。
* 集成测试:
* 端到端场景:
* 多头衔叠加签到倍数,限制生效。
* 两个折扣叠加取最大或cap限。
* 抽奖概率加成后命中率变更,双倍卡当期次数受限。
* 同模板每日领取限 1 次,多效果按策略合并。
* 验收标准:
* 用户可持有多个头衔并正确生效;
* 六类效果在对应事件中结算正确,含叠加与上限;
* 领取型权益防重与限流稳定;
* 审计日志完整,能输出`applied_effects`明细;
* 性能满足并发指标(按业务要求)。
## 交付物
* 结算引擎与效果合并模块的实现方案说明与接口契约。
* 管理/诊断接口的API说明与示例。
* 测试用例清单与通过报告。
* 运营使用指引:效果配置、叠加策略与风险提示。

View File

@ -1,81 +0,0 @@
# 头衔加概率与折扣的可验证设计
## 结论
* 采用“权重修饰”的方式为持有头衔的用户提升抽奖概率。
* 哈希算法HMAC-SHA256 随机熵)保持不变;只扩展承诺与收据的字段以固化规则与有效权重,保证可验证性与不可篡改。
## 必要扩展
* 承诺扩展IssueRandomCommitment
* 新增 `AlgoRulesHash`:对“权重修饰规则(由头衔配置确定的纯函数)”做哈希并随期承诺固化。服务端在抽取前不能更改规则。
* 收据扩展Receipt
* 新增 `UserId`:明确本次抽取的用户。
* 新增 `WeightsTotalEffective`:按用户头衔修饰后的总权重,用于位置采样与第三方复核。
* 新增 `UserWeightFactorsRoot`:以 `reward_id -> factor` 做 Merkle 根,第三方可据此对 `Items(snapshot)` 重建有效权重。
* 编码消息扩展(不改算法,仅改输入)
* 仍使用 `HMAC(serverSubSeed, encodeMessage(...))``encodeMessage` 增加 `UserId``WeightsTotalEffective` 字段(保持确定性)。
## 规则与确定性
* 规则来源头衔模板配置百分比或加点以及叠加策略none|max|multiply
* 纯函数:`factor = F(user_titles, reward_id)`,只依赖用户持有的头衔与静态规则;不依赖随机数或服务端临时状态。
* 可复核:第三方得到 `ItemsRoot``AlgoRulesHash``UserWeightFactorsRoot``UserId` 与收据内快照,即可重建每个 `reward_id` 的有效权重与 `WeightsTotalEffective`,据同样的消息编码与哈希熵验证 `SelectedIndex`
## 核心流程(抽奖不变,仅插入修饰步骤)
1. 读取承诺:`GetIssueRandomCommit(...)``internal/service/activity/random_commit.go:99`)。
2. 载入奖励池快照:同现有实现(`internal/service/activity/draw_execute.go:21-35`)。
3. 计算用户权重修饰:`effective_weight[i] = base_weight[i] * factor(user, reward_id[i])`;汇总 `WeightsTotalEffective`
4. 使用 HMAC-SHA256 生成熵(算法不变),对 `encodeMessage(..., UserId, WeightsTotalEffective)` 取样位点。
5. 按有效权重的累加区间选中 `SelectedIndex/SelectedItemId`
6. 返回收据,并包含 `UserWeightFactorsRoot` 与证明材料。
## 折扣集成(独立不影响随机)
* 抽奖订单创建(新增):以活动门票价 `PriceDraw``internal/repository/mysql/model/activities.gen.go:22`)为基价。
* 头衔折扣:按叠加策略计算 `discount_amount`,落到订单字段 `DiscountAmount/ActualAmount``internal/repository/mysql/model/orders.gen.go:22`)。
* 与优惠券/积分叠加:默认“优惠券优先 + 头衔折上折”,可通过 `stack_policy` 切到“取最大”。
## 接口与服务
* 管理端:头衔模板 CRUD + 发放;配置权重修饰参数与叠加策略。
* APP
* 抽奖订单创建接口(新):返回计算后的金额。
* 抽奖接口:保持路径不变(`internal/api/activity/draw_app.go:28`),服务内执行权重修饰。
* 模拟与校验:
* 模拟接口增加 `user_id`,输出期望概率与观测分布。
* 校验接口以收据扩展字段重建有效权重,验证 `SelectedIndex`
## 验收标准
* 头衔发放后,持有用户的抽奖订单能正确展示并结算折扣。
* 抽奖收据包含新增字段,第三方使用承诺与收据可重放选中过程;不持有头衔的用户与持有者的分布差异符合规则。
* 哈希熵算法未变,安全与不可预测性不受影响。
## 说明:关于“加概率是否影响哈希算法”
* 不影响哈希算法本身(仍是 HMAC-SHA256
* 影响的是“采样空间”——由原始 `weights_total` 改为 `WeightsTotalEffective`,并且将该值与用户标识作为消息输入的一部分来固定采样过程;承诺和收据的扩展确保服务端无法事后调参。

View File

@ -1,127 +0,0 @@
# 头衔权益系统设计与开发方案
## 目标与范围
- 目标:构建“用户头衔”体系,使用户可拥有多个头衔,并在签到、抽奖、领取等事件中触发对应权益。
- 权益范围:
1. 领取优惠券
2. 抽奖折扣(票价优惠)
3. 签到双倍积分
4. 领取道具卡
5. 抽奖概率加成
6. 奖品双倍概率(命中后按概率倍增奖励)
- 管理端:支持头衔模板与效果配置、用户分配与启停;用户端:展示头衔、领取入口、页面显示加成提示。
## 现有能力复用
- 优惠券:`system_coupons/user_coupons` 与管理端发券接口。
- 积分与流水:`user_points/user_points_ledger`,新增“签到”服务即可接入倍数规则。
- 抽奖:`activity_issues/activity_reward_settings` 与执行流程,按设计接入概率加成与双倍概率试验。
- 道具卡:`system_item_cards/user_item_cards` 与相关服务。
## 数据模型已提供DDL
- `system_titles`:头衔模板主表,定义名称、状态、获得规则与作用范围。
- `system_title_effects`:头衔-效果配置,一条记录代表一个效果项(含类型、参数、叠加策略与封顶)。
- `user_titles`:用户持有头衔资产,含激活状态、有效期与来源。
- `user_title_effect_claims`:领取型权益的周期限流与防重(如每日领券一次)。
- 注:不改动 `activity_draw_effects` 结构;抽奖效果的来源审计通过“效果应用层”记录到日志或 `Remark` 字段,保持表结构不变。
## 效果类型与参数约定
- `effect_type`(整型枚举):
- 1=COUPON_CLAIM领取优惠券
- 2=DRAW_DISCOUNT抽奖折扣
- 3=SIGNIN_MULTIPLIER签到积分倍数
- 4=ITEM_CARD_CLAIM领取道具卡
- 5=DRAW_PROBABILITY_BONUS抽奖概率加成
- 6=DRAW_REWARD_DOUBLE_CHANCE奖品双倍概率
- `params_json` 约定:
- COUPON_CLAIM`{template_id, frequency: 'once|daily|weekly|monthly', max_per_period}`
- DRAW_DISCOUNT`{discount_type: 'percent|fixed', value, max_cap, applicable_activities, applicable_issues}`
- SIGNIN_MULTIPLIER`{multiplier_x1000: 2000, cap_value_x1000: 3000}`
- ITEM_CARD_CLAIM`{template_id, frequency, max_per_period}`
- DRAW_PROBABILITY_BONUS`{probability_delta_x1000, cap_value_x1000, applicable_activities, applicable_issues}`
- DRAW_REWARD_DOUBLE_CHANCE`{multiplier_x1000: 2000, chance_x1000: 150, max_multiplier_cap_x1000: 3000, limit_per_day: null, applicable_activities, applicable_issues}`
- `stacking_strategy`0=取最大MAX1=累加并封顶STACK_CAPPED2=按排序首个FIRST_MATCH
## 叠加与封顶规则
- 抽奖折扣同类折扣按策略默认MAX合并`STACK_CAPPED`则累加至`max_cap`
- 签到倍数乘法叠加但有总封顶如≤3x
- 概率加成相加叠加后按上限封顶如≤500‰
- 奖品双倍概率:
- 概率合并:`p_total = 1 - ∏(1 - p_i)`并设置上限如≤50%)。
- 倍数合并:与其他奖励倍数(如道具卡)做乘法,但最终乘积受`max_multiplier_cap_x1000`封顶如≤3x
## 事件与规则引擎EffectEngine
- 统一事件:
- `SIGNIN`:加载用户激活头衔,应用`SIGNIN_MULTIPLIER`,写积分与流水。
- `COUPON_CLAIM/ITEM_CARD_CLAIM`:校验`user_title_effect_claims`限流后发放。
- `DRAW_PURCHASE`:计算`DRAW_DISCOUNT`,作用于抽奖票价。
- `DRAW_EXECUTE`:在“中奖项选择”后进行概率加成与“双倍概率”试验,计算最终奖励倍数;将结果影响在发奖/展示层体现,同时保留来源信息到日志或回执扩展结构(不改表)。
- 引擎职责:读取`user_titles → system_title_effects`,按叠加策略合并同类项并输出结算指令;保障并发可重入与审计信息完备(在不改动抽奖效果表前提下记录来源)。
## 接口设计
- 管理端
- `POST /api/admin/system_titles` 头衔模板创建
- `PUT /api/admin/system_titles/:id` 头衔模板修改
- `GET /api/admin/system_titles` 列表检索
- `DELETE /api/admin/system_titles/:id` 删除(级联效果)
- `POST /api/admin/system_titles/:id/effects` 效果创建含JSON校验
- `PUT /api/admin/system_titles/:id/effects/:effect_id` 效果修改
- `DELETE /api/admin/system_titles/:id/effects/:effect_id` 效果删除
- `POST /api/admin/users/:user_id/titles/:title_id/assign` 分配头衔
- `PUT /api/admin/users/:user_id/titles/:title_id/toggle` 激活/停用
- 用户端
- `GET /api/app/users/:user_id/titles` 我的头衔与效果摘要(含可领取提示)
- `POST /api/app/users/:user_id/titles/:title_id/effects/coupon/claim` 领券
- `POST /api/app/users/:user_id/titles/:title_id/effects/item_card/claim` 领卡
- `POST /api/app/users/:user_id/signin` 签到(生成流水,应用倍数)
- 抽奖购票与执行在现有接口内接入EffectEngine不改外部路由签名。
## 业务流程
- 领取型权益:校验`user_title_effect_claims`唯一键(周期),通过则发券/发卡并写入计数;拒绝则返回已达上限。
- 签到:读取倍数并写入`user_points_ledger(action=signin)`;倍数叠加与封顶在引擎完成。
- 抽奖购票:计算折扣并返回应付价;可在订单侧抵扣。
- 抽奖执行:
1. 按现有算法选中奖励项
2. 计算概率加成后的总概率与“双倍概率”的`p_total`
3. 使用受控RNG进行一次试验命中则将奖励倍数乘以`multiplier_x1000`
4. 将来源信息title/effect参数记录到日志或回执扩展结构不改动表用于运营分析和复核
## 风控与审计
- 限流与防重:依赖`user_title_effect_claims`唯一键与事务保证。
- 并发安全:接口层加幂等键(如`period_key`)与事务处理,避免重复发放。
- 来源审计:在不改表的前提下,将来源写入日志/回执的备注域或独立运营日志表(后续若需要可扩展)。
## 开发方案与里程碑
- M1 数据层与代码生成
- 执行DDL事务+先删后建),新增四张表
- 更新`cmd/gormgen`配置并生成DAO/Model
- 管理端头衔模板与效果CRUD接口与页面
- M2 规则引擎与签到
- 实现EffectEngine加载、合并、封顶、指令输出
- 新增`POST /api/app/users/:user_id/signin`服务,应用签到倍数
- 领取型权益限流服务与接口(领券/领卡)
- M3 抽奖集成
- 在购票结算接入`DRAW_DISCOUNT`
- 在抽奖执行接入`DRAW_PROBABILITY_BONUS``DRAW_REWARD_DOUBLE_CHANCE`
- 在不改表前提下,于日志/回执的备注域记录来源参数
- M4 前端联动与运营
- 管理端:头衔与效果配置表单、分配头衔入口
- 用户端:我的头衔页、领取入口、抽奖页加成展示
- M5 测试与验收
- 单元/集成测试:限流、叠加、封顶、并发
- 压测与随机性验证:抽奖公平性与概率准确性
- 文档与Swagger更新
## 测试用例要点
- 单头衔15%双倍抽样命中率约等于15%倍数为2x
- 多头衔概率合并10%与20% → 总概率≈28%
- 倍数合并与道具卡1.5x叠加,最终乘积≤封顶
- 领取限流:同周期重复领取被拒绝,计数与时间正确更新
- 并发:高并发领取与抽奖下无重复发放与越界叠加
## 验收标准
- 多头衔并存下,五类权益+双倍概率在对应事件正确触发与叠加、封顶生效
- 管理端与用户端流程完整、接口稳定、文档齐备
- 概率与倍数验证通过,随机性与公平性符合预期
- 审计信息可追踪(在现有表结构前提下)
请确认以上“设计方案与开发计划”,确认后我将按里程碑开始实现。

View File

@ -1,102 +0,0 @@
# 头衔表设计DDL含事务与字段说明
## 字段说明
* system\_titles`id``name``description``status``obtain_rules_json``scopes_json``created_at``updated_at`
* system\_title\_effects`id``title_id``effect_type``params_json``stacking_strategy``cap_value_x1000``scopes_json``sort``status``created_at`
* user\_titles`id``user_id``title_id``active``obtained_at``expires_at``source``remark``created_at`
* user\_title\_effect\_claims`id``user_id``title_id``effect_type``target_template_id``period_key``claim_count``last_claim_at``created_at`
* activity\_draw\_effects 扩展:`source_type``source_id`
## 事务化SQL先删表再新建含COMMIT
```sql
START TRANSACTION;
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `system_title_effects`;
DROP TABLE IF EXISTS `user_title_effect_claims`;
DROP TABLE IF EXISTS `user_titles`;
DROP TABLE IF EXISTS `system_titles`;
CREATE TABLE `system_titles` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(64) NOT NULL,
`description` VARCHAR(255) NULL,
`status` TINYINT NOT NULL DEFAULT 1,
`obtain_rules_json` JSON NULL,
`scopes_json` JSON NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
UNIQUE KEY `uk_system_titles_name` (`name`),
INDEX `idx_system_titles_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `system_title_effects` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`title_id` BIGINT NOT NULL,
`effect_type` INT NOT NULL,
`params_json` JSON NOT NULL,
`stacking_strategy` INT NOT NULL DEFAULT 0,
`cap_value_x1000` INT NULL,
`scopes_json` JSON NULL,
`sort` INT NOT NULL DEFAULT 0,
`status` TINYINT NOT NULL DEFAULT 1,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
CONSTRAINT `fk_title_effects_title` FOREIGN KEY (`title_id`) REFERENCES `system_titles`(`id`) ON DELETE CASCADE,
INDEX `idx_title_effects_title` (`title_id`),
INDEX `idx_title_effects_type` (`effect_type`),
INDEX `idx_title_effects_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `user_titles` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT NOT NULL,
`title_id` BIGINT NOT NULL,
`active` TINYINT NOT NULL DEFAULT 1,
`obtained_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`expires_at` DATETIME(3) NULL,
`source` VARCHAR(64) NULL,
`remark` VARCHAR(255) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
CONSTRAINT `fk_user_titles_title` FOREIGN KEY (`title_id`) REFERENCES `system_titles`(`id`) ON DELETE CASCADE,
UNIQUE KEY `uk_user_title_unique` (`user_id`, `title_id`),
INDEX `idx_user_titles_user` (`user_id`),
INDEX `idx_user_titles_active` (`active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `user_title_effect_claims` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT NOT NULL,
`title_id` BIGINT NOT NULL,
`effect_type` INT NOT NULL,
`target_template_id` BIGINT NULL,
`period_key` VARCHAR(16) NOT NULL,
`claim_count` INT NOT NULL DEFAULT 0,
`last_claim_at` DATETIME(3) NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
CONSTRAINT `fk_claims_title` FOREIGN KEY (`title_id`) REFERENCES `system_titles`(`id`) ON DELETE CASCADE,
UNIQUE KEY `uk_title_claim_period` (`user_id`, `title_id`, `effect_type`, `target_template_id`, `period_key`),
INDEX `idx_title_claims_user` (`user_id`),
INDEX `idx_title_claims_effect` (`effect_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `activity_draw_effects`
ADD COLUMN `source_type` INT NOT NULL DEFAULT 0,
ADD COLUMN `source_id` BIGINT NULL,
ADD INDEX `idx_draw_effects_source` (`source_type`, `source_id`);
SET FOREIGN_KEY_CHECKS=1;
COMMIT;
```
## 说明
* `period_key`:按策略生成(如每日`YYYYMMDD`、每周`YYYYWW`、每月`YYYYMM`)。
* 事务与外键检查切换保障迁移原子性与顺序删除/创建。

View File

@ -1,55 +0,0 @@
## 目标与范围
- 设计并梳理六类头衔效果1领取优惠券、2抽奖折扣、3签到双倍积分、4领取道具卡、5概率加成、6奖励双倍。
- 输出参数规范、叠加与封顶策略、作用域策略,以及标准化新建流程(管理端接口与数据约束)。
## 效果类型与参数规范
- 1 领取优惠券:`template_id`(券模板ID)、`frequency.period`(day|week|month)、`frequency.times`(周期次数)。示例:`{"template_id":123,"frequency":{"period":"day","times":1}}`(种子示例 `internal/api/admin/titles_seed.go:31`)。
- 2 抽奖折扣:`discount_type`(percentage|fixed)、`value_x1000`(千分比或固定额)、`max_discount_x1000`(折扣封顶)。示例:`{"discount_type":"percentage","value_x1000":100,"max_discount_x1000":300}``internal/api/admin/titles_seed.go:32`)。
- 3 签到双倍积分:`multiplier_x1000`(如2000表示x2)、`daily_cap_points`(每日积分上限)。示例:`{"multiplier_x1000":2000,"daily_cap_points":3000}``internal/api/admin/titles_seed.go:33`)。
- 4 领取道具卡:`template_id``frequency.period/times`(同“领券”频控)。示例:`{"template_id":456,"frequency":{"period":"week","times":2}}``internal/api/admin/titles_seed.go:34`)。
- 5 概率加成:`target_prize_ids[]`(目标奖品ID)、`boost_x1000`(加成千分比)、`cap_x1000`(单项封顶,可选)。示例:`{"target_prize_ids":[1,2],"boost_x1000":100,"cap_x1000":300}`;应用参考 `internal/service/activity/draw_with_effects.go:104-131`
- 6 奖励双倍:`target_prize_ids[]``chance_x1000`(命中概率千分比)、`period_cap_times`(期内命中上限,可选)。示例:`{"target_prize_ids":[3],"chance_x1000":150}`;命中判定参考 `internal/service/activity/draw_with_effects.go:197-208`
## 叠加与封顶策略
- `StackingStrategy`
- `0` max_only取效果最大值。
- `1` sum_with_cap累加并按封顶值限制优先使用效果内的 `cap_x1000`,否则用 `CapValueX1000`)。
- `2` first_match首个命中后忽略后续。
- 默认:累加并按 `CapValueX1000` 封顶(抽奖已实现 `internal/service/activity/draw_with_effects.go:117-131``internal/service/activity/draw_with_effects.go:146-149`)。
- 封顶单位统一为 `x1000` 千分比,避免浮点误差。
## 作用域策略ScopesJSON
- 字段:`activity_ids/issue_ids/category_ids``exclude` 子段;解析与匹配参考 `internal/service/title/effects_resolver.go:59-69``internal/service/title/effects_resolver.go:133-155`
- 生效原则:先排除再包含;未配置视为全局生效。
## 新建流程(管理端)
- 步骤1创建称号模板
- `POST /api/admin/system_titles``internal/router/router.go:128`;处理 `internal/api/admin/titles_admin.go:81-103`)。
- 请求体:`name/description/status/obtain_rules_json/scopes_json`空JSON默认填 `{}`)。
- 步骤2为称号模板添加效果
- `POST /api/admin/system_titles/:title_id/effects``internal/router/router.go:132-133`;处理 `internal/api/admin/titles_admin.go:183-213`)。
- 请求体:`effect_type/params_json/stacking_strategy/cap_value_x1000/scopes_json/sort/status`
- 步骤3分配给用户可选含有效期
- `POST /api/admin/users/:user_id/titles``internal/router/router.go:127`;处理 `internal/api/admin/titles_admin.go:291-353`)。
- 请求体:`title_id/expires_at(RFC3339)/remark`
- 步骤4校验
- 列表查询与检查:`GET /api/admin/system_titles``GET /api/admin/system_titles/:title_id/effects``internal/router/router.go:126``internal/router/router.go:131`;处理 `internal/api/admin/titles_admin.go:27-59``internal/api/admin/titles_admin.go:156-171`)。
## 数据与规则约束
- 名称唯一与幂等:同名不重复创建(`internal/api/admin/titles_seed.go:41-48`)。
- 状态控制:模板与效果需同时启用才会生效(抽奖过滤 `internal/service/activity/draw_with_effects.go:46-49``internal/service/activity/draw_with_effects.go:80-83`)。
- 有效期与激活:仅激活且未过期的用户称号参与计算(`internal/service/activity/draw_with_effects.go:54-67`)。
- 参数校验:`params_json` 必填(`internal/api/admin/titles_admin.go:195-206`)。
## 示例:新增“签到双倍积分”称号
- 创建模板:`name=签到达人``status=1``scopes_json={}`
- 添加效果:`effect_type=3``params_json={"multiplier_x1000":2000,"daily_cap_points":3000}``stacking_strategy=1``cap_value_x1000=3000``status=1`
- 分配给用户:设置 `expires_at`如7天后并激活。
## 示例:新增“抽奖折扣”称号
- 创建模板:`name=折扣官``status=1``scopes_json={"activity_ids":[1001]}`
- 添加效果:`effect_type=2``params_json={"discount_type":"percentage","value_x1000":150,"max_discount_x1000":500}``stacking_strategy=1``cap_value_x1000=500``status=1`
## 后续实施建议
- 在签到与购票模块接入统一解析器与效果计算器,实现(2)(3)的实际应用;领取型(1)(4)接入 `UserTitleEffectClaims` 做周期限流与资产发放;抽奖效果写入审计日志。
- 你确认后,我将按上述流程与参数规范为每种类型提供可执行的创建脚手架(接口调用示例与测试用例),并补齐未接入业务的效果逻辑。

View File

@ -1,13 +0,0 @@
## 目标
- 将奖品管理页Rewards操作栏的“编辑/删除”文本按钮替换为统一的图标标签按钮ArtButtonTable与活动管理、期数管理保持一致风格。
## 实施点
- 修改文件:`web/admin/src/views/activity/rewards/index.vue`
- 变更内容:
- 引入 `ArtButtonTable` 组件
- 将 `actions` 插槽中的 `ElButton` 替换为 `ArtButtonTable type="edit"``ArtButtonTable type="delete"`
- 不改动后端接口与其它逻辑(保留删除确认与编辑弹窗)。
## 验收
- 奖品管理列表操作栏显示统一的图标标签按钮,交互正常(编辑弹窗、删除确认)。
- 前端构建通过。

View File

@ -1,41 +0,0 @@
## 审计重点
- 配置与密钥:敏感信息明文存储在 `configs/*.toml`数据库密码、JWT密钥、COS密钥、微信AppSecret、commit_master_key
- 路由与鉴权:管理端多数接口已启用 `AdminTokenAuthVerify`;但存在非鉴权管理端接口(如 `seed_default``ensure_titles`)。
- 特效参数:`SystemTitleEffects.ParamsJSON` 后端仅做“非空”校验,运行期解析失败静默忽略;缺少数值范围与集合规模约束。
- 抽奖随机性主密钥未打印日志HMAC与拒绝采样实现合理但密钥来源为明文配置文件需迁移至环境变量。
- 优惠券/道具卡:分配/发放缺少配额与上限控制、幂等与审计;优惠券发放未限制超管。
## 具体风险与代码位置
- 明文密钥:`configs/fat_configs.toml:32``configs/*_configs.toml` 多处JWT/COS/MySQL/Wechat/commit_master_key
- 管理端非鉴权路由:`internal/router/router.go``/api/admin/system_titles/seed_default``/api/admin/menu/ensure_titles`)。
- 特效参数缺失校验:`internal/api/admin/titles_admin.go:173-213, 225-260`(仅非空);运行期解析:`internal/service/activity/draw_with_effects.go:104-149`(失败忽略)。
- 优惠券发放权限与配额:`internal/api/admin/users_admin.go::AddUserCoupon`(无超管校验、无模板配额扣减);道具卡分配虽限超管,但无上限/审计:`internal/api/admin/item_cards_admin.go::AssignUserItemCard`
## 修复方案(分阶段)
1) 配置与密钥治理
- 将敏感配置迁移到环境变量;`configs/*.toml`改为占位与示例;确保不在日志输出敏感值。
- 在密钥读取处加入检测:若为空或默认值,拒绝启动并记录安全告警。
2) 鉴权与权限
- 将所有管理端变更型接口统一置于鉴权组;保留非鉴权的仅限登录与必要的只读接口。
- 优惠券发放接口增加超管校验或细粒度权限;引入操作审计(记录操作者、时间、对象、数量)。
3) 特效参数强校验
- 后端在 `Create/ModifySystemTitleEffect``effect_type` 反序列化到明确 struct启用 `DisallowUnknownFields`
- 数值边界:`boost_x1000/chance_x1000/cap_x1000` 禁止负数,限制最大值;`target_prize_ids` 长度限制与去重;
- 运行期解析失败不再静默:记录结构化告警日志,并在管理端提示配置异常。
4) 优惠券/道具卡防滥用
- 模板配额与上限:发放时扣减可用数量,事务内完成;超量拒绝。
- 用户持有上限与重复发放策略:同模板在时间窗口内单用户限制;引入幂等键防重复请求。
- 操作审计与限流:关键接口限流、审批或二次确认;记录审计日志。
5) 抽奖安全稳健
- 保持现有 HMAC 与拒绝采样;确保 `commit_master_key` 仅从安全环境加载;周期性轮换与版本化控制。
## 交付内容
- 安全问题清单(带文件与行号)。
- 修复清单与实施顺序(先密钥与鉴权→后特效参数校验→最后配额/审计/限流)。
- 修复后的验证方案:单元测试、接口防护测试、管理端提示验证。
如确认,我将按上述方案逐项实施修复并提交相应代码与测试。

View File

@ -1,72 +0,0 @@
## 目标
- 补齐工作台 4 个模块的真实数据接口与前端对接:订单转化漏斗、活动中奖分析、用户概述、抽奖动态文案完善。
## 后端接口设计(统一前缀 `/api/admin/dashboard`
### 1) 订单转化漏斗 `GET /order_funnel`
- 入参:`rangeType=today|7d|30d|custom``start/end`custom 时必填)
- 出参:`FunnelData[]`(与 `web/admin/src/api/operations.ts` 定义一致)
- 统计口径:
- 访问用户:优先取 `log_request``path LIKE '/api/app/%'` 的唯一访问(去重 `tid` 或 IP/UA范围时间内`internal/repository/mysql/model/log_request.gen.go:13-27`
- 下单用户:`orders.created_at` 范围内下单的唯一用户数(`orders.status` 任意)(`internal/repository/mysql/model/orders.gen.go:13-33`
- 支付用户:`orders.status=2`(已支付)范围内唯一用户数
- 完成订单:订单完成的判定(二选一并取并集):`orders.status=2 AND is_consumed=1` 或存在 `shipping_records.status IN (2,3)`(已发货/已签收)(`internal/repository/mysql/model/shipping_records.gen.go:13-31`
- 返回各阶段 `count`,并计算 `rate`(相对上一阶段)与 `lostCount`
### 2) 活动中奖分析 `GET /activity_prize_analysis`
- 入参:`activity_id`(必填),可选 `rangeType`
- 出参:`ActivityPrizeAnalysis``activity` + `prizes[]` + `summary`,与前端类型一致)
- 统计口径:
- 奖品模板:`activity_reward_settings``issue_id` 归属同活动的期)(`internal/repository/mysql/model/activity_reward_settings.gen.go:13-27`
- 中奖统计:`activity_draw_logs``reward_id` 分组统计 `winCount``is_winner=1``drawCount` 为期内抽奖总次数(`internal/repository/mysql/model/activity_draw_logs.gen.go:13-24`
- 概率(设置):`weight/Σweight*100`(模板无显式概率字段,采用权重归一化)
- 概率(实际):`winCount/drawCount*100`
- 成本:若模板 `product_id` 非空,关联 `products.price` 作为单件成本(`internal/repository/mysql/model/products.gen.go:13-25`);否则成本为 0 或采用等级默认表(可配置)
- 活动信息:`activities` + `activity_issues` 组合(名称、时间范围、参与人数=distinct `user_id`,总抽奖次数)
### 3) 用户概述 `GET /user_overview`
- 入参:`rangeType`
- 出参:包含:
- `chart`: 最近 N 个时间桶(默认 9 个月)用户注册量序列(来源 `users.created_at`
- `metrics`: `totalUsers`(系统总量)、`totalVisits``log_request``/api/app/%` 请求计数)、`dailyVisits`(当天 `/api/app/%`)、`weeklyGrowth`(近 7 天 vs 上 7 天访问增幅)
- 说明:若实际“访问”定义需更严格,可后续改为埋点表;当前采用请求日志近似。
### 4) 抽奖动态增强 `GET /draw_stream`(已有)
- 增加字段:`activityName``issueNumber``prizeName`(中奖时)
- 数据来源:`activity_issues``activities``activity_reward_settings.name`
- 现有实现位置:`internal/api/admin/dashboard_admin.go:270-317`(将补充字段)
### 5) 通用
- 时间范围工具沿用:`parseRange/previousWindow/daysBetween``internal/api/admin/dashboard_admin.go:365-417`),对 `custom` 做 30 天上限;趋势分桶工具已实现(`normalizeGranularity/buildBuckets`)。
- 路由注册:在 `adminAuthApiRouter` 添加新端点(参考 `internal/router/router.go:63` 附近已有 dashboard 路由)。
## 前端改动
- 文件:`web/admin/src/api/operations.ts`
- `fetchOrderFunnel(range)``GET admin/dashboard/order_funnel`
- `fetchActivityList()``GET admin/dashboard/activities`(返回 `id,name,type,start,end,status,totalDraws,totalParticipants`;也可直接用现有 `GET /api/admin/activities` 的列表按需映射)
- `fetchActivityPrizeAnalysis(activityId)``GET admin/dashboard/activity_prize_analysis`
- `fetchUserOverview(range)`(新增) → `GET admin/dashboard/user_overview`
- 组件对接:
- `order-funnel.vue` 已调用 `fetchOrderFunnel``web/admin/src/views/dashboard/console/modules/order-funnel.vue:148-153`),直接切换数据源即可。
- `activity-prize-analysis.vue` 使用 `fetchActivityList/fetchActivityPrizeAnalysis``web/admin/src/views/dashboard/console/modules/activity-prize-analysis.vue:219-225``344-357``367-375`),切换到真实 API。
- `active-user.vue` 当前为静态数据(`web/admin/src/views/dashboard/console/modules/active-user.vue:31-46`),改为使用 `fetchUserOverview` 渲染条形图与 4 个指标。
- `dynamic-stats.vue` 增强显示中奖文案:“用户在【活动-期】中奖【奖品名】”(使用新增字段),保留参与与中奖标签(`web/admin/src/views/dashboard/console/modules/dynamic-stats.vue`)。
## 返回示例
- `GET /api/admin/dashboard/order_funnel``[{"stage":"访问用户","count":12580,"rate":100,"lostCount":0}, ...]`
- `GET /api/admin/dashboard/activity_prize_analysis`:结构与前端定义一致,概率为权重归一化。
- `GET /api/admin/dashboard/user_overview``{"chart":[{"date":"2025-03","value":1200},...],"metrics":{"totalUsers":12345,"totalVisits":45678,"dailyVisits":1234,"weeklyGrowth":"+8%"}}`
- `GET /api/admin/dashboard/draw_stream`:增加 `activityName/issueNumber/prizeName` 字段。
## 验收
- 工作台四模块均显示真实数据各接口性能在默认范围≤30 天)内响应合理。
- 环比与趋势计算准确;异常/空数据有合理降级。
## 后续可选优化
- 为 `log_request.created_at``orders.created_at``shipping_records.status``activity_draw_logs.created_at` 增加索引。
- 活动奖品“成本”可从商品资料或运营配置独立维护,避免 0 值。
确认后我将:
1. 补充 3 个新端点与 `draw_stream` 字段;注册路由;
2. 替换 `operations.ts` 的 Mock 为真实请求;
3. 调整前端 3 个模块的渲染逻辑;
4. 进行构建与联调,提供验证指令与预览链接。

View File

@ -1,46 +0,0 @@
## 剩余完善项
- 积分恢复比例与幂等:部分/全额退款的积分恢复算法与重复保护
- 通知事件持久化与去重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_transactionstransaction_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. 测试与联调:全链路验证并修正细节

View File

@ -1,16 +0,0 @@
## 剩余任务
- 后端:
- 积分恢复比例与幂等:部分退款按比例恢复(累计不超抵扣积分)、全额一次性恢复;增加累计校验避免重复恢复
- 通知事件入库与去重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 明细,计算累计已退与可退余额
- 前端:
- 金额统一元格式化展示(两位小数),保留分用于接口与导出
- 订单详情补充交易号/退款号/渠道/支付方式,展示累计已退与可退余额(元)
- 对账入口页面:账单导入与差异列表
- 幂等与安全:核查唯一索引与条件更新,补充异常告警
## 验收
- 覆盖单元与集成测试(换算、比例恢复、幂等、对账差异)
- 前后端构建与联调通过;数据持久化与展示口径一致

View File

@ -1,35 +0,0 @@
## 目标
- 在 APP 端新增接口,直接返回当前登录用户在 `user_inventory` 表中的分页数据,用于“用户中奖数据”查询。
## 接口设计
- 路径:`GET /api/app/users/{user_id}/inventory`
- 鉴权:`AppTokenAuthVerify`,以会话 `ctx.SessionUserInfo().Id` 为准(参考 `internal/api/user/points_app.go:47`)。
- 入参query`page`默认1`page_size`默认20最大100
- 出参JSON
- `page``page_size``total`
- `list`:元素为 `model.UserInventory`,字段:`id,user_id,product_id,order_id,activity_id,reward_id,status,remark,created_at,updated_at`
## 数据来源
- 直接读取 `user_inventory` 表(模型:`internal/repository/mysql/model/user_inventory.gen.go:13`)。
- 分页与排序:按 `id DESC`,与管理端一致(参考 `internal/api/admin/users_admin.go:246-256` 的资产列表实现风格)。
## 实现方案
1. 新增 `internal/api/user/inventory_app.go`
- 定义 `listInventoryRequest/listInventoryResponse``list``[]*model.UserInventory`)。
- `handler.ListUserInventory()`
- 绑定分页参数→会话用户 ID→在 `h.readDB.UserInventory` 上过滤 `user_id``Count``Find``Order(ID.Desc)``Offset/Limit`)。
- 组装分页响应并 `ctx.Payload`
- Swagger 注释与现有风格一致(参考 `internal/api/user/coupons_app.go:34-46`)。
2. 路由注册(`internal/router/router.go:236` APP 认证组):
- `appAuthApiRouter.GET("/users/:user_id/inventory", userHandler.ListUserInventory())`
## 测试要点
- 正常:用户存在记录,分页与 `total` 正确。
- 空数据:返回 `list=[]``total=0`
- 边界参数:`page≤0`→重置为 1`page_size` 超上限→重置为 100。
- 鉴权:忽略路径 `user_id`,以会话用户为准。
## 参考代码位置
- 模型定义:`internal/repository/mysql/model/user_inventory.gen.go:13`
- 路由分组:`internal/router/router.go:236`
- 鉴权风格:`internal/api/user/points_app.go:47`

View File

@ -1,104 +0,0 @@
## 概述
- 目标:提供一个可权限控制的“活动复制”后端接口与管理端前端交互,支持深度复制期次与奖品信息,生成一个状态为“未开始”的新活动,并保证关联关系与数据一致性。
- 范围:仅管理端(/api/admin/*)可用;前端在活动管理页增加“复制”入口与对话框流程。
## 后端设计
### 路由与权限
- 路由:`POST /api/admin/activities/:activity_id/copy`
- 鉴权沿用管理端拦截器JWT + 角色/动作),新增动作码如 `activity.copy`(与现有 `RequireAdminAction` 机制一致)。
- Swagger`/api/admin/activities` 下新增复制接口文档(请求/响应示例与错误码)。
### 请求/响应契约
- 请求:`activity_id`(路径参数,必填)
- 响应:`{ new_activity_id: number, status: 'success' }`;错误场景返回标准错误码与消息(如未授权、找不到活动、复制失败)。
### 复制范围与字段策略
- 活动主表(`Activities`):复制除运行态统计外的业务字段(如 `name/banner/activity_category_id/price_draw/is_boss``status` 固定为 “未开始”;`start_time/end_time` 可按需求重置为空或原值保留(默认重置为空)。
- 期次表(`ActivityIssues`):复制配置与时间安排(如期次名称、时间窗、规则参数等),新的 `issue` 关联新的 `activity_id`
- 奖品表(`ActivityRewards`):复制奖品类型、数量、概率等,新的奖品关联新的 `issue_id`
- 其他关联:不复制抽奖日志、收据、随机承诺历史等运行态数据;仅复制可配置静态数据。
### 实现步骤(事务 + 深度复制)
1. 读取源活动(校验存在且可复制)。
2. 开启事务(`writeDB.Begin()`)。
3. 新建活动记录(`status=未开始`,重置 `start/end`),拿到 `new_activity_id`
4. 查询源活动的全部期次(按 `activity_id`),按批次复制:
- 将源期次映射为新期次(替换 `activity_id`),批量插入(`CreateInBatches`),建立 `old_issue_id -> new_issue_id` 映射表。
5. 查询源期次对应的全部奖品,按批次复制:
- 使用映射替换为新的 `issue_id`,批量插入。
6. 提交事务;返回 `new_activity_id` 与状态。
- 失败回滚:任何步骤出错时回滚并返回错误码与错误信息。
### 数据一致性与约束
- 全流程单事务保证跨表一致性。
- 外键关系通过 ID 映射正确重建(`issue_id``activity_id`)。
- 使用 `gorm.Session{SkipHooks:false}` 保持必要校验;如性能需要可在复制路径禁用不必要的钩子(经评估再启用)。
### 性能优化策略
- 查询与插入采用分页/批量:`limit/offset` 读取、`CreateInBatches(batchSize=1000)` 写入。
- 仅选择必要字段,避免大 JSON/Blob 字段。
- 大数据量策略:当期次或奖品总数超阈值(如>2万返回 `202 Accepted` 并创建异步任务(可选扩展);当前版本先同步实现并在前端加入进度提示与超时 UI。
### 错误码与日志
- 复用项目错误码规范与上下文绑 trace`internal/pkg/trace`)。
- 记录复制耗时、批次数与行数,便于审计与性能分析。
### 影响文件(后端)
- 路由:`internal/router/router.go`(新增 `POST /api/admin/activities/:activity_id/copy`
- 控制器:`internal/api/admin/activities_copy_admin.go`(新建,处理入参、鉴权、调用服务层)
- 服务层:`internal/service/activity/activity_copy.go`(新建,封装事务与深度复制逻辑)
- DAO/模型:复用 `internal/repository/mysql/dao/gen.go``Query``Activities/ActivityIssues/ActivityRewards`)与对应 `.gen.go` 模型
- Swagger`docs/swagger.yaml``docs/docs.go`(新增接口文档)
## 前端设计(管理端)
### 交互流程
1. 用户在活动管理页点击“复制”按钮。
2. 弹出复制对话框,异步加载活动下拉(显示名称+ID支持搜索/分页)。
3. 选择目标活动 → 点击“确认”。
4. 调用后端复制接口显示处理中状态loading
5. 成功:提示成功并跳转到新活动编辑页(`/activity/wizard?activityId=...`)或刷新列表;失败:提示错误详情。
### 组件与页面改动
- 活动管理页:`web/admin/src/views/activity/manage/index.vue`
- 在列表或操作区增加“复制”入口按钮
- 引入 `CopyActivityDialog`(新建组件或内联对话框)
- 复制对话框组件:`web/admin/src/views/activity/manage/CopyActivityDialog.vue`
- 下拉框(远程搜索/分页),确认/取消按钮
- 状态提示loading、成功、失败
### API 封装与权限
- API`web/admin/src/api/adminActivities.ts`
- 新增 `copyActivity(activityId: number)``POST admin/activities/${activityId}/copy`
- 活动列表接口复用现有 admin 列表或新增轻量下拉接口
- 权限显示:根据用户的动作权限(如 `activity.copy`)决定是否展示“复制”按钮。
### 错误处理与状态提示
- 统一使用项目的消息组件与错误拦截(`@/utils/http`)。
- Loading 覆盖对话框;失败显示具体原因(来自后端错误码)。
- 大数据量复制时,提示“复制时间较长”,并在超时后提供轮询或后台进度(后端异步模式可选)。
### 影响文件(前端)
- `web/admin/src/views/activity/manage/index.vue`(增加按钮、接入对话框)
- `web/admin/src/views/activity/manage/CopyActivityDialog.vue`(新建组件)
- `web/admin/src/api/adminActivities.ts`(新增复制接口方法)
- 如需权限常量:`web/admin/src/router/guards/beforeEach.ts` 或权限配置处进行动作码接入
## 验收标准
- 后端:
- `POST /api/admin/activities/:activity_id/copy` 在合法权限下返回 200包含 `new_activity_id`;事务复制期次与奖品,关联正确,状态为“未开始”。
- 非法权限或不存在的活动返回规范错误码。
- 大数据量下复制耗时可接受且不阻塞服务。
- 前端:
- 管理页出现“复制”按钮;弹框可加载活动列表并选择;调用接口后有明显状态提示。
- 成功后刷新或跳转到新活动编辑页;错误时信息清晰。
## 交付项
- 新接口实现路由、控制器、服务、Swagger 文档)。
- 前端交互(按钮+对话框+API 封装)。
- 基础单元测试(服务层:期次与奖品复制的映射与事务回滚路径)。
## 后续可选扩展
- 异步复制任务 + 进度查询(`202 Accepted` + `job_id`)。
- 复制参数增强(重命名、时间偏移、是否复制奖品池等)。
请确认以上方案后,我将开始实现并提交相应代码改动与测试。

View File

@ -1,56 +0,0 @@
## 结论
- 不需要单独开新接口;在现有“统一接口”体系内,通过活动类型与策略插件即可兼容「一番赏」。
- 做法:以统一的资源与动作(创建/生成/列表/抽取/验证)为主干,按活动类型切换具体策略与字段含义。
## 统一接口适配方案
- 活动类型标识:在 `raffle_event` 增加 `activity_type`(如 `ichiban_position`) 与 `capabilities`(如 `choice_input=position``commit_reveal=true`)。
- 统一端点保持:
- 管理端:`POST /admin/events``POST /admin/events/{id}/generate``POST /admin/events/{id}/activate``POST /admin/events/{id}/close`
- 用户端:`GET /events/{id}``GET /events/{id}/choices``POST /events/{id}/draw``GET /events/{id}/verify`
- 字段语义按类型切换:
- `GET /events/{id}/choices`:一番赏返回可选 `slotIndex[]`;其他玩法可能返回空/不同结构。
- `POST /events/{id}/draw`:一番赏请求体携带 `slotIndex`;其他玩法携带各自所需的 `choice` 或为空(纯随机)。
- `GET /events/{id}/verify`:若 `commit_reveal=true`,统一返回 `salt``commitment_root`;无承诺玩法返回空/禁用。
## 策略插件化
- 定义通用接口 `ActivityPolicy`
- `generate(event, prize_types)` 预生成逻辑
- `presentChoices(event)` 返回用户可选项
- `validateChoice(event, choice)` 校验输入
- `draw(event, user, choice, tx)` 开奖事务
- `verify(event)` 承诺揭示
- 为「一番赏」实现 `IchibanPositionPolicy`;其他玩法实现各自策略,统一由路由/服务层按 `activity_type` 分派。
## 数据与契约最小改动
- 数据库:
- `raffle_event` 增:`activity_type``capabilities(json)``commitment_root``salt_hash`(适用于 commit-reveal 类型)
- 复用 `prize_slot``user_draw`;无需新表,仅根据类型解释 `slot_index` 的含义。
- 接口契约:
- `GET /events/{id}` 增加 `activityType``capabilities` 字段,前端可按能力渲染。
- 统一错误码:`CHOICE_INVALID``CHOICE_CONFLICT``EVENT_CLOSED` 等适用于所有玩法。
- 幂等与并发控制沿用统一实现。
## 前端适配
- 读取 `capabilities.choice_input`
- `position`:渲染位置网格并调用统一 `choices``draw(slotIndex)`
- 其他类型:按能力渲染不同输入组件或隐藏选择入口(纯随机)。
- 统一交互:下单/支付/抽取/结果页/分享逻辑一致,仅差异在“选择输入”。
## 公平与验证统一化
- 对支持承诺的玩法统一启用 `commit-reveal`
- 管理端在 `generate` 返回或活动页显示 `commitment_root`
- 关闭活动时 `verify` 公开 `salt` 与必要证明;
- 不支持承诺的玩法该端点返回空或 404。
## 测试与回归
- 策略切换回归:对 N 种玩法执行同一套接口用例,确保契约不变。
- 并发/幂等:统一覆盖;一番赏专项测试“同一位置并发争抢”。
- 能力探测:前端依据 `capabilities` 自适应渲染的快照测试。
## 迁移与兼容
- 现有客户端无需改路由;仅根据 `capabilities` 决定是否展示“选择位置”。
- 服务端新增策略模块与少量字段;旧玩法默认 `commit_reveal=false``choice_input=none`
## 验收准则
- 统一端点下,所有玩法均可正常运行。
- 「一番赏」玩法可完成预生成、选择位置、开奖、并发安全、事后可验证。

View File

@ -1,68 +0,0 @@
## 总体方案
- 引入统一软删除机制:所有支持回收站的业务表增加 `deleted_at DATETIME NULL``deleted_by BIGINT NULL` 字段,并在 ORM 层启用 GORM Soft Delete 以默认过滤已删除记录。
- 提供中心化回收站接口与界面:集中列出各类型的软删除数据,支持恢复与彻底删除(二次确认)。
- 权限与菜单对齐:仅管理员可访问回收站;侧边栏在“系统管理”下新增“回收站”菜单,配置角色可见与按钮权限。
## 数据库改造
- 目标表(根据现有模型与端点清单):`activities``activity_issues``activity_reward_settings``products``product_categories``banner``guild``system_titles``system_title_effects``system_item_cards``system_coupons``menus``menu_actions``roles``role_users`
- 变更内容:
- 添加列:`deleted_at DATETIME NULL`(建立索引以提升查询效率)、`deleted_by BIGINT NULL`
- 后续通过 `cmd/gormgen` 重新生成 `internal/repository/mysql/{model,dao}/*.gen.go`,使模型包含 `gorm.DeletedAt``DeletedBy` 字段。
- 迁移策略:当前仓库未集成迁移工具,计划新增 `migrations/` 与 SQL 脚本(或集成 `goose`),在测试/生产库执行列新增与索引建立,完成后重新生成 ORM 代码。
## 后端改造
- 技术栈:`gin` + `gorm`(已存在),读写库封装见 `internal/repository/mysql/mysql.go`,路由集中在 `internal/router/router.go`
- 删除逻辑切换为软删除:
- 将所有 `DELETE /api/...` 对应的处理器由物理删除改为 `gorm` 软删除:`db.Delete(&model)`,自动写入 `deleted_at`;同时通过上下文注入当前用户 ID 写入 `deleted_by`(更新列)。
- 涉及端点示例(集中定义于 `internal/router/router.go` 对应 handler活动、期数、奖励、工会、商品、分类、轮播图、称号/特效、道具卡、优惠券、系统菜单/动作、角色、角色成员等。
- 查询默认过滤:
- 使用含 `gorm.DeletedAt` 的模型,`gorm` 默认生成 `WHERE deleted_at IS NULL`;确保服务与 DAO 层不使用 `Unscoped()`,避免误返回已删除数据。
- 回收站接口:新增 `internal/api/admin/system_recycle.go` 与服务层 `internal/service/recycle/recycle_service.go`
- `GET /api/admin/recycle`:参数 `type`枚举activity、issue、reward、product...)、分页;实现为 `Unscoped().Where("deleted_at IS NOT NULL")` 列表,返回原始字段与 `deleted_at``deleted_by`
- `POST /api/admin/recycle/restore`:参数 `type,id``Unscoped()` 更新目标记录 `deleted_at=NULL, deleted_by=NULL`,恢复数据。
- `DELETE /api/admin/recycle`:参数 `type,id``Unscoped().Delete(&model)` 执行物理删除。
- 关联完整性:
- 软删除不级联子表;外键仍保持,恢复后关联自然可用。
- 若业务场景需要父子一致软删除(可选扩展),在服务层增加按类型的联动策略,但默认不做级联以降低风险。
- 权限拦截:
- 以上回收站接口全部走管理员认证中间件 `internal/router/interceptor/admin_auth.go::AdminTokenAuthVerify`
- 彻底删除在服务层进行额外二次确认校验(配合前端确认对话框),后端也校验 `role``action` 权限。
## 前端改造web/admin
- 框架:`vue3` + `vue-router` + `pinia` + `element-plus`,侧边栏位于 `web/admin/src/components/core/layouts/art-menus/art-sidebar-menu`,菜单数据由路由模块/后端菜单驱动。
- 路由与页面:
- 新增路由模块 `web/admin/src/router/modules/system/recycle.ts``path:'/system/recycle'``meta.roles:['admin']``meta.authList:['recycle:list','recycle:restore','recycle:forceDelete']`
- 新增页面 `web/admin/src/views/system/recycle/index.vue`
- 顶部类型筛选(下拉:活动、商品、工会…)。
- 表格列出删除数据(含关键原字段、`deleted_at``deleted_by`),分页。
- 行操作:`恢复`(调用 `POST /api/admin/recycle/restore`)、`彻底删除``ElMessageBox.confirm` 二次确认后调用 `DELETE /api/admin/recycle`)。
- API 封装:`web/admin/src/api/system/recycle.ts`(使用现有 Axios 封装 `web/admin/src/utils/http/index.ts``listDeleted(type,page,size)``restore(type,id)``forceDelete(type,id)`
- 菜单项:
- 后端在 `menus` 表增加“系统管理/回收站”,`path:'/system/recycle'`,并在角色分配接口 `POST /api/role/:role_id/menus` 仅赋予管理员;前端 `ListSimpleMenus` 拉取后自动渲染。
- 权限指令与守卫:
- 按按钮权限 `meta.authList` 与角色 `meta.roles` 控制显示;普通用户不显示菜单,无法访问路由或调用接口。
## 权限控制细则
- 仅管理员角色拥有“回收站”菜单与相关按钮权限;普通用户删除操作也走软删除,但无法访问回收站接口。
- 彻底删除在前端强制二次确认,并在后端再次校验权限与保护条件(如必须管理员)。
## 测试方案
- 后端Go `testing`
- `internal/api/admin/system_recycle_test.go`
- 删除后记录存在于回收站接口,原查询不可见(验证 `deleted_at` 与默认过滤)。
- 恢复后记录重新可见,`deleted_at`/`deleted_by` 为空。
- 权限:非管理员访问回收站接口返回 `401/403`;管理员正常。
- 关联完整性:恢复后关联查询正常(父子外键未破坏)。
- 前端Vitest
- `web/admin/src/tests/system/recycle/recycle.test.ts`
- 菜单与路由权限:管理员可见“回收站”,普通用户不可见。
- 列表加载与分页、恢复按钮触发成功提示与刷新、彻底删除二次确认弹窗与成功流程。
- 401/403 拦截按现有拦截器工作(参考 `src/tests/auth/401-error-handling.test.ts`)。
## 交付与验证
- 执行数据库迁移并生成 ORM 代码 → 修改删除逻辑到软删除 → 新增回收站接口与前端页面 → 配置菜单与角色 → 编写并运行测试。
- 验收标准:
- 所有默认查询不返回已删除记录。
- 回收站能列出各类删除记录,恢复/彻底删除工作正常。
- 权限严格生效;普通用户删除进入回收站但不可访问回收站界面。
- 恢复后关联数据完整、功能正常。

View File

@ -1,92 +0,0 @@
## 范围与原则
* 仅完善用户端(管理端与其 API 保持不变)。
* 目标:用户可在 App 端对其奖品资产执行:邀请他人填写地址、兑换积分;若用户已有默认地址,可在 App 端提交发货申请(生成待发货记录),后续由管理端流程发货。
## 用户端功能
* 我的资产列表与详情
* 展示每条资产的当前状态:`持有``共享待地址``待发货``已发货``已兑换`
* 操作区:
* `邀请他人填写地址`:生成分享链接/二维码,显示有效期与复制按钮;允许撤销与重新生成。
* `兑换为积分`:弹窗确认兑换规则与积分值,成功后资产变为“已兑换”。
* `申请发货`(可选):当用户已有默认地址时展示;提交后资产进入“待发货”。
* 分享落地页(公域 H5/Web
* 通过一次性 `share_token` 打开表单,填写地址后提交;成功页提示“已提交,等待发货”。
* 链接过期/撤销/已消费时展示对应状态页。
## 后端(仅用户端与公域接口)
* 创建共享地址请求App 端)
* `POST /api/app/inventory/{inventory_id}/address-share/create`
* 返回:`share_url``expires_at``qrcode`(可选)与当前资产状态。
* `POST /api/app/inventory/{inventory_id}/address-share/revoke`
* 撤销未使用的请求,状态改为 `revoked`
* 申请发货App 端,可选)
* `POST /api/app/inventory/{inventory_id}/request-shipping`
* 条件:用户存在默认地址;未兑换/未发货。
* 行为:创建 `ShippingRecords` 为“待发货”,后续由管理端更新物流信息。
* 兑换积分App 端)
* `POST /api/app/inventory/{inventory_id}/redeem-to-points`
* 规则:依据奖品配置(如 `exchange_points_amount`);写 `user_points_ledger` 动作 `REDEEM_REWARD`,更新 `user_points`;资产标记为“已兑换”。
## 数据与安全
* 共享请求模型:`shipping_shared_address_requests`(一次性 `share_token`、有效期、状态、审计字段、地址快照或 `user_address_id`)。
* 状态机:`OWNED``AWAITING_ADDRESS_SHARED``SHIPPING_PENDING``SHIPPED`;或 `OWNED``REDEEMED`
* 幂等与并发:事务校验资产状态;链接一次性消耗;重复操作返回已处理错误码。
* 审计记录创建人、撤销、提交人可匿名、IP/UA所有动作可追踪。
## 前端实现App
* App 页面
* `资产详情页`:按钮与状态展示、结果提示、列表刷新;分享弹窗含链接、二维码、有效期、撤销。
* `积分兑换弹窗`:展示规则与额度,确认后调用兑换 API。
* `申请发货`:若存在默认地址,直接申请;否则引导“邀请他人填写地址”或“去设置默认地址”。
## 测试与验收
* 单元测试:共享请求生命周期(创建/撤销/过期/提交)、资产状态流转、兑换与申请发货幂等并发。
* 集成测试App 端创建链接→公域表单提交→资产进入待发货;重复提交与过期处理;兑换后禁止申请发货。
* 验收:用户端操作闭环生效;状态与流水正确;无重复副作用;管理端无需改动即可继续履约。
## 文档与 Swagger
* 更新 App 与公域接口说明、错误码、状态枚举与审计字段;保留管理端文档不变。
* 执行时按 6A 规范生成 ALIGNMENT/CONSENSUS/DESIGN/TASK/ACCEPTANCE/FINAL 文档并持续维护。
## 上线与回滚
* 先灰度用户端;监控链接使用量、失败率与发货效率。
* 如需回滚,关闭公域入口并撤销未消费请求;资产状态回退并保留审计。

View File

@ -1,127 +0,0 @@
## 当前情况
- 管理端“工作台”路由:`web/admin/src/router/modules/dashboard.ts:3-24`
- 工作台页面:`web/admin/src/views/dashboard/console/index.vue`
- 工作台数据源:`web/admin/src/api/dashboard.ts` 全为本地 Mock无真实后端调用
- 后端已存在管理端认证路由分组:`internal/router/router.go:55-151`但未注册任何“dashboard”端点
## 技术栈与约束
- 后端Gin + GORM Gen入口 `main.go:52-67`,路由 `internal/router/router.go`
- 数据模型可用:`internal/repository/mysql/model/*.gen.go`(如 `users``activity_draw_logs``user_item_cards``user_points``guild_members`
- 认证:管理端统一走 `Authorization` 头(`LoginVerifyToken`),同组复用:`internal/router/router.go:56`
## 端点设计(前缀 `/api/admin/dashboard`
1) `GET /cards`
- 入参:`rangeType=today|7d|30d|custom`,可选 `start=YYYY-MM-DD&end=YYYY-MM-DD`custom 时)
- 出参:`CardStat`(与 `web/admin/src/api/dashboard.ts` 一致)
- 统计口径:
- `itemCardSales``user_item_cards.created_at` 范围新增数(`user_item_cards`
- `drawCount``activity_draw_logs.created_at` 范围次数(`activity_draw_logs`
- `newUsers``users.created_at` 范围新增数(`users`
- `totalPoints`:全量有效积分总和(`user_points.points` 过滤 `valid_end` 未过期),实现参考 `internal/service/user/points_balance.go:8-21`
- 环比字段 `*_Change`:与上一等长时间窗口对比百分比(向下取整),无前窗则返回 `+0%`
2) `GET /user_trend`
- 入参:`rangeType``granularity=day|week|month`
- 出参:`UserTrendResp`
- 统计:按粒度聚合 `users.created_at` 计数生成时间序列
3) `GET /draw_trend`
- 入参:同上
- 出参:`DrawTrendResp`
- 统计:按粒度聚合 `activity_draw_logs.created_at` 计数生成时间序列
4) `GET /new_users`
- 入参:`page``page_size`
- 出参:`NewUserListResp`
- 明细项字段:
- `pointsBalance`:用户有效积分余额(复用 `GetPointsBalance` 逻辑 `internal/service/user/points_balance.go:8-21`
- `inventoryCount``user_inventory` 记录数(状态过滤视业务)
- `itemCardCount``user_item_cards` 有效未使用数量(`status=1`
5) `GET /draw_stream`
- 入参:`since_id`(可选)、`limit`(默认 50最大 100
- 出参:`DrawStreamResp`
- 统计:按 `id` 递减拉取最近抽奖日志,关联 `users.nickname``activity_issues.name` 生成展示项;返回 `sinceId = max(id)` 以便前端轮询增量
6) `GET /todos`
- 入参:`limit`(默认 50最大 100
- 出参:`TodoListResp`
- 规则:
- `bind_mobile``users.mobile IS NULL OR ''`
- `join_guild``NOT EXISTS guild_members WHERE user_id=users.id AND status=1`
- 返回 `avatar``nickname``taskLabel` 友好文案
## 后端改动点
- 新增处理器:`internal/api/admin/dashboard_admin.go`
- 采用现有 handler 模式:`internal/api/admin/admin.go:16-42`
- 为每个端点提供 `core.HandlerFunc`,入参校验使用 `ShouldBindForm/JSON``internal/pkg/validation`
- 新增服务层:`internal/service/admin/dashboard_*.go`
- `GetCardStats(ctx, range)`, `GetUserTrend(ctx, range, granularity)`, `GetDrawTrend(...)`
- `ListNewUsersWithStats(ctx, page, pageSize)`多表聚合users + user_points + user_item_cards + user_inventory
- `ListDrawStream(ctx, sinceID, limit)`:抽奖日志联表 users、activity_issues
- `ListTodos(ctx, limit)`:基于 users 与 guild_members 规则
- 路由注册:在管理端认证组添加
- 文件:`internal/router/router.go`
- 组:`adminAuthApiRouter``internal/router/router.go:55-151`
- 注册形如:
- `adminAuthApiRouter.GET("/dashboard/cards", adminHandler.DashboardCards())`
- `adminAuthApiRouter.GET("/dashboard/user_trend", adminHandler.DashboardUserTrend())`
- `adminAuthApiRouter.GET("/dashboard/draw_trend", adminHandler.DashboardDrawTrend())`
- `adminAuthApiRouter.GET("/dashboard/new_users", adminHandler.DashboardNewUsers())`
- `adminAuthApiRouter.GET("/dashboard/draw_stream", adminHandler.DashboardDrawStream())`
- `adminAuthApiRouter.GET("/dashboard/todos", adminHandler.DashboardTodos())`
## 前端改动点
- 更新 `web/admin/src/api/dashboard.ts`
- 用 `request.get` 替换 Mock
- `fetchCardStats(range)``GET admin/dashboard/cards`
- `fetchUserTrend(range, granularity)``GET admin/dashboard/user_trend`
- `fetchDrawTrend(range, granularity)``GET admin/dashboard/draw_trend`
- `fetchNewUsers(page, pageSize)``GET admin/dashboard/new_users`
- `fetchDrawStream(sinceId, limit)``GET admin/dashboard/draw_stream`
- `fetchTodos(limit)``GET admin/dashboard/todos`
- 保持 TS 接口不变,便于无缝替换
## 数据来源与代码参考
- 路由分组:`internal/router/router.go:55-61`
- 抽奖日志分页示例:`internal/service/activity/draw_logs_list.go:9-28`
- 用户积分余额:`internal/service/user/points_balance.go:8-21`
- 用户综合统计:`internal/service/user/stats.go:13-38`
- 数据模型表:`internal/repository/mysql/model/*.gen.go`(如 `users.gen.go:15-29``activity_draw_logs.gen.go:13-24``user_item_cards.gen.go:13-28``guild_members.gen.go:13-23`
## 接口示例返回
- `GET /api/admin/dashboard/cards`
```json
{
"itemCardSales": 1234,
"drawCount": 5678,
"newUsers": 321,
"totalPoints": 98765,
"itemCardChange": "+12%",
"drawChange": "+8%",
"newUserChange": "+5%",
"pointsChange": "+3%"
}
```
- `GET /api/admin/dashboard/user_trend` / `draw_trend``{ "granularity": "day", "list": [{"date":"2025-11-01","value":100}, ...] }`
- 其他返回与 `web/admin/src/api/dashboard.ts` 对齐
## 验证与测试
- Swagger 注解与分组标签:沿用现有风格(参考 `internal/api/admin/users_admin.go:30-46`
- 集成测试:参考 `internal/api/admin/titles_admin_test.go` 新增 API 测试用例(登录获取 Token 后调用)
- 手动验证:
- 后端:`curl -H "Authorization: <token>" "http://localhost:<port>/api/admin/dashboard/cards?rangeType=7d"`
- 前端:工作台各模块正常展示且不再使用随机数据
## 性能与索引建议
- 为 `users.created_at``activity_draw_logs.created_at``user_item_cards.created_at` 建立索引
- 汇总接口注意分页与时间窗口限制,默认范围 7 天,可配置上限 30 天
## 交付范围与验收
- 完成 6 个端点后,前端工作台所有模块均有真实数据源
- 验收:页面展示与接口返回满足 TS 类型Swagger 文档可用;管理员认证校验生效
确认后我将:
- 在后端新增处理器与服务实现并注册路由
- 替换前端 `dashboard.ts` 的 Mock 为真实请求
- 补充必要的 Swagger 注释与最小测试用例

View File

@ -1,97 +0,0 @@
# 目标与范围
- 目标:落地微信小程序(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`

View File

@ -1,58 +0,0 @@
## 现状与缺口
- 审核未落地:`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` 无破坏性更改。
- 并发与一致性:审批通过与重复申请的幂等处理;用户离开后重新申请的限制延续。
- 回滚策略:所有写操作事务化;失败回滚不影响既有功能。
请确认以上方案,确认后我将分步骤提交后端与前端改造,并补齐测试与文档。

View File

@ -1,49 +0,0 @@
## 目标
* 在活动模块中彻底移除“批量测试/批量抽奖”入口与弹窗,避免在创建流程中出现。
* 保留其他模块(如“抽奖模拟盈亏”页面)的相似功能,不影响运营侧工具。
* 统一“新建活动”UI为 [https://www.artd.pro/docs/zh/ ](https://www.artd.pro/docs/zh/) 官方风格,保持与项目其他新建页面一致。
## 变更范围
* 移除组件引用与入口:
* `web/admin/src/views/activity/manage/index.vue`:删除 `BatchDrawDialog` 引用与渲染、`openBatchDraw` 按钮及逻辑。
* `web/admin/src/views/activity/list/index.vue`:删除批量抽奖按钮与 `BatchDrawDialog` 渲染。
* 组件保留但不再使用:
* `web/admin/src/views/activity/rewards/modules/batch-draw-dialog.vue` 保留于项目(供其他模块复用),但活动模块不再引用。
* 路由与流程:
* 无需调整路由;保留 `Operations/LotterySimulation` 等运营侧工具。
* 确保“创建活动 → 期数 → 奖品”流程不再出现任何测试入口。
## UI统一
* 两处向导页面完全采用官方 EP 风格(已部分完成):
* `ElSteps` simple 模式;
* `ElCard` 内容区;
* `ElForm` 默认尺寸与标准栅格;
* 移除自定义渐变、玻璃拟态与重阴影。
## 验收标准
* 活动模块中不再出现“批量测试/批量抽奖”按钮或弹窗。
* 创建流程不再被任何测试功能打断。
* 新建活动页面的视觉与项目其他新建保持一致EP 官方风格)。
* 构建与运行通过,页面与流程验证无误。
如确认,我将执行上述移除与统一改造,并完成构建与流程验证。

View File

@ -1,92 +0,0 @@
## 目标
- 以微信支付 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平台证书自动下载与缓存

View File

@ -1,55 +0,0 @@
## 目标
- 新用户列表增加:积分余额、资产、道具卡、优惠券、称号、上一次在线时间
- 实时抽奖动态展示:用户 | 活动 | 奖品
- 待办事项改为:未抽奖用户列表
## 后端改动
### 新用户列表扩展 `GET /api/admin/dashboard/new_users`
- 文件:`internal/api/admin/dashboard_admin.go`
- 在 `DashboardNewUsers()` 为每个用户追加:
- `couponCount``user_coupons` 有效/未使用计数
- `titleCount``user_titles` 数量(或仅活跃状态)
- `lastOnlineAt`:近似“最后活跃时间”,取多个行为表的最大时间:
- `activity_draw_logs.created_at`
- `orders.updated_at``paid_at`
- `user_points_ledger.created_at`
- `user_item_cards.updated_at`
- `user_inventory.updated_at`
- 注:当前日志表 `log_request` 未记录 `user_id`,无法精确映射在线时间;后续可优化为日志写入 `user_id` 字段。
- 返回结构:在 `newUserItem` 增加 `couponCount:number``titleCount:number``lastOnlineAt:string`
### 抽奖动态增强 `GET /api/admin/dashboard/draw_stream`
- 已新增字段:`activityName``issueNumber``prizeName`
- 保持现有结构,前端按“用户 | 活动 | 奖品”展示即可(`dynamic-stats.vue` 已适配)
### 待办事项改为未抽奖用户 `GET /api/admin/dashboard/todos`
- 文件:`internal/api/admin/dashboard_admin.go`
- 原“绑定手机号/加入公会”规则改为:
- 查询 `users`,过滤 `NOT EXISTS activity_draw_logs WHERE user_id=users.id`
- 返回字段:`userId/nickname/avatar``taskType` 固定为 `undrawn`,文案 `taskLabel='从未参与抽奖'`
- 保持分页/limit 与安全校验一致
## 前端改动
### 新用户模块 `web/admin/src/views/dashboard/console/modules/new-user.vue`
- 表格新增列:优惠券数、称号数、上一次在线时间
- 数据源类型:更新 `web/admin/src/api/dashboard.ts``NewUserItem` 接口增加上述3字段`fetchNewUsers` 返回保持一致
### 抽奖动态 `web/admin/src/views/dashboard/console/modules/dynamic-stats.vue`
- 已展示“活动名-期号”和中奖奖品名;文案保持“用户 | 活动 | 奖品”格式
### 待办事项 `web/admin/src/views/dashboard/console/modules/todo-list.vue`
- 调整列表文案与标签:从“绑定手机号/加入公会”改为“从未参与抽奖”的用户清单(调用同名接口)
## 验证
- 后端:`go build ./...` 通过;
- 接口验证:
- 新用户:`curl -H 'Authorization: <token>' 'http://localhost:8000/api/admin/dashboard/new_users?page=1&page_size=20'`
- 抽奖动态:`curl -H 'Authorization: <token>' 'http://localhost:8000/api/admin/dashboard/draw_stream?limit=50'`
- 待办(未抽奖用户):`curl -H 'Authorization: <token>' 'http://localhost:8000/api/admin/dashboard/todos?limit=50'`
- 前端:工作台“新用户”、“抽奖动态”、“待办事项”模块显示对应数据
## 后续可选优化
- 在 `log_request` 增加 `user_id` 字段,并在中间件从会话写入;将“上一次在线时间”改为日志精准口径
- 为关联查询增加必要索引以保证统计性能(`activity_draw_logs.user_id/issue_id``orders.user_id/status` 等)
确认后我将按上述文件路径逐项实现并联调。

View File

@ -1,79 +0,0 @@
## 目标
- 将现有单件发货接口扩展为批量接口,支持一次提交多个资产 ID{"inventory_ids":[52,53,...]})。
- 复用现有发货能力(默认地址、资产状态迁移、备注标记),保证幂等与错误可追踪。
## 现状
- 单件接口:`POST /api/app/users/{user_id}/inventory/request-shipping`,请求体 `{ inventory_id }`
- 处理器位置internal/api/user/request_shipping_app.go:30-47
- 服务层:`RequestShipping(ctx, userID, inventoryID)` 设置资产状态为已申请发货并记录备注
- 代码位置internal/service/user/address_share.go:120-126`UPDATE user_inventory SET status=3, ... remark+='|shipping_requested' WHERE status=1`
## 接口设计
- 新增:`POST /api/app/users/{user_id}/inventory/request-shipping-batch`
- 请求体:
- `inventory_ids` 数组,必填,长度 1100去重后处理
- `address_id` 可选;若不提供则使用用户默认地址
- 返回体:
- `address_id` 实际使用的地址 ID
- `success_ids` 已成功提交的资产 ID 列表
- `skipped` 数组:[{ id, reason }](如 not_found、not_owned、invalid_status、already_requested
- `failed` 数组:[{ id, reason }](如 DB 错误等)
## 行为与规则
- 地址选择
- 若提供 `address_id`:校验属于该用户且有效;否则返回 400
- 若未提供:读取默认地址;不存在则返回 400沿用现有单件逻辑
- 资产校验(逐个)
- 必须属于该用户
- `status=1`(可发货)才处理;`status=3`(已申请)则标记为 `already_requested` 并 skip幂等
- 不存在或不属于该用户 → `skipped.not_owned/not_found`
- 幂等性
- 若 remark 已包含 `shipping_requested` 或状态为 3则视为已处理加入 `skipped` 并不报错
- 原子性
- 批量以“逐条子事务”处理(每条调用服务层更新),确保单条失败不影响其他条目;最终返回成功/跳过/失败三类列表
- 审计
- 在 `user_points_ledger` 无需记录(该流程与积分无关);如需审计可在后续增加 `user_operations` 表留痕
## 服务层扩展
- 新增:`RequestShippings(ctx, userID int64, inventoryIDs []int64, addressID *int64) (addrID int64, success []int64, skipped []struct{id int64; reason string}, failed []struct{id int64; reason string}, err error)`
- 内部:
- 若 `addressID==nil`,读取默认地址(沿用单件方法)
- 循环:校验→调用现有 `RequestShipping(ctx, userID, inventoryID)`;捕获错误进行分流
- 可选优化:对同一用户的多件,合并一次地址校验;对 DB 更新使用独立事务(已在现有方法内处理)
## 处理器实现
- 新增处理器:`RequestShippingBatch()`
- 位置internal/api/user/request_shipping_app.go或新文件 `request_shipping_batch_app.go`
- 解析 `inventory_ids`(去重、长度限制);解析可选 `address_id`
- 调用服务层批量方法,组装响应
- 统一错误码:参数错误 `code.ParamBindError`,无地址 `10021`(沿用或新增),其他子项错误填入 `failed` 字段
## 错误码与返回示例
- 400 参数错误:`{"code":10023,"message":"invalid inventory_ids"}`
- 200 成功+部分跳过:
```
{
"address_id": 888,
"success_ids": [52, 53],
"skipped": [{"id": 54, "reason": "already_requested"}],
"failed": []
}
```
## 测试用例
- 有默认地址,提交 1、N 个有效资产 → 全成功
- 混合:包含非本用户、已申请、不存在 → 分别进 `skipped`
- 指定 address_id 非本用户 → 400
- 无默认地址且未指定 address → 400
- 幂等:重复提交相同资产 → 均进入 `already_requested`
## 兼容性
- 不改动现有单件接口;前端可增设批量勾选后调用新接口
- DB 无结构变化;仍依赖 `user_inventory.status``remark` 标记
## 交付内容
- 新增批量处理器与服务方法
- Swagger 注释与接口文档
- 单元测试:服务层批量逻辑、处理器参数校验
请确认按此方案实施,我将立即落地代码、接口与测试。

View File

@ -1,64 +0,0 @@
## 目标
- 为运营提供可控的批量操作能力:批量增加积分、批量发送优惠券、批量发放奖励。
- 满足权限、配额、上限、幂等与审计要求,闭环验证整个盲盒系统运营流程。
## 问题与现状
- 现有单用户接口:`POST /api/admin/users/:user_id/points/add``/coupons/add``/item_cards``/rewards/grant`;缺少批量维度与风控保护。
- 已加固优惠券发放需超管internal/api/admin/users_admin.go:596-616、发放配额与持有上限internal/service/user/coupon_add.go、道具卡数量与持有上限internal/service/user/item_card_add.go
- 仍缺:批量接口、幂等、审计与限流。
## API 设计
- 批量积分:`POST /api/admin/users/batch/points/add`
- Body`{ users: number[], amount: number, reason?: string, idempotency_key?: string }`
- 校验:超管;`amount > 0``users.length <= 5000`;可选日累计上限(防爆刷)。
- 行为:逐用户执行加分(重用现有服务层),支持幂等(按 `op_type+idempotency_key+users+amount` 去重)。
- 返回:`{ success: number, failed: number, details: Array<{user_id,status,msg}> }`
- 批量优惠券:`POST /api/admin/users/batch/coupons/add`
- Body`{ users: number[], coupon_id: number, quantity_per_user?: number, idempotency_key?: string }`
- 校验:超管;模板启用与有效期;配额与单用户持有上限(已在服务层);`quantity_per_user` 上限(<=5
- 行为:逐用户发券(重用 `AddCoupon`),事务分片(每批 100~200记录逐项状态幂等去重。
- 返回:同上。
- 批量奖励:`POST /api/admin/users/batch/rewards/grant`
- Body`{ users: number[], reward_id: number, quantity?: number, idempotency_key?: string }` 或按活动期:`POST /api/admin/activities/:activity_id/issues/:issue_id/rewards/batch_grant` `{ users: number[], reward_id: number, quantity?: number }`
- 校验:超管;奖励模板/库存;单用户持有上限(可与道具卡同策略);数量上限(<=10
- 行为:逐用户入库,事务分片,幂等去重。
- 返回:同上。
## 权限与风控
- 仅超管可调用批量接口;非超管拒绝。
- 限流:每分钟调用次数限制(可在拦截器或中间件层实现)。
- 上限请求用户数上限5000单用户数量上限模板配额检查用户持有上限已存在的加固沿用
- 幂等:支持 `idempotency_key`;服务层保存批次指纹,重复提交直接返回历史结果。
- 审计:记录批量操作日志(操作者、时间、对象数量、成功/失败明细摘要与请求指纹)。
## 后端改造点
- 路由:`internal/router/router.go`
- 新增:`POST /api/admin/users/batch/points/add``/batch/coupons/add``/batch/rewards/grant`
- 处理器:`internal/api/admin/users_admin.go`
- 新增批量处理 Handler结构体 Request/Response、超管校验、参数绑定、分片循环、调用服务层、汇总返回
- 服务层:`internal/service/user/*`
- 新增批量方法或在 Handler 中循环调用现有单个方法(推荐服务层添加批量封装,带事务与幂等指纹)。
- 中间件:可选全局限流与审计记录入口(日志/Audit DAO
## 前端(管理端)
- 称号运营页或用户管理页新增“批量操作”入口,支持:
- 用户选择(列表多选/CSV导入
- 操作类型(积分/优惠券/奖励)与参数(金额/模板/数量)
- 幂等键(可选自动生成)
- 执行后展示分项结果与失败原因下载。
## 测试与验收
- Postman 集合扩展三条批量接口;注入 admin 令牌后按顺序回归。
- 验收标准:
- 权限校验:非超管拒绝。
- 配额/上限:超量拒绝;成功/失败计数正确。
- 幂等:重复提交返回相同结果,不重复写入。
- 审计:生成批次日志记录(至少在系统日志中可查)。
## 交付
- 批量接口与处理器实现、服务层封装、路由挂载。
- 集合/脚本更新与验收报告(问题清单与修复项)。
确认后我将开始实现并提交对应代码与测试集合更新。

View File

@ -1,68 +0,0 @@
## 现状与问题
- 当前 APP 抽奖接口:`POST /api/app/activities/:activity_id/issues/:issue_id/draw`,处理函数 `internal/api/activity/draw_app.go:27-46`,服务侧使用 `internal/service/activity/draw_execute.go:12-89` 的可验证随机抽样HMAC-SHA256 + 拒绝采样)。
- 现有实现按期issue维度抽取奖励已具备收据与随机性证明但不同活动类型分类/玩法)未来可能有差异化前置校验、选择规则与奖励发放流程。
- 需求:我们将有很多活动,接口到底通用还是分活动?
## 结论选择
- 接口保持通用(单一端点),后端采用策略模式按活动类别/配置差异化实现逻辑。这样:
- 前端保持统一调用与响应结构,降低复杂度与版本碎片。
- 后端可插拔扩展不同活动的校验、选择、奖励发放与效果管道,避免复制粘贴与接口膨胀。
- 与现有 `Receipt` 结构兼容,保留可验证性。
## 技术方案
- 保留通用端点:`POST /api/app/activities/:activity_id/issues/:issue_id/draw`
- 引入“活动策略”接口(示例命名):
- `ActivityDrawStrategy`
- `PreChecks(ctx, activity, issue, user) error`(余额/订单/库存/状态/时间窗/限频)
- `SelectItem(ctx, issue, baseSelector) (selectedRewardID, proof)`(可复用当前 HMAC 随机选择器作为 `baseSelector`,策略可注入权重或过滤规则)
- `GrantReward(ctx, user, rewardID) error`(扣减数量、落库收据/日志、发放道具/实物/积分/券)
- `PostEffects(ctx, user, activity, issue, rewardID)`(道具效果、称号、积分等联动)
- 策略注册与路由:
- 基于 `activity.activity_category_id` 或活动配置选择策略;默认策略沿用当前实现(完全兼容)。
- 效果管道:
- 抽奖前置/后置效果处理如限时加权、Boss 限制、用户卡片效果),设计为中间件式管道,便于组合。
- 并发与一致性:
- 引入期级别乐观扣减或分布式锁(后续落地),确保高并发下数量与日志一致。
- 支持 `X-Idempotency-Key` 防重(后续扩展),避免重复下单/重复抽取。
- 监控与审计:
- 统一埋点:抽奖尝试、成功、失败原因、库存变化、用户画像。
- 审计日志与追踪 ID 与收据关联。
## API 契约
- 请求:保持不变;可新增可选头 `X-Idempotency-Key`
- 响应:保持 `receipt` 字段结构;策略仅影响内部选择/发放过程,不破坏契约。
- 错误码:标准化前置校验失败(余额不足、活动未开始、库存不足、限频)与系统错误。
## 数据与规则
- 数量规则:`original_qty` 为期初总数,`quantity` 当前剩余;不限量为 `-1`,选择与发放逻辑需正确处理。
- 收据:继续包含随机性证明与所选项快照,具备可验证性。
- 扣减与发放:在 `GrantReward` 中统一实现(现阶段代码只返回收据,发放与扣减建议纳入策略实现)。
## 变更范围
1. 新增 `internal/service/activity/strategy/` 策略接口与默认实现;注册表按分类 ID 选择策略。
2. 在 `ExecuteDraw()` 增强:
- 解析活动与用户上下文,调用策略 `PreChecks``SelectItem``GrantReward``PostEffects`
- 默认策略复用当前 `draw_execute.go` 的随机选择器。
3. `draw_app.go` 维持统一端点,增加对用户上下文的传递(如需要),契约不变。
## 分阶段实施
1. 引入策略接口与默认策略(不改变行为)。
2. 接入活动类别路由与注册(读取 `ActivityCategoryID`)。
3. 将扣减与发放迁移到策略中,补充事务与并发控制。
4. 增强效果管道,接入道具卡、称号、积分的已有模块。
5. 增加监控与限频、幂等,完善错误码。
6. 编写单元测试与集成测试:
- 权重选择正确性与拒绝采样无偏。
- 不限量与零库存边界。
- 并发扣减一致性。
- 幂等与限频。
## 验收标准
- 单一抽奖端点适配 2+ 类活动策略,无需前端切换接口。
- 收据结构与随机性证明保持一致,验证脚本通过。
- 库存扣减与奖励发放在并发下无不一致;关键路径有审计日志。
- 测试覆盖关键流程与边界条件。
## 后续扩展
- 按活动类型开放“策略配置”与“玩法参数”在管理端,可动态调整权重、效果、限频。
- 如某玩法确有完全不同交互(例如合成/多次抽/队列结算),再考虑单独端点,但仍优先在通用端点层面通过参数与策略实现。

View File

@ -1,71 +0,0 @@
## 执行逻辑(目标态)
### 参与下单(仅创建订单,不开奖)
- 接口:`POST /api/app/lottery/join`
- 入参:`activity_id``issue_id``count`(默认1≤N)、可选 `client_nonce`
- 流程:
- 查询活动单价 `price_draw`;计算 `total_amount = price_draw * count`
- 查询积分余额;计算需积分 `needPts = ceil(total_amount / 10)`;实际扣减 `usePts = min(balance, needPts)`
- 写订单:`draw_count = count``PointsAmount = usePts * 10``ActualAmount = total_amount - PointsAmount``Status=1`
- 当 `ActualAmount == 0` → 将订单置为已支付(`Status=2``PaidAt=now`),但不开奖
- 返回:`order_no``draw_mode``count`、金额与抵扣细节、`queued`
### 预下单(微信 JSAPI
- 接口:`POST /api/app/pay/wechat/jsapi/preorder`
- 校验:订单属于当前用户且 `Status=1`
- 返回:`wx.requestPayment` 参数;记录 `PaymentPreorders`
### 支付回调(入账)
- 接口:`POST /api/pay/wechat/notify`
- 行为:验签→记录交易→更新订单 `Status=2(PAID)``PaidAt`;幂等事件处理
- 触发:即时模式下调用 `DrawProcessor`;计划模式下等待调度
### 抽奖处理器(按订单维度执行)
- 输入:`order_id/order_no/activity_id/issue_id/draw_mode/draw_count`
- 即时模式:
- 读取已抽次数 `n = count(ActivityDrawLogs where order_id)`
- 循环 `i=n+1..draw_count`: `SelectItem``GrantReward``Create(ActivityDrawLogs{draw_index=i})`
- `completed == draw_count` → 订单标记 `SETTLED`
- 计划模式:
- 到 `scheduled_time` 统一处理;参与不足自动退款(先积分、后微信金额),保留现有逻辑
- 幂等:以上循环按“每订单已存在日志条数”补齐,事务化执行
### 统一结果轮询(基于 order_no
- 接口:`GET /api/app/lottery/result?order_no=...`
- 返回:
- `status`: `pending|paid_waiting|settled|refunded`
- `draw_mode``count``completed``results[{reward_id,reward_name,level,draw_index}]`
- `receipt``issue_id/seed_version/timestamp/nonce/signature/algorithm`
- `nextPollMs`建议2s-5s
## 改造点与文件定位
- 修改 `JoinLottery`internal/api/activity/lottery_app.go
- 增加 `count` 入参;仅下单与积分抵扣;免支付不开奖
- 保持 `WechatJSAPIPreorder` 不变internal/api/user/pay_wechat_app.go
- 扩展回调:在 `WechatNotify` 支付入账后触发即时模式订单的 `DrawProcessor`internal/api/pay/wechat_notify.go
- 定时调度按订单 `draw_count` 抽取或退款internal/service/activity/scheduler.go
- 新增统一查询接口 `GET /api/app/lottery/result`:新增 handlerinternal/api/activity/lottery_result_app.go挂载到 APP 认证组internal/router/router.go
## 数据库变更
- `orders` 新增:`draw_count INT NOT NULL DEFAULT 1`(可选:`draw_mode` 冗余)
- `activity_draw_logs` 可选新增:`draw_index INT NULL`(记录第几次抽取)
- 迁移默认值:历史订单填充 `draw_count = 1`
## 边界与异常
- 积分充足:免支付→订单直接 `PAID`;由处理器负责开奖
- 积分不够/无积分:需支付→入账后再开奖
- 计划不足:自动退款(积分与金额),订单 `REFUNDED`
- 并发与幂等:以 `order_id` + 已有日志条数控制,所有扣减与发奖在事务中执行
## 前端调用契约
- 流程:`join` → (可选)`preorder` → 支付 → 轮询 `result``settled|refunded`
- 显示:用 `results` 数组展示 N 次抽奖结果;根据 `status` 渲染进度与状态
## 测试与验收
- 单元:积分计算(充足/不够/为0、N 抽即时/计划模式日志与发奖一致
- 集成:支付回调幂等、计划不足退款、统一查询状态流转
- 验收:同一 `order_no` 在两模式下的查询端点返回一致结构与正确状态机;库存扣减准确;退款日志完整
## 注意事项
- `client_nonce`(可选):用于防重复参与提交的客户端幂等键;不影响支付与开奖
- 安全JWT校验、签名凭证不泄露种子所有金额与积分变更审计入库

View File

@ -1,40 +0,0 @@
## 问题现象
- 前端“运营→称号管理”页面列表为空。
- 可能原因:后端未创建称号数据、未登录导致接口被拦截、数据库未连接、或菜单未展示导致误判。
## 状态检查
- 后端运行:确认服务已启动并连接数据库(`go run ./main.go -env dev` 日志无报错)。
- 登录状态:确认在管理端已登录并持有有效 Token接口返回 `token`,在请求头 `Authorization: Bearer <token>`)。
- 路由与接口:
- 列表接口:`GET /api/admin/system_titles`(需要登录)。
- 初始化接口:`POST /api/admin/system_titles/seed_default`(非认证组,便于快速初始化)。
- 菜单补齐:`POST /api/menu/ensure_titles`(非认证组,确保“称号管理”菜单出现)。
## 初始化步骤
1. 管理员登录(你提供的账号密码)
- 账号:`admin`
- 密码:`chat2025`(需 MD5 后传输:`39f841df6f9a4768330b00d3ab9f1b4d`
- `curl -s -X POST http://localhost:9991/api/admin/login -H 'Content-Type: application/json' -d '{"username":"admin","password":"39f841df6f9a4768330b00d3ab9f1b4d"}'`
- 记录返回 `token`
2. 初始化 6 个称号及效果
- `curl -s -X POST http://localhost:9991/api/admin/system_titles/seed_default`
- 预期返回:`{ created: N, exists: M, ids: [...] }``created+exists=6`
3. 菜单补齐(后端动态菜单模式)
- `curl -s -X POST http://localhost:9991/api/menu/ensure_titles`
- 预期返回:`{ ensured: true, parent_id: <Operations>, menu_id: <Titles> }`
4. 验证列表
- `curl -s 'http://localhost:9991/api/admin/system_titles?page=1&page_size=20' -H 'Authorization: Bearer <token>'`
- 预期返回:`{ list: [...], total: 6, page: 1, page_size: 20 }`
## 前端验证
- 在管理端左侧菜单进入“运营管理 → 称号管理”,页面应显示 6 条数据。
- 若仍为空:
- 打开浏览器“网络”面板,检查 `admin/system_titles` 响应码与返回体,是否 401未登录或 200 但 `list=[]`
- 若 `list=[]`说明初始化未成功或数据库无数据重复调用步骤2
## 说明与已修复事项
- 之前的路由重复注册导致 Gin panic已修复为仅在“非认证组”保留 `POST /api/admin/system_titles/seed_default`;构建通过。
- 初始化的 6 个称号及效果已内置参数,后续可按你的运营实际替换 `template_id``target_prize_ids` 等占位参数。
## 后续扩展
- 如需完善“称号模板与效果”的 CRUD 管理页,我将新增后端 CRUD 与前端编辑表单,支持运营在页面上直接增删改查。

View File

@ -1,28 +0,0 @@
## 你的现象与原因归纳
- 你只看到 `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` 能产生差异数据(如金额不一致、缺失条目)。
我将按以上步骤先进行只读验证(接口与日志路径),随后按修复方案更新入库错误处理并回归测试,确保你在“退款管理”与“对账管理”都能看到数据。

View File

@ -1,44 +0,0 @@
## 审计结论摘要
- 金额统一“分”,积分统一“元等值整数(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. 测试与联调:场景覆盖与灰度验证,文案与枚举一致。

View File

@ -1,19 +0,0 @@
## 后端
- 通知事件:入库 payment_notify_eventsnotify_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
- 条件更新控制并发;异常与差异告警
## 验收
- 单元/集成:换算与比例恢复、幂等、预下单→通知→退款→详情一致、对账导入与差异生成
- 前后端构建通过,联调验证无误

View File

@ -1,56 +0,0 @@
## 目标与原则
- 禁用“积分直接购买/抵扣”用于抽奖订单;保留积分兑换优惠券/商品能力。
- 优惠券支持“部分使用”且订单累计抵扣≤总价的50%。
- 不新增任何新表:仅在`user_coupons`增加一列`balance_amount`,订单侧复用`orders.remark`记录使用明细。
## 数据变更(最小)
- `user_coupons`新增:`balance_amount`默认NULL/0
- 直减金额券发券时初始化为模板面值使用时扣减余额为0时`status=2/used_at`
- 满减/折扣券:不产生余额(保持一次性)。
- 不新增`order_coupons`表;订单使用明细写入`orders.remark`(结构化片段)。
## 使用明细编码remark复用
- 约定片段:`|c:<user_coupon_id>:<applied_amount>`,可重复出现多段代表多券。
- 示例票价50封顶25使用面值100券→`|c:12345:2500`单位分用户券余额从10000降至7500。
- 退款:解析`orders.remark`,逐段恢复对应`user_coupons.balance_amount += applied_amount`;余额>0则`status=1/clear used_at`
## 后端改动点
- 抽奖下单internal/api/activity/lottery_app.go:110176
- 移除积分抵扣分支;`order.PointsAmount`保持0新订单
- 计算`total``cap = total * rate(默认0.5)``remaining_cap = cap - order.DiscountAmount`
- 直减金额券:`applied = min(user_coupons.balance_amount, remaining_cap)`;更新`order.DiscountAmount/ActualAmount`,扣减余额并按规则更新`status/used_at`;在`orders.remark`追加`|c:<id>:<applied>`
- 满减/折扣券:计算规则折扣`rule_discount``applied = min(rule_discount, remaining_cap)`;一次性使用并在`remark`追加同样片段(余额不维护)。
- 券发放internal/service/user/coupon_add.go:1155
- 直减券初始化`balance_amount=discount_value`;其他类型`balance_amount=NULL/0`
- 退款internal/api/admin/pay_refund_admin.go:189228internal/api/pay/wechat_notify.go:28174
- 解析`orders.remark`恢复直减券余额与状态;同步回退`orders.discount_amount/ActualAmount`
## 配置与校验
- `coupons_max_discount_rate`默认0.5)。
- 兼容多券叠加:逐券按余额与剩余封顶扣减,超过封顶的券不使用;`remark`记录每次使用的`applied_amount`
## 前端改动
- 结算页:
- 展示“最多抵扣50%”;支持金额券余额显示与预估本次可抵扣金额。
- 当券面值大于封顶自动按封顶计算应付并提示“剩余XX元券余额保留”。
- 管理端订单详情:展示已应用的`c:<id>:<applied>`明细与用户券余额。
## 文档与错误码
- Swagger更新`/api/app/lottery/join`;保留/新增积分兑换券/商品接口文档。
- 错误码:`coupon_exceeds_cap``coupon_no_balance``insufficient_points``duplicate_redeem`
## 迁移与兼容
- 历史未使用直减券:初始化`balance_amount=discount_value`
- 已使用券:`balance_amount=0/NULL`
- 无新表;仅一列新增与`remark`编码扩展。
## 测试示例
- 面值100券、票价50、封顶25→应付25`remark``|c:<id>:2500`余额7500。
- 多券叠加至封顶;超过部分不使用;退款逐段恢复余额。
- 新订单不产生积分抵扣流水。
## 变更文件参考
- 抽奖下单internal/api/activity/lottery_app.go:110176。
- 发券internal/service/user/coupon_add.go:1155。
- 退款internal/api/admin/pay_refund_admin.go:189228internal/api/pay/wechat_notify.go:28174。
- 用户券模型internal/repository/mysql/model/user_coupons.gen.go新增`balance_amount`)。

View File

@ -1,31 +0,0 @@
## 目标
- 连接已生成的 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`
- 条件更新控制并发;异常与差异告警。
## 验收与测试
- 单元:分↔积分换算、比例恢复、幂等重复;
- 集成:预下单→通知→主动退款→详情一致;
- 对账:拉取账单并生成差异;前后端构建与联调通过。

View File

@ -1,52 +0,0 @@
## 目标
- 按真实微信支付流程完善一个完整的支付体系:持久化预下单/交易/退款/通知/对账,统一金额/积分口径,优化退款恢复与幂等,前后端一致展示。
## 数据库与模型
- 新增表(均使用“分”为金额单位):
- 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_preordersout_trade_no=order_no, prepay_id回填 orders.pay_preorder_id返回调起参数RSA-SHA256 四行签名)。
- 支付通知:
- 验签解密 payments.Transaction幂等notify_id 去重)入库 payment_notify_events写入 payment_transactionstransaction_id/raw/success_timeorders 条件推进 status:1→2写 paid_at。
- 退款:
- 管理端主动退款:生成 refund_no 并调用 refunddomestic.Create入库 payment_refundsrefund_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=... 控制并发;
- 日志与告警:验签失败、金额不一致、退款异常、对账差异。
## 测试与验收
- 单元:分↔积分换算、部分/全额退款恢复、幂等重复;
- 集成:真实预下单→回调→主动退款→详情一致;
- 对账:拉取账单并生成差异;
- 验收:管理端全链路可用、金额与积分口径一致、数据持久化完整。

View File

@ -1,27 +0,0 @@
## 目标
- 在运营管理中新增“头衔管理”菜单与页面 `/operations/titles`,支持称号列表与搜索、并提供为玩家分配称号的入口。
- 兼容当前两种菜单控制模式:前端模块路由与后端动态菜单;确保至少前端可见并可用。
## 变更项
### 前端路由与页面
- 路由:在 `web/admin/src/router/modules/operations.ts` 新增子路由 `titles``name: 'Titles'``component: '/operations/titles'``meta.title: '称号管理'`)。
- 页面:新增 `web/admin/src/views/operations/titles/index.vue`
- 列表:调用 `titlesApi.getList` 展示称号名称、状态、创建时间、描述;支持名称与状态筛选。
- 操作:
- 行内“分配称号”:弹出 `assign-title-dialog.vue`,为指定玩家分配称号。
- (占位)创建/编辑/删除:预留按钮,后端 CRUD 未完成则禁用或隐藏。
### 前端 API
- 已有 `web/admin/src/api/titles.ts` 支持 `getList``assignToUser`;页面直接复用。
### 菜单显示(后端动态菜单模式)
- 后端菜单接口:`/api/v3/system/menus/simple` 已存在。
- 如需在后端模式下展示菜单:可在后台菜单管理中新增一条菜单记录(父节点为运营),`component: '/operations/titles'``name: 'Titles'``icon: 'ri:medal-line'``status: true`;当前计划不强制修改后端菜单数据,仅完成前端路由与页面以确保可见与可用。
## 验收
- 运营管理左侧菜单出现“称号管理”,进入后能搜索与查看称号列表。
- 行内“分配称号”打开弹窗,提交后接口返回成功并刷新列表。
- 前端构建通过;后端接口已就绪(列表与分配)。
确认后我将立即实施以上前端路由与页面接入,并在必要时协助配置后端菜单以在“后端控制菜单模式”下显示。

View File

@ -1,42 +0,0 @@
## 检查项
- 路由与前端:确认管理端生成/摘要接口使用活动级路径
- 生成:`POST /api/admin/activities/:activity_id/commitment/generate`
- 摘要:`GET /api/admin/activities/:activity_id/commitment/summary`
- 前端 API 指向上述路径(不是旧的 `/ichiban/...`
- 数据迁移:核实 `activities` 已存在承诺字段algo/seed_master/seed_hash/state_version/items_root且成功执行
- 服务实现:检查 ActivityCommitmentService
- 生成:写入 `seed_master``seed_hash``state_version`;当前 items_root 为 NULL需要按最新设计计算
- 摘要:从 `activities` 读取版本与算法,`has_seed` 通过 `LENGTH(seed_master)` 判断
- 策略消费:一番赏映射读取活动承诺种子(非期级表),若缺承诺返回明确错误
## 修正与实现(不兼容旧期级)
- 计算并写入 `commitment_items_root`
- 规则:对活动下各期的奖励配置,按每个奖励的 `original_qty` 构造 slots 数组(长度为 N计算 `items_root = SHA256(JSON(slots))`
- 写入 `activities.commitment_items_root`,用于事后验证映射根
- 版本递增:每次生成承诺 `state_version+1`
- 一番赏映射页承诺校验
- 进入列表或详情时检查活动承诺存在(`has_seed=true`);无承诺返回“请在活动管理生成承诺”提示
- 活动管理显示
- 活动列表新增“承诺版本”列(已加),确保加载摘要后填充版本
- 活动编辑弹窗顶部承诺信息卡片(已加),显示 `seed_version/algo/状态`
## 验证流程
- 生成承诺后,确认 `activities` 中:
- `LENGTH(commitment_seed_master)=32``LENGTH(commitment_seed_hash)=32`
- `commitment_state_version` 递增
- `LENGTH(commitment_items_root)>0`
- 前端列表显示版本值;映射页顶部显示活动承诺版本
- 并发占位与映射不变:种子存在即可按位置开奖;缺承诺时前端/后端均提示
## 接口与 curl
- 生成:`curl -X POST 'http://127.0.0.1:9991/api/admin/activities/<id>/commitment/generate' -H 'Authorization: Bearer TOKEN'`
- 摘要:`curl 'http://127.0.0.1:9991/api/admin/activities/<id>/commitment/summary' -H 'Authorization: Bearer TOKEN'`
- DB 校验:`SELECT LENGTH(commitment_seed_master), LENGTH(commitment_seed_hash), LENGTH(commitment_items_root), commitment_state_version FROM activities WHERE id=<id>;`
## 交付修改点
- 后端:完善 ActivityCommitmentService 的 items_root 计算与写入(活动维度)
- 前端:确保活动列表与编辑对话框显示承诺;映射页在无承诺时给出指导提示
## 说明
- 全量切换到活动级承诺;不读取旧 `issue_random_commitments`
- 保持统一接口与策略,后续抽奖逻辑统一使用活动承诺

View File

@ -1,31 +0,0 @@
## 现状确认
- 后端接口已对接:`POST /api/admin/activities``internal/router/router.go:78`),控制器 `CreateActivity``internal/api/admin/activities_admin.go:41`)。
- 管理端前端已调用:`createActivity()` 指向 `admin/activities``web/admin/src/api/adminActivities.ts:13`)。
- 向导有两种:
- 独立向导页 `wizard/index.vue``下一步`仅切换步骤(`next()`),提交动作分别由页面内的`提交`按钮触发(`submitActivity/submitIssue/submitRewards`)。
- 管理页弹窗向导 `manage/index.vue``下一步`会调用创建接口然后推进步骤(`nextWizard()`)。
## 可能原因
- 使用了独立向导页,点击`下一步`未触发API预期行为需用`提交`按钮)。
- 请求未通过权限:后端要求超级管理员(`ctx.SessionUserInfo().IsSuper == 1``internal/api/admin/activities_admin.go:56-59`)。
- 请求参数不合法:`name`必填、`activity_category_id`不可为0同文件 15-18, 51-54
- 环境未配置或认证头缺失:`VITE_API_URL` 未指向后端 `/api` 前缀,或 `Authorization` 未带登录后的纯 Token不加 `Bearer`)。
## 排查步骤
1) 打开浏览器 Network在点击`下一步``提交`后确认是否发起 `POST {VITE_API_URL}/api/admin/activities`;查看响应码与返回体。
2) 确认当前页面是弹窗向导还是独立向导:
- 若是独立向导,请使用每步的`提交`按钮;`下一步`只换页不提交。
3) 验证权限与参数:
- 登录账号需 `is_super=1`;否则返回 400 `禁止操作`
- `name``activity_category_id`需填充;分类可通过 `GET /api/admin/activity_categories` 获取。
4) 检查前端环境:
- `VITE_API_URL` 应指向后端根(例如 `http://localhost:8000`),请求工具会拼接 `admin/...``/api/admin/...`
- `Authorization` 头为登录获得的 Token 字符串(无 `Bearer` 前缀)。
## 改造建议(可选)
- 为独立向导页 `wizard/index.vue` 增强交互:在 `next()` 中根据 `active` 自动调用对应的 `submitActivity/submitIssue`,使“下一步”即提交。
- 在按钮文案上提示“提交并进入下一步”,并在未填必填项时给出校验提示(阻止推进)。
## 验收标准
- 在管理端,创建活动流程点击`提交`或(改造后)点击`下一步`能成功发起请求并进入下一步;错误时有明确提示。
- 非超级管理员或参数缺失时,前端能正确显示后端返回的错误信息。

View File

@ -1,50 +0,0 @@
## 目标范围
- 在“创建活动”时新增2个可配置属性`是否可以使用道具卡``是否可以使用优惠券`
- 属性保存到`activities`表,默认启用(向后兼容现有活动)。
- 管理端创建/编辑表单与接口支持读写这两个属性;详情接口返回这两个属性。
- 暂不实现具体“应用道具卡/优惠券”的核销/抵扣逻辑,仅提供全局开关位,后续业务在下单/抽奖流程使用该开关位做限制。
## 数据库与模型
- 新增`activities`表列:
- `allow_item_cards` TINYINT(1) NOT NULL DEFAULT 1 注释“是否允许使用道具卡1是0否”。
- `allow_coupons` TINYINT(1) NOT NULL DEFAULT 1 注释“是否允许使用优惠券1是0否”。
- 对齐模型:在`Activities`结构体中新增同名字段并JSON映射internal/repository/mysql/model/activities.gen.go:15
- 启动时DDL修复仿照`main.go`已有字段修复逻辑添加这两列main.go:38-58
## 后端接口改动
- 管理端创建/修改请求增加字段:
- `createActivityRequest`/`modifyActivityRequest`新增`allow_item_cards``allow_coupons`int或bool保持与`is_boss`的风格一致为`int32`的0/1。位置参考internal/api/admin/activities_admin.go:14-34, 156-176。
- 传递至服务层:
- `CreateActivityInput`/`ModifyActivityInput`新增同名字段internal/service/activity/activity.go:110-154
- `CreateActivity`设置字段,`ModifyActivity``updates`包含这两列internal/service/activity/activity_create.go:13-55, internal/service/activity/activity_modify.go:11-48
- 详情返回:
- 管理端与APP端`activityDetailResponse`新增两个字段并赋值internal/api/admin/activities_admin.go:41-62 与 internal/api/activity/activities_app.go:41-62
- DrawConfig无需改动仅开奖相关internal/service/activity/draw_config_save.go:9-16
## 前端管理端改动
- 创建向导页`wizard/index.vue`
- 在“活动基本信息”区域新增两个`ElSwitch`,绑定`activityForm.allow_item_cards``activityForm.allow_coupons`,默认`true`
- `submitActivity()``params`包含这两个字段web/admin/src/views/activity/wizard/index.vue:777-804
- 活动管理页`manage/index.vue`
- 编辑对话框`form`新增两个字段与对应`ElSwitch`,支持修改与展示。
- 接口类型与调用:
- `web/admin/src/api/adminActivities.ts``createActivity`/`updateActivity`入参类型新增`allow_item_cards?``allow_coupons?`并透传adminActivities.ts:3-22, 26-49
## APP端可选展示
- `GetActivityDetail`响应新增两个布尔位,前端可据此展示“本活动不支持优惠券/道具卡”文案internal/api/activity/activities_app.go:140-186
## 兼容与默认
- 新增列默认值为1历史活动即视为允许管理端可随时改为0禁止。
- 未提供值的创建请求按默认启用处理。
## 校验与测试
- 后端单测/集成测试:
- 创建活动带`allow_*` → 详情返回一致;修改后值变更正确。
- 前端回归:
- 创建向导能设置并成功保存;编辑页能读取并更新。
## 后续接入点(不在本次范围,供后续实现)
- 下单/支付阶段应用优惠券时:在订单创建前根据活动`allow_coupons`拒绝并提示。
- 抽奖策略`PostEffects()`应用道具卡时:根据活动`allow_item_cards`拒绝卡效果并记录原因。
请确认以上方案,确认后我将按此实现并同步更新相关文档(说明文档、接口文档与前端表单)。

View File

@ -1,87 +0,0 @@
## 目标
- 将活动创建流程改造成“清晰、可操作、可回退”的向导式体验,显著降低填写负担、减少出错率,并确保与现有后端接口完全对齐。
- 统一字段与交互:活动、期、奖励配置的字段、校验与排序一致;消除重复输入与歧义(如奖励名称以选品自动回填)。
- 杜绝常见错误:时间格式、权限校验、表单必填项、动态模块加载等问题在交互层面被提前防护。
## 现状与痛点
- 后端接口已齐备(管理端):
- 创建活动:`POST /api/admin/activities``internal/router/router.go:78`;控制器 `internal/api/admin/activities_admin.go:41`;服务层 `internal/service/activity/activity_create.go:12-41`
- 创建期:`POST /api/admin/activities/:activity_id/issues``internal/router/router.go:83`;控制器 `internal/api/admin/issues_admin.go`;服务层 `internal/service/activity/issue_create.go:13-32`
- 创建奖励:`POST /api/admin/activities/:activity_id/issues/:issue_id/rewards`
- 前端存在两套创建入口:
- 独立向导页:`web/admin/src/views/activity/wizard/index.vue`(“下一步”仅切换步骤,提交由各步的“提交”触发)
- 管理页弹窗向导:`web/admin/src/views/activity/manage/index.vue`(“下一步”实际调用创建并推进)
- 痛点汇总:
- 两处入口的字段命名、排序不一致;奖励有的手填名称,有的选商品
- 时间未填时易触发数据库零日期错误(`TRADITIONAL SQL` 模式下)
- “下一步”与“提交”含义不统一,用户易误操作
- 表格行式编辑密度过高,填写多项参数不友好
- 动态导入失败来源于 SFC 结构不合法、重复脚本块或未声明变量导致编译失败
## 重构设计(交互与结构)
### 向导统一(单入口)
- 入口:统一从“管理页弹窗向导”打开;支持跳转到独立页,但独立页沿用同一套逻辑(避免两套逻辑分歧)
- 步骤:
1. 基本信息活动名称、分类、状态、门票价格、Boss、开始/结束时间
2. 期信息(活动期):期号、状态、排序
3. 奖励配置按商品选择与参数设置权重、数量、原始数量、等级、排序、Boss
4. 确认提交:汇总校验与最终提交
- 导航:
- “下一步”即执行当前步的提交(成功后推进),失败则停留并有明确错误提示
- “上一步”回退不丢数据;每步保存草稿状态在组件内存
### 表单布局与输入体验
- 两列栅格布局(`ElRow/ElCol`):提升可读性与并行填写效率
- 奖励配置交互:
- 改为“卡片列表 + 弹窗表单”,卡片上显示商品名称及关键参数;“新增奖励/编辑奖励”在弹窗中填写,分两列布局
- 商品选择:远程搜索 `admin/products``web/admin/src/api/product.ts`),选择后自动回填奖励名称(传递给后端以兼容)
- 支持复制卡片、删除卡片、批量导入CSV/JSON作为扩展项初版可不做批量
- 字段统一:奖励行统一字段为 `product_id、weight、quantity、original_qty、level、sort、is_boss``name`由选品自动补齐,不再手填
- 权限与提示:检测 `SessionUserInfo.IsSuper`(控制器内已做),前端在 403/400 的返回文案时专门弹出“权限不足”提示
### 校验与防错
- 实时校验规则:
- 活动:名称与分类必填;开始/结束时间支持空,但若为空则前端默认以 `ISO8601` 当前时间传入或后端统一允许 `NULL`
- 期:期号必填;排序与状态为数值校验
- 奖励:`product_id` 必填;数值项均为非负或正数;`level` 合法枚举
- 时间防错:
- 方案优先级A数据库将 `start_time/end_time` 改为可 `NULL` 且默认 `NULL`(推荐);
- 方案优先级B前端在未选择时默认传当前时间的 `toISOString()`
- 后端保留 `Omit` 逻辑(`internal/service/activity/activity_create.go:29-35`),避免强行写零日期
### 代码结构与可维护性
- 组件结构:每个步骤独立子组件(`ActivityStepBasic.vue``ActivityStepIssue.vue``ActivityStepRewards.vue`+ 容器(`ActivityWizard.vue`
- 状态管理:使用本地状态(`ref/reactive`)即可;如需跨页保持,用 `pinia` 记录向导草稿
- API层沿用 `web/admin/src/api/adminActivities.ts`,奖励提交前统一映射自动补齐 `name`
- 动态导入稳定性:
- 确保每个 SFC 仅一个 `<template>` 和一个 `<script setup>`,无重复导入、重复变量与重复函数
- 编译错误先清理其他页面的类型错误(如 `shipping-stats/index.vue``remoteLoading.orders``reconcile/diff/index.vue``formatDetail`),避免影响整体构建可用性
## 后端约束与对齐
- 保持接口不变:`CreateActivity`/`CreateIssue`/`CreateIssueRewards`
- 修改建议(可选):
- 数据库列允许 `NULL`(避免零日期);
- 在 `CreateActivity` 中对 `StartTime/EndTime` 为空时不写入(现有已 Omit
## 验收标准
- 用户可通过统一向导完成活动创建:每步“下一步”均会发起对应请求并进入下一步;错误有明确提示
- 奖励配置以选择商品为主,不再手填名称;行字段与顺序一致
- 复杂表单两列布局,填写效率明显提升;可编辑/删除奖励卡片
- 构建通过(无 SFC 结构错误与类型错误);页面加载不再出现“动态模块导入失败”
## 实施步骤
1. 重构向导组件结构与路由入口;统一使用弹窗向导(保留独立页入口但复用组件)
2. 基本信息与期信息改为两列布局;“下一步”触发提交与推进
3. 奖励配置改为卡片 + 弹窗表单;远程选品与字段统一;提交前自动补齐名称
4. 表单校验与错误提示完善;未填时间的兜底(前端默认或后端允许 NULL
5. 清理页面内无关类型错误(`shipping-stats/index.vue``reconcile/diff/index.vue`)保证构建与动态导入稳定
6. 编写端到端测试用例:
- 正常流程:活动→期→奖励→提交 成功
- 边界:缺少必填项、非超级管理员、时间为空等
- 性能:奖励 50+ 条下的编辑与提交
## 交付物
- 统一的向导组件及子组件
- API 映射与表单校验实现
- 样式与布局更新,卡片/弹窗交互
- 测试用例与构建通过证明

View File

@ -1,229 +0,0 @@
## 项目架构对齐
* 后端框架:`gin``internal/pkg/core`),统一中间件与响应封装
* 路由分组:`internal/router/router.go` 下 APP 公开/鉴权分组可扩展 `products` 资源
* 鉴权APP 端采用 `core.WrapAuthHandler(intc.AppTokenAuthVerify)``Authorization: Bearer <token>`
* ORM/DB`gorm` + `gorm/gen``internal/repository/mysql/dao/Query`),读写分离 `GetDbR/GetDbW`
* Swagger通过注释生成`swaggo/swag`),挂载 `GET /swagger/*any`
* 商品相关模型:`product`SPU`sku``category``spec``spec_option``product_spec``sku_spec_value`、推荐位 `recommendation_slot/item`
## 接口设计
### 1) 商品列表
* 路径:`GET /api/app/products`
* 鉴权需要App Token
* 请求参数:
* `page`默认1`pageSize`默认20最大50
* `categoryId`(可选)
* `priceMin``priceMax`(可选,闭区间)
* `salesMin`(可选)
* `inStock`布尔可选默认true仅展示可售
* `sortBy``sales|price|createdAt``order``asc|desc`,默认 `desc`
* 业务规则:只返回 `status=上架` 的 SPU`inStock=true` 时需至少一个 SKU `stock-stock_locked>0`
* 响应结构:
```json
{
"total": 1234,
"currentPage": 1,
"pageSize": 20,
"list": [
{
"id": 1001,
"name": "羽绒服",
"mainImage": "https://.../xx.jpg",
"price": 299.00,
"sales": 5230,
"inStock": true,
"categoryId": 12
}
]
}
```
* 过滤/排序实现:
* 基于 `product``status/category_id/sales_count/created_at/price_min/price_max` 组合条件
* `inStock` 通过子查询/EXISTS 关联 `sku` 计算有效库存
### 2) 商品详情
* 路径:`GET /api/app/products/:id`
* 鉴权需要App Token
* 业务规则SPU `status=上架` 且可售;下架/缺货返回业务态错误码
* 响应结构:
```json
{
"id": 1001,
"name": "羽绒服",
"album": ["https://.../1.jpg", "https://.../2.jpg"],
"priceRange": {"min": 199.00, "max": 399.00},
"sales": 5230,
"stock": {"total": 235, "available": 220},
"specs": [
{"name": "颜色", "options": ["黑色", "白色"]},
{"name": "尺码", "options": ["M", "L", "XL"]}
],
"description": "高蓬松保暖...",
"service": ["7天无理由", "运费险"],
"recommendations": [
{"id": 1002, "name": "羽绒马甲", "mainImage": "...", "price": 199.00}
]
}
```
* 规格/相册/描述来源:
* 相册:`product.images_json` 或关联表(若存在),解析为数组
* 规格:`product_spec` + `spec` + `spec_option` 聚合;
* 描述/服务保障:从 `product` 富文本字段(或扩展表)读取
* 推荐逻辑:
* 优先使用 `recommendation_slot.scene='detail_related'` 的绑定数据
* 兜底:同类目类目内 `sales_count` Top N去重当前商品
## 路由与代码结构
* 路由注册:`internal/router/router.go`
* `appAuthGroup := router.Group("/api/app", core.WrapAuthHandler(intc.AppTokenAuthVerify))`
* `appAuthGroup.GET("/products", product.New(repo, logger).List)`
* `appAuthGroup.GET("/products/:id", product.New(repo, logger).Detail)`
* Handler`internal/api/product/list_app.go``internal/api/product/detail_app.go`
* 统一入参校验、容错与错误码
* Service`internal/service/product/service.go`
* 封装查询、库存计算、推荐聚合、缓存命中
* DAO查询使用 `dao.Query``GetDbR`
* DTO`internal/api/product/dto.go`(列表项与详情结构体)
## 鉴权与权限
* 使用现有 APP Token 验证中间件Swagger `@Security LoginVerifyToken`
* 接口动作无需RBAC面向APP用户但保留限流与风控扩展点
## 性能与优化500ms SLA
* 索引:
* `product(status, category_id, sales_count, created_at)` 复合索引
* `sku(product_id, status)``sku(stock, stock_locked)` 参与计算库存
* 查询:
* 列表:单次查询 + `EXISTS` 子查询校验库存;分页使用 `LIMIT/OFFSET`页码≤100
* 详情:读取 SPU + 聚合 `SUM(stock-stock_locked)` + 规格与相册多表查询
* 缓存:
* 本地 TTL 缓存:
* 列表:键 `list:{filters}:{page}:{pageSize}`TTL 30s命中后直接返回
* 详情:键 `detail:{id}`TTL 60s库存变更事件可主动失效后续扩展
* 并发合并:使用 `singleflight` 避免热点详情击穿
* 传输仅必要字段GZIP 已由中间件统一处理
* 限流:`/products` 每用户每秒 ≤10后续中间件实现可选
## 错误码与校验
* 下架:`PRODUCT_OFFSHELF`HTTP 200业务码提示“商品已下架”
* 缺货:`PRODUCT_OUT_OF_STOCK`
* 参数错误:`INVALID_PARAM`(页码、价格区间校验)
* 统一响应封装沿用 `internal/pkg/core/context.go`
## Swagger文档
* 在两个 Handler 顶部添加注释:
* `@Summary``@Description``@Tags products`
* `@Param`(列出查询参数与类型)
* `@Success 200 {object} ListResp` / `DetailResp`
* `@Router /api/app/products [get]``/api/app/products/{id} [get]`
* `@Security LoginVerifyToken`
* 生成后可在 `http://127.0.0.1:9991/swagger/index.html` 查看
## 单元测试
* Handler`httptest.NewServer` + 构造 `Authorization` 头,校验分页与过滤、错误码
* Service使用内存/模拟 DAO或事务性测试库覆盖库存计算、规格聚合、推荐兜底
* 覆盖率目标:核心逻辑 ≥70%
## 压力测试
* 工具:`hey``wrk`
* 示例:
* 列表:`hey -n 5000 -c 100 'http://localhost:9991/api/app/products?page=1&pageSize=20'`
* 详情:`wrk -t4 -c200 -d30s 'http://localhost:9991/api/app/products/1001'`
* 目标P95 ≤ 500ms错误率 < 0.1%
## 联调与交付
* 与前端约定字段与筛选参数;提供响应示例与错误码表
* 提供 Swagger 文档与可复用的 Postman/HTTP 文件
* 完成线上/测试环境联调回归
## 验收标准
* 两个接口完成并通过单元与压力测试
* Swagger 文档完整,含请求/响应示例
* P95 响应时间 ≤ 500ms测试环境数据量下
* 鉴权与状态校验生效;缓存命中率提升热点访问性能

View File

@ -1,51 +0,0 @@
## 目标与范围
- 优化期数管理:在期数列表中新增“奖品数量”,支持实时刷新与排序。
- 登录状态保持:核查并完善前端请求头携带令牌、后端校验逻辑,加入令牌自动刷新机制。
- 奖励管理增强:名称后显示价格“名称(价格)”,下拉显示名称+价格,统一两位小数,支持按价格筛选与排序。
## 现状定位
- 期数列表:`web/admin/src/views/activity/issues/index.vue` 使用 `listActivityIssues`;后端 `internal/api/admin/issues_admin.go` 未返回奖品数量。
- 登录令牌:前端 `web/admin/src/utils/http/index.ts` 注入 `Authorization`401登出无刷新后端鉴权在 `internal/router/interceptor/admin_auth.go`JWT工具具备 `Refresh` 但未暴露接口。
- 奖励管理:`web/admin/src/views/activity/rewards/index.vue` 已有价格缓存与总成本;向导 `wizard/index.vue` 已显示单价但管理页未统一展示,筛选仅支持名称/等级/Boss。
## 实施方案
### 1) 期数管理“奖品数量”
- 后端接口返回扩展:
- 在 `internal/api/admin/issues_admin.go` 的列表响应结构添加 `prize_count` 字段;统计当前活动期对应的奖励配置数量(表 `activity_reward_settings`)并填入。
- 为避免 N+1 查询,优先采用按 `IssueID` 分组的聚合统计一次性返回。
- 前端表格列:
- 在 `issues/index.vue``columnsFactory` 增加 `{ prop: 'prize_count', label: '奖品数量', minWidth: 120, align: 'center', sortable: true }`
- 保持字体大小与现有列一致,列宽 120保证清晰可见。
- 实时刷新:
- 在新增/删除奖励后(`createIssueRewards``deleteIssueReward`)触发行级期数列表刷新;或在奖励模块完成操作后发出事件总线通知,期数页监听并刷新。
### 2) 登录状态保持与自动刷新
- 前端核查与完善:
- 保证所有请求统一使用封装实例,已自动注入 `Authorization`;抽样验证重要路由跳转后的接口头部是否带令牌(在拦截器内添加开发模式日志)。
- 在 `axios` 响应拦截器中实现一次性刷新流程:当收到 401 且存在 `refreshToken`,调用后端刷新接口获取新 `accessToken`,更新到 `useUserStore` 并重放队列中的失败请求。
- 后端刷新接口:
- 新增 `POST /api/admin/auth/refresh`,从 `refreshToken` 校验后发新 `accessToken` 与新的过期时间;使用 `internal/pkg/jwtoken.Refresh`
- 刷新令牌有效期策略:`accessToken` 24h`refreshToken` 714d可根据配置调整。
- 有效期验证:
- 检查管理端 JWT `exp` 与中间件的过期处理;在返回体附带 `expires_in`(秒)便于前端设置刷新窗口(如 T-5min 刷新)。
### 3) 奖励管理价格展示与筛选排序
- 名称后显示“名称(价格)”:
- 在 `rewards/index.vue` 的名称列渲染处,读取 `priceCache[row.product_id]`,以元显示并保留两位小数:`{{ row.name }} (¥{{ unitPrice(row.product_id).toFixed(2) }})`
- 下拉选项展示价格:
- 在新增/编辑奖励的商品选择下拉项 `<ElOption>` 增加右侧显示 `¥price.toFixed(2)`;统一从 `fetchProducts``price` 转换为元并缓存。
- 价格筛选与排序:
- 在筛选区新增最小价/最大价输入与排序选择(`price asc/desc`),在 `displayedData` 计算中对 `priceCache` 区间过滤并根据选择排序。
- 统一格式:
- 所有价格展示统一使用元、`toFixed(2)`;缓存与计算逻辑复用,避免重复请求与闪烁。
## 验收标准
- 期数列表出现“奖品数量”列且支持点击列头进行升降序;奖励增减后期数页能自动反映变更。
- 前端所有 API 请求头包含 `Authorization`登录态过期前能自动刷新并续期401 时先刷新再重放请求,刷新失败才登出。
- 奖励管理名称与下拉均显示价格,价格保留两位小数;可按价格筛选与排序,结果正确稳定。
## 交付变更清单
- 后端:扩展期数列表响应(含聚合统计)、新增管理员令牌刷新接口与逻辑。
- 前端期数列表新增“奖品数量”列与刷新机制HTTP 拦截器加入自动刷新与队列重放;奖励管理价格展示、筛选与排序统一完善。
确认后我将按以上步骤实施,并为关键函数按要求补充函数级注释。

View File

@ -1,49 +0,0 @@
## 核心原则
- 承诺是活动级属性:所有活动均需生成并持有承诺;抽奖只能在承诺存在的活动上执行
- 不做旧期兼容:不读取/维护期级承诺表;全量切换到活动级承诺
## 数据模型(仅 activities
- `activities` 新增字段:
- `commitment_algo` VARCHAR(32) 默认 `commit-v1`
- `commitment_seed_master` BLOB活动随机种子256位
- `commitment_seed_hash` BLOB`SHA256(seed_master)`
- `commitment_state_version` INT递增
- `commitment_items_root` BLOB按玩法需要可填比如 Ichiban 的 slots 根)
- 期级承诺表不再使用(保留但不访问),所有策略仅访问活动级承诺
## 接口
- 生成承诺(活动级):`POST /api/admin/activities/:activity_id/commitment/generate`
- 行为:生成随机种子;计算 `seed_hash`;按玩法(如 Ichiban计算 `items_root`;版本 +1
- 承诺概览:`GET /api/admin/activities/:activity_id/commitment/summary`
- 返回:`{ seed_version, algo, has_seed, items_root(optional) }`
- 抽奖前置校验:策略入口统一校验活动承诺存在;缺失则返回 `COMMITMENT_REQUIRED`
## 策略消费
- Ichiban
- 读取活动级 `seed_master` 作为随机源
- 根据期的奖励配置构造 slots基于 `original_qty`)→ 使用活动种子做确定性洗牌
- 保持“位置→奖品”稳定,版本变更后整体映射更新
- 其他玩法:
- 统一读取活动承诺作为随机源或哈希链起点
## 前端改造
- 活动管理:
- 操作列显示“生成承诺”与“承诺概览”(适用于所有活动,不再区分玩法)
- 详情/期次页可显示版本号与算法
- 一番赏序号映射:只读查看映射;顶部显示当前活动 `seed_version`
## 抽奖流程
- 即时/定时调用策略前:校验活动承诺
- 抽奖回执携带:`seed_version``algo`
## 测试与验收
- 生成承诺版本递增与哈希一致性
- 策略在承诺存在时正常运行、缺失时明确错误
- 一番赏映射稳定性(同版本稳定)
## 上线步骤
- 执行 `activities` 字段迁移
- 接入生成与概览接口
- 改造策略读取活动承诺
- 更新前端活动管理显示与按钮
- 移除前端/后端期级承诺路径(保留表但不访问)

View File

@ -1,45 +0,0 @@
# 渠道数据分析功能开发计划
本计划旨在为渠道管理增加数据分析功能,包括用户增长和付费数据的可视化展示。
## 1. 需求分析与对齐 (Align)
* **目标**: 在渠道列表页增加“分析”入口,点击后展示该渠道的运营数据。
* **核心指标**:
* **用户增长**: 累计注册用户数、每日新增用户趋势。
* **付费数据**: 累计订单数、累计 GMV (销售额)、每日订单/GMV 趋势。
* **交互**: 列表页操作栏新增“分析”按钮 -> 弹出模态框/抽屉展示仪表盘。
## 2. 架构设计 (Architect)
### 2.1 后端设计
* **API 接口**: 新增 `GET /admin/v1/channels/{id}/stats`
* **数据源**:
* 用户数据: `users` 表 (字段: `created_at`, `channel_id`)
* 订单数据: `orders` 表 (需关联 `users` 表,通过 `user_id` 关联,筛选 `channel_id`)
* **Service 层**:
* 扩展 `ChannelService`,增加 `GetStats(ctx, channelID, timeRange)` 方法。
* 使用 GORM 进行聚合查询 (Group by date)。
### 2.2 前端设计
* **组件复用**: 使用现有的 `ArtStatsCard` (指标卡片) 和 `ArtLineChart` (折线图)。
* **页面改造**: `web/admin/src/views/operations/channels/index.vue`
* 新增“分析”按钮。
* 新增弹窗组件,包含数据加载逻辑。
## 3. 任务拆解 (Atomize)
### 3.1 后端开发
1. **Service 实现**: 在 `internal/service/channel/channel.go` 中实现数据统计逻辑 (用户数、订单数、GMV)。
2. **Controller 实现**: 在 `internal/api/admin/channels.go` 中添加处理函数。
3. **路由注册**: 注册新的 API 路由。
### 3.2 前端开发
1. **API 定义**: 在 `web/admin/src/api/channels.ts` 中添加 `getChannelStats` 方法。
2. **UI 实现**: 在 `channels/index.vue` 中集成分析弹窗,展示指标卡片和趋势图表。
## 4. 执行步骤 (Automate)
1. **Step 1**: 实现后端统计接口 (Service + API)。
2. **Step 2**: 定义前端 API 接口。
3. **Step 3**: 实现前端分析弹窗 UI 并对接数据。
4. **Step 4**: 验证数据准确性和图表展示效果。

View File

@ -1,22 +0,0 @@
## 用途说明
- 退款管理:全局退款列表与详情,用于按条件检索所有订单的退款记录。前端 `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` 组合查询)。

View File

@ -1,24 +0,0 @@
## 问题定位
- 操作列使用 `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` + `<template #operation>` 模板渲染,统一风格(本次先用 `ElSpace` 修复)。
## 具体修改点
- 文件:`web/admin/src/views/player-manage/index.vue`
- 修改 `columnsFactory()``operation` 列的 `formatter` 容器为 `ElSpace``wrap: true``size: 6`)。
- 将 `operation``width` 调整为 `320`
## 验收标准
- 操作列按钮横向排列,换行时保持等距,不再竖排堆叠。
- 表头、分页与表格不发生位移,整页无水平滚动条。
- 批量操作按钮(表头左侧)不受影响,交互正常。
## 影响范围
- 仅影响玩家管理页视图渲染;不改动接口、不影响其它模块。
确认后我将执行上述修改并重新构建前端,提供预览截图/链接以供验收。

View File

@ -1,41 +0,0 @@
# 奖品双倍头衔设计补充
## 目标
- 为“头衔”新增一种权益:抽奖命中后有一定概率将奖励倍数提升(默认双倍)。
## 模型与配置
- `system_title_effects.effect_type = DRAW_REWARD_DOUBLE_CHANCE`
- `params_json` 示例:
- `multiplier_x1000`: 2000默认2x允许配置3x等
- `chance_x1000`: 150表示15%
- `max_multiplier_cap_x1000`: 3000总倍数上限例如≤3x
- `limit_per_day`: null 或数值(每日最多触发次数)
- `applicable_activities/issues`: 作用范围限定列表
- 叠加策略:
- 概率合并:`p_total = 1 - Π(1 - p_i)`并设置上限如≤50%)。
- 倍数合并:与其他“奖励倍数”效果(如道具卡)相乘,最终倍数受 `max_multiplier_cap_x1000` 封顶。
## 抽奖流程接入
- 触发点:`DRAW_EXECUTE` 阶段,在完成“中奖项选择”之后执行一次“是否倍增”的伯努利试验。
- 步骤:
1. EffectEngine 汇总用户激活头衔的 `DRAW_REWARD_DOUBLE_CHANCE`,计算 `p_total` 与期望倍数通常为2x
2. 使用抽奖同一随机源或受控RNG进行试验命中则将当前中奖奖励按倍数提升。
3. 将结果写入 `activity_draw_effects.reward_multiplier_x1000`,并记录 `source_type=TITLE``source_id=title_id`、命中与否、概率参数。
4. 回执与发奖按倍数后的值结算(库存/道具类型按现有规则处理)。
## 审计与风控
- 落盘:在抽奖效果表记录每次试验的参数与结果,便于复核与分析。
- 上限:概率与倍数均受可配置总封顶约束,防止过度放大。
- 并发:同一抽奖回合仅进行一次合并后的试验,避免重复计算。
## 管理端与前端
- 管理端在“头衔效果”配置表单中提供:倍数、概率、封顶、作用范围、每日上限等字段。
- 用户端抽奖页面展示“我的双倍概率与倍数”提示;在中奖结果处标注“头衔加成(双倍)”。
## 测试与验收
- 用例:
- 单头衔15%双倍 → 约15%回合出现倍增且倍数=2x。
- 多头衔概率合并10%与20%)→ 总概率约28%倍数与其他倍数效果如1.5x道具卡)相乘并≤总封顶。
- 封顶与每日上限有效;审计记录完整。
确认后,我将把该效果纳入头衔体系的实现与抽奖流程接入。

View File

@ -1,91 +0,0 @@
## 范围
* 覆盖活动/期/奖励、随机承诺与抽奖、称号/效果、用户资产(优惠券/道具卡/积分)、用户与公会、商品与轮播、菜单权限。
* 验证管理端鉴权与超管权限;核查风险接口与配置密钥安全。
## 现状与疑点
* 鉴权管理端认证路由启用internal/router/router.go:58-66但存在非鉴权初始化接口seed\_default、ensure\_titles—需确认是否保留。
* 活动列表(管理端):未提供 `GET /api/admin/activities` 列表;前端使用 APP 端 `GET /api/app/activities` 作为数据源web/admin/src/api/activity.ts:31
* 优惠券发放:`POST /api/admin/users/{user_id}/coupons/add`internal/router/router.go:121internal/api/admin/users\_admin.go:596-616未见配额/用户上限校验(服务层 AddCoupon
* 道具卡分配:`POST /api/admin/users/{user_id}/item_cards`internal/api/admin/item\_cards\_admin.go:355-376仅超管限制但无用户上限与批量安全边界服务层 AddItemCard
* 称号效果:后端对 `params_json` 仅做非空internal/api/admin/titles\_admin.go:173-213,221-277运行期解析失败会静默忽略internal/service/activity/draw\_with\_effects.go:104-149
* 抽奖随机HMAC与拒绝采样实现正确internal/service/activity/random\_commit.go:70-81draw\_execute.go:43-51,131-145主密钥明文在配置configs/fat\_configs.toml:32
## 缺口清单(可能不足)
* 管理端活动列表与搜索:缺 `GET /api/admin/activities`;当前运维依赖 APP 列表接口,可能不满足运营筛选需求。
* 优惠券/道具卡风控:缺配额扣减、用户持有上限、幂等与审计日志;易被滥用。
* 称号效果参数校验:缺严格模式与数值边界;配置易失真或异常。
* 安全非鉴权初始化接口seed/ensure在生产不宜暴露密钥应迁移到环境变量。
## 闭环测试计划(管理端)
* 登录鉴权
* `POST /api/admin/login` 使用后台账号 `admin / chat2025` 拿到 `Authorization`
* 活动与期
* 创建活动:`POST /api/admin/activities`;创建期:`POST /api/admin/activities/{id}/issues`
* 配置奖励:`POST /api/admin/activities/{id}/issues/{issue}/rewards`
* 随机承诺:`POST /.../commit_random``GET /.../commit_random``GET /.../commit_random/history`
* 抽奖执行与验证
* 执行抽奖APP鉴权`POST /api/app/activities/{id}/issues/{issue}/draw`需APP用户token
* 管理端批量抽取(运营回归):`POST /api/admin/activities/{id}/issues/{issue}/batch_draw`
* 抽奖收据验证:`POST /api/admin/activities/{id}/issues/{issue}/verify_draw`;查看收据:`GET /api/admin/draw_receipts/{draw_id}`
* 称号与效果type=5/6
* 创建称号:`POST /api/admin/system_titles`;添加效果:`POST /api/admin/system_titles/{title_id}/effects`(参数按文档模式)。
* 分配用户称号:`POST /api/admin/users/{user_id}/titles`,设置有效期(确保“只取最新激活”策略)。
* 在指定期抽奖,核查“概率加成”权重调整与“双倍概率”翻倍命中(查看批量抽取统计)。
* 用户资产
* 发放优惠券:`POST /api/admin/users/{user_id}/coupons/add`;查询用户券:`GET /api/admin/users/{user_id}/coupons`
* 分配道具卡:`POST /api/admin/users/{user_id}/item_cards`;查询卡列表:`GET /api/admin/users/{user_id}/item_cards`
* 添加积分:`POST /api/admin/users/{user_id}/points/add`;查询积分与余额:`GET /api/admin/users/{user_id}/points``/points/balance`
* 安全回归
* 非鉴权路由只保留登录;其他改动接口需 `Authorization`
* 校验后台超管限制(优惠券发放/道具卡分配),普通管理员应被拒绝。
## 缺陷验证点
* 称号效果参数错误时应返回 400当前静默忽略的路径需提示
* 优惠券发放应拒绝过期模板与总量耗尽;道具卡发放应限制批量与用户持有上限。
* 抽奖随机性验证:同一承诺版本重复验证一致;拒绝采样不产生取模偏差。
## 交付
* 提供接口调用序列与测试数据清单(活动/期/奖励/称号/效果参数/指定用户)。
* 输出问题清单与修复建议(接口、权限、风控、参数校验、密钥安全)。
* 可选:生成 Postman/HTTPie 脚本或后端集成测试go test便于自动化回归。
确认后,我将按以上计划执行闭环测试,汇总问题与修复建议,并提供测试脚本(不使用 curl 的情况下可采用内置工具或 Postman 集合)。

View File

@ -1,132 +0,0 @@
## 目标
* 全面清理未用代码、注释废弃块、空文件与无用测试
* 识别并重构重复代码重复率≥80%
* 保持现有功能稳定,构建与测试全部通过
* 输出对比报告与文档更新
## 范围
* 后端:`internal/**``cmd/**``migrations/**`
* 前端管理:`web/admin/**`Vue/TS/样式与公共组件)
* 通用资源:`docs/**`、脚手架与配置(不更改生产配置)
## 清理策略与工具
* 未用与死代码检测
* Go`golangci-lint`unused、deadcode、revive`go vet`
* TS/Vue`tsc --noEmit`(类型与未用导出)、`eslint`no-unused-vars/no-dead-code
* 注释废弃块识别
* 规则Grep 检索注释中出现代码结构(`func|class|export|<template>`),人工确认后删除
* 空文件/无用测试
* Glob + Read 识别空/仅注释文件;移除未被引用的测试(无匹配运行入口或全跳过)
* 重复代码检测
* 跨语言:`jscpd`Vue/TS/Go`dupl`Go
* 阈值:重复度 ≥ 80% 且行数 ≥ 20 行
* 重构原则
* 后端:抽取到 `internal/pkg/common` 或现有包的工具单元;避免交叉包循环依赖
* 前端:抽取到 `web/admin/src/components/common``utils`,保持现有风格与命名
## 执行步骤
1. 基线采集
* 读取项目结构与关键模块,记录当前构建状态(不修改)
* 运行只读分析:语义搜索/正则/Grep收集疑似未用项、注释废弃块、空文件、重复片段清单
1. 未用代码清理
* 逐文件比对引用关系Grep/语义搜索),将“未被任何入口引用”标记为候选
* 生成候选清单(含文件路径与符号名),按模块批次删除;每次删除后执行增量构建验证
1. 注释废弃块删除
* 扫描 `//``/* */``<!-- -->` 中含可编译结构的片段,人工确认后删除
* 对 SFC 中注释的 `<template>/<script>/<style>` 片段严谨处理,避免结构破坏
1. 空文件与无用测试
* 移除 0 字节/仅注释文件;对测试:无法被测试运行器加载、或所有用例被跳过的文件移除
1. 重复代码重构
* 跑相似度分析,生成报告(位置、重复度、建议合并点)
* 抽取公共方法/组件,替换调用方;保持 API 不变,变更点最小化
1. 依赖与引用更新
* 后端:修复 import前端修复路径别名与组件引用保证编译通过
1. 验证与回归
* 构建验证:`go build``tsc --noEmit`、前端 `npm run build`(或等价)
* 单/集成测试:运行现有测试;若缺失,补最小冒烟测试(关键模块)
* 功能回归:登录、活动管理、一番赏映射、承诺生成/摘要、开奖与订单查询
## 安全保障
* 分批次清理,每批次后执行构建与核心用例回归
* 只删除“未引用/重复/注释废弃/空”的候选核心路径API、路由、策略、DAO、视图谨慎处理
* 所有变更均记录到临时报告与文档
## 交付物
* 代码对比报告:删除/变更列表(文件路径、符号名、原因)
* 重复代码检测报告:重复片段与重构前后引用图
* 验收文档:构建输出、测试结果、回归清单
* 文档更新:在 `docs/代码清理` 目录建立说明与进度记录
## 本次已执行清理项2025-12-08
* 移除抽奖策略注册表冗余(保留接口类型),不影响默认策略与一番赏流程
* 删除 Guild 相关 DAO/Model 生成文件(项目内无引用,运行不受影响)
* 移除管理端 batch\_users 接口与路由挂载(前端无调用)
* 移除管理端 IssueUserToken 路由挂载(前端无调用;代码文件暂保留为未引用状态)
* 构建验证:`go build ./...` 通过;`go test ./...` 存在历史用例失败(外部依赖与软删列缺失),与本次变更无关
## 文档与规范对齐
* 创建:`docs/代码清理/ALIGNMENT_代码清理.md`(范围/边界/不确定点)
* 共识:`docs/代码清理/CONSENSUS_代码清理.md`(验收标准与方案)
* 设计与任务拆分:`docs/代码清理/DESIGN_代码清理.md``TASK_代码清理.md`
* 执行与评估:`docs/代码清理/ACCEPTANCE_代码清理.md``FINAL_代码清理.md``TODO_代码清理.md`
## 后续执行说明
* 获批后:按上述步骤使用只读扫描确定候选清单→分批次提交清理补丁→每批次构建与回归验证→汇总报告与文档更新。

View File

@ -1,157 +0,0 @@
## 目标
* 移除无关的“客服组”等错误效果类型与字段严格对齐后端模型定义SystemTitles/SystemTitleEffects/UserTitles
* 统一“效果类型/参数/叠加策略/封顶/作用范围”配置为后端可用的结构确保抽奖逻辑effect\_type=5/6准确生效。
* 优化前端管理界面与 API 交互,保证运营可视化配置与后端运行一致。
## 范围
* 前端文件:
* `web/admin/src/views/operations/titles/components/EffectEditDialog.vue`
* `web/admin/src/views/operations/titles/components/EffectManagerDialog.vue`
* `web/admin/src/views/operations/titles/components/RuleConfigDialog.vue`
* `web/admin/src/views/operations/titles/index.vue`
* `web/admin/src/api/titles.ts`
* 后端仅对齐说明,不改动逻辑(模型/服务已正确):
* `internal/service/activity/draw_with_effects.go`(抽奖应用)
* `internal/service/title/effects_resolver.go`(效果范围)
* `internal/api/admin/titles_admin.go`(创建/修改/分配)
## 重构设计(按模型对齐)
### 效果类型与参数(替换现有 UI 与默认值)
* 1 领券(优惠券使者)
* `params_json``{ "template_id": number, "frequency": { "period": "day|week|month", "times": number } }`
* 2 抽奖折扣
* `params_json``{ "discount_type": "percentage|fixed", "value_x1000": number, "max_discount_x1000": number }`
* 3 签到倍数
* `params_json``{ "multiplier_x1000": number, "daily_cap_points": number }`
* 4 领道具卡
* `params_json``{ "template_id": number, "frequency": { "period": "week|month", "times": number } }`
* 5 概率加成(抽奖)
* `params_json``{ "target_prize_ids": number[], "boost_x1000": number, "cap_x1000": number? }`
* 6 奖品双倍概率(抽奖)
* `params_json``{ "target_prize_ids": number[], "chance_x1000": number, "period_cap_times": number? }`
* 移除:`生日特权/专属客服` 效果类型与全部相关字段UI/默认值/展示/说明)。
### 叠加策略与封顶(严格按模型)
* `stacking_strategy` 取值与文案:
* 0最大值max\_only
* 1累加封顶sum\_with\_cap
* 2首个匹配first\_match
* `cap_value_x1000`:统一封顶,单位千分比(用于 5/6 合并时的全局封顶)。
* 统一前端控件与默认值默认建议1 累加封顶;`cap_value_x1000`=0 表示不封顶)。
### 效果作用范围(效果级 scopes\_json
* 字段:`activity_ids/issue_ids/category_ids``exclude.*`
* 在效果编辑对话框中新增“作用范围”折叠面板:
* 多选输入上述集合;支持排除列表;空集合视为全局。
* 展示格式:在效果列表中将 `scopes_json` 可视化为“包含/排除”标签。
### 获得规则(标题级 obtain\_rules\_json
* 保留 UI方便运营标注明确说明其仅存储不参与当前自动授予后续如接入事件评估器再使用。
* JSON 结构:`{ methods: string[], conditions: object }`;维持现有 RuleConfigDialog 保存逻辑。
## 具体改动点(不执行,供审核)
* `EffectEditDialog.vue`
* 替换效果类型下拉项为 1\~6 正确枚举;删除“生日特权/专属客服”。
* 重写各类型的表单字段与 `getDefaultParams()` 返回值,改为上述模型参数结构。
* 调整 `stacking_strategy` 单选值为 0/1/2文案与后端一致。
* 新增“作用范围”字段并在提交时携带 `scopes_json`
* `EffectManagerDialog.vue`
* 更新 `effectTypes` 名称与 Tag 映射为正确文案:`领券/抽奖折扣/签到倍数/领道具卡/概率加成/双倍概率`
* 调整 `stackingStrategies` 文案为 0/1/2去掉无模型项。
* 更新参数格式化 `formatParam()`,支持 `template_id/frequency/discount_type/value_x1000/...`
* 增加对 `scopes_json` 的展示:包含/排除集合标签。
* `RuleConfigDialog.vue`
* 保留现有 UI在文档中明确“标题级范围不参与运行时判定”避免误解。
* `titles.ts`
* 校验请求参数与后端一致:`effect_type/params_json/stacking_strategy(0|1|2)/cap_value_x1000/scopes_json/sort/status`
## 数据迁移与兼容
* 已存在的效果记录:
* 运营检查并手动改为新结构(通过管理端编辑),或提供一次性迁移脚本:按 `effect_type` 将旧 `params_json` 映射为新字段。
* 前端:
* 旧页面中“生日特权/客服组”等效果无法再新建;编辑时提示需切换为模型定义效果。
## 测试与验收
* 配置与展示:创建每一种效果,检查列表显示与 JSON 持久化。
* 抽奖逻辑:
* `effect_type=5`:目标奖品权重按 `boost_x1000` 调整,叠加与封顶正确;参见 `internal/service/activity/draw_with_effects.go`
* `effect_type=6`:双倍概率按 `chance_x1000` 合并封顶;命中后倍数 x2名称后缀标识参见同文件。
* 作用范围:在 issueID 不同期下验证包含/排除命中行为;参见 `effects_resolver.go` 与抽奖文件。
* 运营页面每个功能或者配置字段需要详细描述
## 交付物
* 代码层面的重构(上述文件修改)。
* 文档更新:已在 `docs/称号系统/说明文档.md` 完成字段级详解与代码绑定点,重构后同步补充“字段校验与默认值”章节与新 UI 截图位点。
## 风险与注意
* 现有错误效果类型的数据需运营确认是否迁移或删除。
* 前端表单字段与后端参数必须完全对齐,否则抽奖效果不会生效。
确认后我将开始实施重构并提供测试用例与验证记录。

View File

@ -1,67 +0,0 @@
## 目标
- 交付一份字段级的《说明文档》增强版,逐一解释:
1) 获得规则ObtainRulesJSON字段字典、用途、代码绑定点
2) 使用范围ScopesJSON字段字典、用途、代码绑定点
3) 效果数量与叠加策略(多效果、排序、封顶、组合规则);
4) 规则助手(前端可视化配置)字段映射与校验;
- 所有字段给出示例与“在代码块哪里起作用”的精确文件/行号。
## 文档结构
- 《docs/称号系统/说明文档.md》新增章节
- 获得规则字段详解ObtainRulesJSON
- 使用范围字段详解ScopesJSON标题&效果)
- 效果数量、叠加策略与封顶StackingStrategy/CapValueX1000
- 规则助手详解UI字段 → JSON 映射 → 后端字段)
- 代码绑定点总览(带 file_path:line_number 引用)
## 字段级内容(概要)
- 获得规则ObtainRulesJSON
- 字段:`methods: string[]``conditions: object`(如 `lottery_type`, `min_amount`, `time_range` 等)
- 用途:当前仅“存取”,未参与后端自动授予逻辑(需后续服务层接入)
- 代码绑定:
- 写入:`internal/api/admin/titles_admin.go:88/94/125`
- 预置:`internal/api/admin/titles_seed.go:54`
- DAO映射`internal/repository/mysql/dao/system_titles.gen.go:34/53/77/102`
- 示例与建议:提供多场景 JSON 模板与后续接入建议(事件钩子/规则评估器)
- 使用范围ScopesJSON
- 标题层:`SystemTitles.ScopesJSON`(目前仅存取,不参与运行时判定)
- 效果层:`SystemTitleEffects.ScopesJSON`(运行时生效,活动/期/分类的包含/排除)
- 字段字典:`activity_ids, issue_ids, category_ids, exclude.{...}`
- 代码绑定:
- 解析与匹配:`internal/service/title/effects_resolver.go`scopeMatch
- 抽奖应用:`internal/service/activity/draw_with_effects.go`issue 过滤)
- 管理端读写:`internal/api/admin/titles_admin.go`、预置:`titles_seed.go`
- 示例:包含与排除的组合配置(带示意流程)
- 效果数量与叠加策略
- 每个称号可挂载多个效果;按 `sort` 排序;`status` 控制启用;
- `StackingStrategy`0最大值/1累加封顶/2首个匹配/默认累加封顶;
- `CapValueX1000`:统一封顶(千分比)
- 代码绑定:
- 抽奖应用:`internal/service/activity/draw_with_effects.go`effect_type=5/6 合并规则)
- 模型定义:`internal/repository/mysql/model/system_title_effects.gen.go`
- 示例:多效果组合的实际影响(权重调整/双倍合并概率)
- 规则助手(前端)
- 位置:`web/admin/src/views/operations/titles/components/RuleConfigDialog.vue`
- 字段映射UI → `obtain_rules_json`/`scopes_json`
- 校验/预览JSON 校验、示例模板、保存策略
- 注意:当前 effect_type=5/6 的前端参数与后端不一致(计划列出对齐方案与字段映射表,不改代码,先文档说明)
## 代码绑定点(示例引用)
- ObtainRulesJSON 写入:`internal/api/admin/titles_admin.go:88, 94, 125`
- ScopesJSON 应用匹配:`internal/service/title/effects_resolver.go:line`(具体行号在交付时标注)
- 抽奖效果应用:`internal/service/activity/draw_with_effects.go:line`(包含 5/6 的 switch 分支具体行号)
- 种子数据:`internal/api/admin/titles_seed.go:31-37, 54-56, 70-80`
## 交付步骤
1. 使用只读工具确认所有字段与代码绑定点的具体行号;
2. 更新《说明文档.md》按上述结构补充完备的字段级说明与代码引用
3. 提供前端与后端参数对齐表(仅文档,不改代码);
4. 给出后续建议:自动授予逻辑接入方案(服务层/事件钩子/任务调度)。
## 验收标准
- 文档包含所有字段的用途、类型、取值范围、示例;
- 每个字段“在哪里起作用”都给出精确文件/行号;
- 覆盖四项:获得规则、使用范围、效果数量、规则助手;
- 补充参数对齐提醒与后续接入建议;
确认后我将开始更新文档并提交增强内容。

View File

@ -1,116 +0,0 @@
## 目标
- 为“抽奖后获得双倍奖励,并具备时效”的业务提供可执行的创建流程与说明文档。
- 输出一份可直接用于运营与研发的《说明文档.md》包含项目规划、实施方案、进度记录以及完整的操作与集成指南。
## 核心概念
- SystemTitles系统称号主数据含名称、描述、获取规则、适用范围等。参考 internal/repository/mysql/model/system_titles.gen.go
- SystemTitleEffects称号效果含效果类型、参数、叠加策略、封顶值等。参考 internal/repository/mysql/model/system_title_effects.gen.go
- UserTitles用户称号记录含生效状态与过期时间用于管控时效。参考 internal/repository/mysql/model/user_titles.gen.go
- 效果类型示例:在 admin/titles_seed.go 已内置6类含“双倍之王EffectType=6”。参考 internal/api/admin/titles_seed.go:18-37
## 后端接口一览(供前后端与运维对齐)
- 系统称号(管理员)
- GET/POST/PUT/DELETE `/api/admin/system_titles`
- GET/POST/PUT/DELETE `/api/admin/system_titles/:title_id/effects`
- 用户称号分配(管理员)
- POST `/api/admin/users/:user_id/titles`(分配或更新有效期与激活状态)
- 路由参考internal/router/router.go
- 管理端控制器参考internal/api/admin/titles_admin.go列表/创建/修改/删除/分配)
## 创建流程(运营页面)
1. 进入管理端:`/operations/titles`(确保菜单已存在,见 EnsureTitlesMenu
2. 创建称号:
- 名称:例如“抽奖双倍达人”
- 描述例如“抽奖奖励翻倍限时24小时”
- 状态:启用
3. 配置获取规则ObtainRulesJSON
- methods`["lottery"]`
- conditions可选如抽奖类型 `lottery_type="normal"`
- 示例:
```json
{
"methods": ["lottery"],
"conditions": {"lottery_type": "normal"}
}
```
4. 配置适用范围ScopesJSON可为空或限定品类/场次/用户等级,按需填写
5. 添加效果SystemTitleEffects选择“EffectType=6双倍概率并设置参数
- `target_prize_ids`: [](空表示所有奖品)
- `chance_x1000`: 1000100% 双倍;或按需改为 200=20%
- `period_cap_times`: 1周期内最多触发次数建议与日/活动周期对齐)
- 示例:
```json
{
"target_prize_ids": [],
"chance_x1000": 1000,
"period_cap_times": 1
}
```
6. 保存称号与效果,确认列表中显示。
## 分配与时效(运营或自动)
- 运营手动分配(页面“分配用户”对话框):
- 选择用户,设定有效期类型:
- `permanent` 永久
- `period` 指定开始/结束时间(填写 `start_time`/`end_time`
- `days` 按天数有效(例如 1 天)
- 勾选“覆盖现有/发送通知”按需配置,提交后生效
- 抽奖后自动分配(后端集成点):
- 在抽奖流程成功后,调用分配接口或服务方法为用户写入 UserTitles设置 `expires_at`
- 建议在抽奖服务中统一封装:检查用户是否已拥有称号;若没有则分配,有则延长或保持不变(按业务策略)
## 抽奖流程应用双倍效果(后端逻辑)
- 在发奖时:
1. 读取用户当前激活且未过期的 UserTitles
2. 聚合其 SystemTitleEffectsEffectType=6解析 `chance_x1000``period_cap_times`
3. 按概率/次数限制判断是否触发翻倍,将奖品数量或价值翻倍(或对指定 `target_prize_ids` 生效)
4. 记录一次触发(用于控制当期 `period_cap_times`
- 注意叠加策略StackingStrategy与封顶CapValueX1000需一并考虑避免超限
## 配置示例(可直接复用)
- 称号获取规则(让称号只在抽奖场景下获得)
```json
{
"methods": ["lottery"],
"conditions": {"lottery_type": "normal"}
}
```
- 双倍效果参数(对所有奖品 100% 双倍,且周期内最多 1 次)
```json
{
"target_prize_ids": [],
"chance_x1000": 1000,
"period_cap_times": 1
}
```
- 用户分配有效期(按天)
```json
{
"expire_type": "days",
"days": 1,
"remark": "抽奖获得双倍称号",
"override_existing": true,
"send_notification": true
}
```
## 测试与验收
- 管理端:
- 创建称号与效果 → 列表校验 → 为测试用户分配1天有效
- 抽奖流程:
- 模拟用户抽奖 → 校验发奖结果是否翻倍 → 检查 period_cap_times 限制是否生效
- 到期后再次抽奖:不再翻倍(验证 `expires_at` 生效)
- 数据核查:
- 检查 UserTitles 记录及过期时间
- 检查效果触发记录(若有)与叠加/封顶逻辑
## 说明文档结构(交付内容)
- 文件:`docs/称号系统/说明文档.md`
- 项目规划:业务目标、范围、时间节点
- 实施方案:模型与接口、创建与分配流程、抽奖集成方案
- 进度记录:每次变更与验证结果
- 操作指南:页面操作步骤、配置示例、常见问题
- 验收标准:用例列表与通过条件
## 后续工作
- 我将依据上述结构生成《说明文档.md》并补充操作截图位点与接口示例如需我同时在抽奖服务中添加自动分配与双倍应用的集成代码请确认后我直接实现并提交测试用例。

View File

@ -1,19 +0,0 @@
## 目标
- 从工作台移除“代办事项”模块
- 将“实时抽奖动态”扩展为整行宽度展示,并修复行内容被裁剪问题
## 变更点
1) 页面布局 `web/admin/src/views/dashboard/console/index.vue`
- 删除 `TodoList` 引入与对应列
- 调整最后一行:`Dynamic` 占满 24 列(全宽),`NewUser` 保持 12/24 或前面布局不变
2) 动态组件样式 `web/admin/src/views/dashboard/console/modules/dynamic-stats.vue`
- 移除每行固定高度与 `overflow-hidden`,允许换行:`py-2 leading-6``whitespace-normal break-words`
- 行内容分两行显示:第一行“昵称 在 活动-期号”,第二行“中奖 奖品名/参与”
- 保持滚动容器,确保长列表可滚动
- 去掉裁剪到 100 条的逻辑,以满足“全部显示实时动态”(如需后续限制可再加分页/虚拟列表)
## 验证
- 工作台不再显示“代办事项”卡片
- 动态模块全宽显示,长活动名/奖品名不再被裁剪
- 列表滚动正常,持续追加数据可见

View File

@ -1,12 +0,0 @@
## 目标
- 将效果编辑对话框的作用范围精简为“包含活动(activity_ids)”“包含期(issue_ids)”与“排除期(exclude.issue_ids)”三项,移除分类等无关选项。
- 在效果列表中新增“作用范围”列,直观展示包含/排除的活动与期。
- 保持与后端抽奖逻辑一致,确保 effect_type=6 的参数与范围保存后可被正确解析与应用。
## 改动点
- 编辑对话框:只保留 activity_ids、issue_ids、exclude.issue_ids 的输入;提交时构建简化的 scopes_json。
- 效果列表:新增范围列并格式化展示。
- API 类型:已支持 scopes_json无需后端改动
## 验收
- 可在管理端创建或编辑双倍效果,查看列表范围列;抽奖时按期过滤生效。

View File

@ -1,34 +0,0 @@
## 现状审计
- 高级表格使用:
- 活动管理:`web/admin/src/views/activity/manage/index.vue:10-17` 使用 `ArtTable`(高级表格)
- 期数管理:`web/admin/src/views/activity/issues/index.vue:6-14` 使用 `ArtTable`
- 奖励管理:`web/admin/src/views/activity/rewards/index.vue:31-37` 使用 `ArtTable`
- 操作栏一致性:
- 活动管理中“模拟”按钮使用 `ElButton``manage/index.vue:32-39`),其余使用 `ArtButtonTable`;风格不一致
- 期数管理操作栏统一使用 `ArtButtonTable``issues/index.vue:21-33`
- 表格可视宽度:多列设置了固定 `width`(如 `manage/index.vue:291-297``issues/index.vue:83-88``name` 列使用 `showOverflowTooltip``manage/index.vue:292`),导致内容显示受限;未显式启用 `tableLayout="auto"`
## 优化目标
1. 全部使用高级表格组件,启用自适应布局以显示完整内容
2. 操作栏统一为图标标签按钮(`ArtButtonTable`),视觉与交互一致
3. 表格宽度与列长度自适应:移除过度固定宽度,采用 `minWidth``tableLayout="auto"`,仅对数值列设定最小宽度;必要时保留溢出提示
## 具体改动
- 活动管理(`web/admin/src/views/activity/manage/index.vue`
- 在 `ArtTable` 上增加 `tableLayout="auto"`
- 将 `columnsFactory` 中固定 `width` 改为 `minWidth`(如 `name``minWidth: 260``categoryName` `minWidth: 140``actions` `minWidth: 240`),取消 `showOverflowTooltip` 或仅对极长文本保留
- 将“模拟抽奖”按钮统一为 `ArtButtonTable`,图标 `ri:shuffle-line`,与现有按钮风格一致(替换 `ElButton`,位置 `index.vue:32-39`
- 期数管理(`web/admin/src/views/activity/issues/index.vue`
- 在 `ArtTable` 上增加 `tableLayout="auto"`
- 将列宽改为 `minWidth`,例如:`issue_number` `minWidth: 160``status` `minWidth: 110``actions` `minWidth: 200`
- 已统一使用 `ArtButtonTable`,无需额外调整
- 奖励管理(`web/admin/src/views/activity/rewards/index.vue`
- 已是 `ArtTable` 并启用 `tableLayout="auto"`;保留前次优化的汇总与列增强(剩余/总量、期望概率)
## 验收标准
- 三个页面均使用 `ArtTable`,内容在常见分辨率下完整显示(无不必要截断)
- 操作栏按钮风格一致,皆为图标标签按钮,间距统一
- 表格宽度随容器自适应,长文本列通过 `minWidth` 与布局算法得到更好的展示;必要时保留 tooltip
## 风险与回滚
- 改动为前端表现层,低风险;如需回滚,可恢复原 `width` 配置与旧按钮类型。

Some files were not shown because too many files have changed in this diff Show More