迭代计划准备
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# 需求14:导出功能
|
||||
|
||||
> 复用现有 ExportTask 体系(`tb_export_task` + Asynq)
|
||||
> 状态:待评审
|
||||
> 复用现有 ExportTask 体系(`tb_export_task` + Asynq),不为每个业务模块新增一套导出路由。
|
||||
|
||||
---
|
||||
|
||||
@@ -14,6 +15,7 @@
|
||||
| 6.8.4 | 退款管理退款列表导出 | **全新** |
|
||||
| 6.8.5 | 换货管理导出 | **全新** |
|
||||
| 6.8.6 | 代理充值导出 | **全新**(去掉"支付通道"字段) |
|
||||
| 需求22 | 临期资产列表导出 | **全新**,`scene=expiring_asset` |
|
||||
|
||||
---
|
||||
|
||||
@@ -26,11 +28,47 @@
|
||||
- Asynq Worker 根据任务里的 `scene` 字段,从 Registry 取对应 DataSource 执行
|
||||
- 前端轮询任务状态后下载
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User as 后台用户
|
||||
participant Web as 前端
|
||||
participant API as ExportTask API
|
||||
participant DB as PostgreSQL
|
||||
participant Worker as Asynq Worker
|
||||
participant Storage as 对象存储
|
||||
|
||||
User->>Web: 选择导出字段并确认
|
||||
Web->>API: POST /api/admin/export-tasks
|
||||
API->>API: 计算数据范围和字段权限交集
|
||||
API->>DB: 保存查询、范围和字段快照
|
||||
API-->>Web: task_id
|
||||
API->>Worker: 入队导出任务
|
||||
Worker->>DB: 按 scene 分片查询
|
||||
Worker->>Storage: 上传导出文件
|
||||
Worker->>DB: 更新进度和 download file_key
|
||||
Web->>API: 轮询 GET /api/admin/export-tasks/{id}
|
||||
API-->>Web: 状态、进度、download_url
|
||||
```
|
||||
|
||||
新增导出模块需要:
|
||||
1. 在 `pkg/constants/constants.go` 新增 `ExportTaskSceneXxx` 场景常量
|
||||
2. 在 `internal/exporter/` 新建 `xxx_scene.go`,实现 `DataSource` 接口(`Scene()`/`Count()`/`Headers()`/`Fetch()`)
|
||||
3. 在 `registry.go` 的 `NewDefaultRegistry()` 中注册,并更新 `IsSupportedScene()`
|
||||
4. 新增对应的 Export API(创建导出任务,传入 `scene` 字段)
|
||||
3. 在 `registry.go` 的 `NewDefaultRegistry()` 中注册,并更新 `IsSupportedScene()`
|
||||
4. 扩展统一 `CreateExportTaskRequest.Scene` 的允许值,通过 `POST /api/admin/export-tasks` 创建任务
|
||||
5. 在字段目录中注册稳定字段 key、中文表头、默认选择和所需导出权限
|
||||
|
||||
统一创建请求扩展为:
|
||||
|
||||
```go
|
||||
type CreateExportTaskRequest struct {
|
||||
Scene string `json:"scene" validate:"required" description:"导出场景"`
|
||||
Format string `json:"format" validate:"required,oneof=xlsx csv" description:"导出格式"`
|
||||
Query map[string]any `json:"query,omitempty" description:"与列表一致的筛选条件"`
|
||||
Fields []string `json:"fields" validate:"required,min=1" description:"申请导出的字段key"`
|
||||
}
|
||||
```
|
||||
|
||||
新增场景:`agent_wallet_transaction`、`package`、`refund`、`exchange`、`agent_recharge`、`expiring_asset`。DTO description 和 `pkg/constants/` 必须同步维护。
|
||||
|
||||
---
|
||||
|
||||
@@ -66,7 +104,7 @@ DataLimitMB int64 `gorm:"column:data_limit_mb"`
|
||||
|
||||
## 6.8.2:代理资金概况-预充值钱包流水导出
|
||||
|
||||
**新增接口**:`POST /admin/agent-wallet-transactions/export`
|
||||
**新增场景**:`scene=agent_wallet_transaction`
|
||||
|
||||
支持与现有钱包流水列表相同的筛选条件,异步生成 Excel。
|
||||
|
||||
@@ -83,7 +121,7 @@ DataLimitMB int64 `gorm:"column:data_limit_mb"`
|
||||
| 交易时间 | `created_at` |
|
||||
| 交易前金额 | `balance_before / 100` |
|
||||
| 交易后金额 | `balance_after / 100` |
|
||||
| 购买套餐名称 | `metadata` 中 JSON 字段 `package_name`,或 JOIN 订单 |
|
||||
| 购买套餐名称 | 新数据读取 `tb_order_item.package_name` 不可变快照;历史缺失时才回退 `metadata.package_name`,禁止关联当前套餐名称 |
|
||||
| 操作人 | JOIN `tb_account`(`creator` 字段关联 `tb_account.id`,取 `username`) |
|
||||
| 交易 ID | `id` |
|
||||
| 关联业务订单号 | `reference_id` 对应的单号(JOIN 对应表) |
|
||||
@@ -93,11 +131,11 @@ DataLimitMB int64 `gorm:"column:data_limit_mb"`
|
||||
|
||||
## 6.8.3:套餐列表导出
|
||||
|
||||
**新增接口**:`POST /admin/packages/export`
|
||||
**新增场景**:`scene=package`
|
||||
|
||||
支持现有套餐列表筛选条件。
|
||||
|
||||
导出字段(按需求文档 24 个字段):
|
||||
导出字段(按实际列举的 25 个稳定字段 Key):
|
||||
|
||||
```go
|
||||
type PackageExportRow struct {
|
||||
@@ -133,9 +171,9 @@ type PackageExportRow struct {
|
||||
|
||||
## 6.8.4:退款管理退款列表导出
|
||||
|
||||
**新增接口**:`POST /admin/refund-orders/export`
|
||||
**新增场景**:`scene=refund`
|
||||
|
||||
导出字段(去掉"退款到账方式",保留其余字段,部门领导/财务审批人依赖审批流):
|
||||
导出字段(去掉“退款到账方式”,审批信息使用动态摘要,不固定具体节点):
|
||||
|
||||
```go
|
||||
type RefundExportRow struct {
|
||||
@@ -153,21 +191,25 @@ type RefundExportRow struct {
|
||||
Status string `xlsx:"状态"`
|
||||
RefundReason string `xlsx:"退款原因"`
|
||||
Remark string `xlsx:"备注"`
|
||||
ApprovalRemark string `xlsx:"审批备注"`
|
||||
ApprovalSource string `xlsx:"审批来源"`
|
||||
ApprovalStatus string `xlsx:"审批状态"`
|
||||
ProcessingStatus string `xlsx:"退款处理状态"`
|
||||
CurrentNodeName string `xlsx:"当前审批节点"`
|
||||
ApprovalRecords string `xlsx:"审批记录"`
|
||||
AppliedAt string `xlsx:"退款申请时间"`
|
||||
CompletedAt string `xlsx:"退款完成时间"`
|
||||
SubmitterName string `xlsx:"提交人"`
|
||||
DeptLeaderName string `xlsx:"部门领导审批人"`
|
||||
FinanceName string `xlsx:"财务审批人"`
|
||||
VoucherURLs string `xlsx:"退款凭证"`
|
||||
}
|
||||
```
|
||||
|
||||
`ApprovalRecords` 格式示例:`业务审核:张三(通过,同意,附件2个);财务审核:李四(待处理)`。导出只记录附件数量,不导出对象存储 URL 或 file_key。`ProcessingStatus` 区分待人工退款、代理钱包回退处理中、已完成和处理失败。数据源按本批业务单的 `approval_instance_id` 批量查询审批任务、审批人和附件计数,禁止逐行查询。历史终态单据没有流程实例时,`ApprovalSource` 输出“历史审批”,审批记录仅使用原业务审批字段和审计日志,不伪造多节点记录。
|
||||
|
||||
---
|
||||
|
||||
## 6.8.5:换货管理导出
|
||||
|
||||
**新增接口**:`POST /admin/exchange-orders/export`
|
||||
**新增场景**:`scene=exchange`
|
||||
|
||||
```go
|
||||
type ExchangeExportRow struct {
|
||||
@@ -193,7 +235,7 @@ type ExchangeExportRow struct {
|
||||
|
||||
## 6.8.6:代理充值导出
|
||||
|
||||
**新增接口**:`POST /admin/agent-recharge-orders/export`
|
||||
**新增场景**:`scene=agent_recharge`
|
||||
|
||||
去掉"支付通道"字段(需求文档中明确去掉),保留其他字段:
|
||||
|
||||
@@ -215,13 +257,17 @@ type AgentRechargeExportRow struct {
|
||||
PaidAt string `xlsx:"支付时间"`
|
||||
CompletedAt string `xlsx:"完成时间"`
|
||||
SubmitterName string `xlsx:"提交人"`
|
||||
DeptLeaderName string `xlsx:"部门领导审批人"`
|
||||
FinanceName string `xlsx:"财务审批人"`
|
||||
ApprovalSource string `xlsx:"审批来源"`
|
||||
ApprovalStatus string `xlsx:"审批状态"`
|
||||
CurrentNodeName string `xlsx:"当前审批节点"`
|
||||
ApprovalRecords string `xlsx:"审批记录"`
|
||||
VoucherURLs string `xlsx:"支付凭证"`
|
||||
Remark string `xlsx:"备注"`
|
||||
}
|
||||
```
|
||||
|
||||
充值导出的审批记录格式和批量查询规则与退款导出一致。
|
||||
|
||||
---
|
||||
|
||||
## 前端对接(通用模式)
|
||||
@@ -229,19 +275,138 @@ type AgentRechargeExportRow struct {
|
||||
各导出入口:对应列表页右上角"导出"按钮(与现有导出按钮样式一致)。
|
||||
|
||||
调用流程:
|
||||
1. 点击"导出" → 携带当前筛选条件 → `POST /admin/{module}/export`
|
||||
2. 返回 `export_task_id`
|
||||
3. 前端轮询 `GET /admin/export-tasks/{id}` 直到 `status=completed`
|
||||
4. 下载 `download_url`
|
||||
1. 点击“导出”后请求当前账号在该场景可导出的字段目录。
|
||||
2. 弹框只展示后端允许的字段,默认勾选 `default_selected=true` 的字段。
|
||||
3. 提交 `scene + format + query + fields` 到 `POST /api/admin/export-tasks`。
|
||||
4. 返回 `task_id` 后,前端轮询 `GET /api/admin/export-tasks/{id}` 直到终态。
|
||||
5. `status=3` 时下载 `download_url`;失败时展示任务错误摘要,禁止自动重复创建任务。
|
||||
|
||||
(与现有导出体系完全一致,复用现有前端导出 Hook)
|
||||
|
||||
---
|
||||
|
||||
## 权限导出配置(预留)
|
||||
## 导出字段权限
|
||||
|
||||
需求提到"不同权限显示的字段不同,角色管理中新增导出字段配置"。
|
||||
|
||||
**本次迭代**:统一导出全量字段,不做权限差异化(复杂度高,单独排期)。
|
||||
评审结论:本迭代必须实现角色级导出字段配置,不作为后续预留。
|
||||
|
||||
**预留方案**:在导出 Handler 里预留 `filterFieldsByRole(userRoles, rows)` 的调用点,本次返回全量,后续加权限配置后在此处过滤。
|
||||
该要求涉及真流量、成本价、身份材料等敏感数据,不能以“本期先全量导出”代替。字段权限必须由后端强制执行,前端复选框只负责展示。
|
||||
|
||||
### 1. 字段目录
|
||||
|
||||
每个场景在代码中注册稳定字段 key:
|
||||
|
||||
```go
|
||||
// ExportFieldDefinition 导出字段定义
|
||||
type ExportFieldDefinition struct {
|
||||
Key string
|
||||
Header string
|
||||
DefaultSelected bool
|
||||
Required bool
|
||||
}
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
scene=package
|
||||
package_code 套餐编码 默认选择
|
||||
package_name 套餐名称 默认选择
|
||||
real_data_mb 真流量额度 敏感字段
|
||||
virtual_data_mb 虚流量额度 默认选择
|
||||
cost_price 成本价 敏感字段
|
||||
```
|
||||
|
||||
表头中文可以调整,但 `scene + field_key` 一经发布不得随意改名,否则历史角色配置会失效。
|
||||
|
||||
### 2. 角色字段授权表
|
||||
|
||||
```sql
|
||||
CREATE TABLE tb_role_export_field_permission (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
role_id BIGINT NOT NULL,
|
||||
scene VARCHAR(50) NOT NULL,
|
||||
field_key VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
creator BIGINT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_role_export_field_permission
|
||||
ON tb_role_export_field_permission(role_id, scene, field_key);
|
||||
```
|
||||
|
||||
账号拥有多个角色时,字段权限按 RBAC 常规规则取并集;超级管理员拥有全部字段。数据行范围仍使用现有数据权限快照,字段权限不能扩大店铺或企业数据范围。
|
||||
|
||||
### 3. 权限 API
|
||||
|
||||
```text
|
||||
GET /api/admin/export-fields?scene=package
|
||||
```
|
||||
|
||||
返回当前账号可选择的字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"scene": "package",
|
||||
"fields": [
|
||||
{
|
||||
"key": "package_code",
|
||||
"header": "套餐编码",
|
||||
"default_selected": true,
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
角色管理:
|
||||
|
||||
```text
|
||||
GET /api/admin/roles/{role_id}/export-fields
|
||||
PUT /api/admin/roles/{role_id}/export-fields
|
||||
```
|
||||
|
||||
更新请求按场景提交字段 key 列表,后端校验字段存在并记录审计日志。
|
||||
|
||||
### 4. 创建任务时的权限快照
|
||||
|
||||
创建任务时计算:
|
||||
|
||||
```text
|
||||
resolved_fields = requested_fields ∩ role_allowed_fields ∩ scene_supported_fields
|
||||
```
|
||||
|
||||
- 必选字段由后端自动补齐。
|
||||
- 交集为空时拒绝创建任务。
|
||||
- `resolved_fields` 和对应 `resolved_headers` 写入任务的 `query_json`,Worker 不重新根据后来变化的角色权限扩大字段。
|
||||
- Worker 只按照快照字段输出,未知字段直接失败,不允许静默回退到全量字段。
|
||||
|
||||
### 5. 前端角色配置
|
||||
|
||||
- 角色编辑页增加“导出字段权限”页签,按场景分组展示复选框。
|
||||
- 普通列表的导出弹框只显示当前账号被授权的字段。
|
||||
- 敏感字段可以增加“敏感”标记,但标记不能替代后端权限。
|
||||
- 用户取消所有可选字段时禁用提交按钮,并提示至少选择一个字段。
|
||||
|
||||
---
|
||||
|
||||
## 查询与性能约束
|
||||
|
||||
- 退款和充值审批记录必须先按本批 `approval_instance_id` 批量查询,再在内存按实例分组,禁止逐行查审批任务。
|
||||
- 关联业务单号、套餐名称和操作人必须使用批量 JOIN 或批量查询,禁止 N+1。
|
||||
- DataSource 的 Count 和 Fetch 必须使用同一份权限过滤和查询条件。
|
||||
- 动态字段不代表动态拼接不受控 SQL;字段 key 必须通过服务端白名单映射到固定查询列。
|
||||
|
||||
---
|
||||
|
||||
## 发布与回滚
|
||||
|
||||
本需求随七月迭代停机发布:
|
||||
|
||||
1. 维护窗口内先执行角色字段权限表迁移,并初始化现有角色的最小可用字段集。
|
||||
2. 同一发布窗口部署场景常量、DataSource、管理 API、Worker 和前端字段选择/角色配置页面。
|
||||
3. 开放访问前验证普通角色、敏感字段角色和超级管理员的字段集合及实际导出文件。
|
||||
4. 回滚时可关闭新增场景入口,但保留任务、文件和字段权限历史。
|
||||
|
||||
禁止在角色权限尚未初始化时默认放开全部敏感字段;无法解析权限时应拒绝导出并记录错误。
|
||||
|
||||
Reference in New Issue
Block a user