Files
ccdi/lsfx-mock-server/docs/plans/2026-03-12-upload-status-api-design.md

374 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 获取单个文件上传状态接口优化设计
## 文档信息
- **创建日期**: 2026-03-12
- **设计者**: Claude Code
- **状态**: 待实施
## 1. 需求背景
### 1.1 接口信息
- **接口路径**: `/watson/api/project/bs/upload` (GET)
- **接口名称**: 获取单个文件上传后的状态
- **项目背景**: 流水分析 Mock 服务器
### 1.2 当前问题
当前实现存在以下问题:
1. **依赖实际上传记录**: 接口依赖 `self.file_records`上传时存储的记录如果没有上传过文件logs 返回空数组
2. **不符合 Mock 服务器定位**: Mock 服务器应该独立工作,前端测试时不应依赖其他接口
3. **字段值不正确**: `logMeta` 字段中的 `balanceAmount` 值为布尔值 `true`,应该为字符串 `"-1"`
### 1.3 期望行为
根据接口文档(`assets/兰溪-流水分析对接3.md` 第374-516行
1. **带 logId 参数**: 根据 logId 生成固定的文件记录数据(相同 logId 返回相同数据)
2. **不带 logId 参数**: 返回空的 logs 数组
3. **固定成功状态**: status=-5, uploadStatusDesc="data.wait.confirm.newaccount"
4. **独立性**: 不依赖实际上传的文件记录,接口独立工作
## 2. 解决方案
### 2.1 设计原则
1. **确定性随机**: 使用 `random.seed(log_id)` 确保相同 logId 生成相同数据
2. **完全独立**: 不依赖 `self.file_records`,在 `get_upload_status()` 中直接生成数据
3. **文档对齐**: 严格遵循接口文档示例的字段和格式
4. **简单高效**: 代码简洁,易于维护和测试
### 2.2 核心设计
#### 2.2.1 数据生成策略
**基于 logId 的确定性随机生成**
```python
def get_upload_status(self, group_id: int, log_id: int = None) -> dict:
"""
获取文件上传状态
Args:
group_id: 项目ID
log_id: 文件ID可选
Returns:
上传状态响应字典
"""
logs = []
if log_id:
# 使用 logId 作为随机种子,确保相同 logId 返回相同数据
random.seed(log_id)
# 生成确定性的文件记录
record = self._generate_deterministic_record(log_id, group_id)
logs.append(record)
# 返回响应
return {
"code": "200",
"data": {
"logs": logs,
"status": "",
"accountId": 8954,
"currency": "CNY"
},
"status": "200",
"successResponse": True
}
```
#### 2.2.2 字段生成规则
根据文档示例(`assets/兰溪-流水分析对接3.md` 第431-499行logs 数组中的每个对象包含 26 个字段:
| 字段名 | 生成规则 | 示例值 |
|--------|----------|--------|
| accountNoList | 11位随机数字 | ["18785967364"] |
| bankName | 从3种银行中随机选择 | "ALIPAY" |
| dataTypeInfo | 固定值 | ["CSV", ","] |
| downloadFileName | 基于 logId 生成 | "测试文件_13994.csv" |
| enterpriseNameList | 70%概率有主体30%为空 | ["测试主体"] 或 [""] |
| fileSize | 随机范围 10000-100000 | 16322 |
| fileUploadBy | 固定值 | 448 |
| fileUploadByUserName | 固定值 | "admin@support.com" |
| fileUploadTime | 当前时间 | "2025-03-13 08:45:32" |
| isSplit | 固定值 | 0 |
| leId | 10000 + 随机数 | 10741 |
| logId | 参数传入 | 13994 |
| logMeta | **修复为字符串 "-1"** | "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}" |
| logType | 固定值 | "bankstatement" |
| loginLeId | 10000 + 随机数 | 10741 |
| lostHeader | 固定空数组 | [] |
| realBankName | 与 bankName 一致 | "ALIPAY" |
| rows | 固定值 | 0 |
| source | 固定值 | "http" |
| status | 固定成功值 | -5 |
| templateName | 根据银行选择对应模板 | "ALIPAY_T220708" |
| totalRecords | 随机范围 100-300 | 127 |
| trxDateEndId | 当前日期 | 20231231 |
| trxDateStartId | 当前日期 - 随机90-365天 | 20230102 |
| uploadFileName | 基于 logId 生成 | "测试文件_13994.pdf" |
| uploadStatusDesc | 固定成功描述 | "data.wait.confirm.newaccount" |
#### 2.2.3 银行类型映射
| bankName | templateName | realBankName |
|----------|--------------|--------------|
| "ALIPAY" | "ALIPAY_T220708" | "ALIPAY" |
| "BSX" | "BSX_T240925" | "BSX" |
| "ZJRCU" | "ZJRCU_T251114" | "ZJRCU" |
### 2.3 关键修复点
#### 修复1: logMeta 字段
**当前实现**`services/file_service.py:47`:
```python
log_meta: str = "{\"lostHeader\":[],\"balanceAmount\":true}" # ❌ 错误
```
**修复后**:
```python
log_meta: str = "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}" # ✅ 正确
```
#### 修复2: 独立数据生成
**当前实现**: 依赖 `self.file_records`
**修复后**: 在 `get_upload_status()` 中独立生成数据,不依赖上传记录
## 3. 技术设计
### 3.1 修改文件清单
| 文件 | 修改内容 |
|------|----------|
| `services/file_service.py` | 1. 修复 FileRecord.log_meta 默认值<br>2. 重构 get_upload_status() 方法<br>3. 新增 _generate_deterministic_record() 方法 |
### 3.2 核心代码实现
#### 3.2.1 新增方法: _generate_deterministic_record()
```python
def _generate_deterministic_record(self, log_id: int, group_id: int) -> dict:
"""
基于 logId 生成确定性的文件记录
Args:
log_id: 文件ID用作随机种子
group_id: 项目ID
Returns:
文件记录字典26个字段
"""
# 银行类型选项
bank_options = [
("ALIPAY", "ALIPAY_T220708"),
("BSX", "BSX_T240925"),
("ZJRCU", "ZJRCU_T251114")
]
bank_name, template_name = random.choice(bank_options)
# 生成交易日期范围
end_date = datetime.now()
start_date = end_date - timedelta(days=random.randint(90, 365))
# 生成账号和主体
account_no = f"{random.randint(10000000000, 99999999999)}"
enterprise_names = ["测试主体"] if random.random() > 0.3 else [""]
return {
"accountNoList": [account_no],
"bankName": bank_name,
"dataTypeInfo": ["CSV", ","],
"downloadFileName": f"测试文件_{log_id}.csv",
"enterpriseNameList": enterprise_names,
"fileSize": random.randint(10000, 100000),
"fileUploadBy": 448,
"fileUploadByUserName": "admin@support.com",
"fileUploadTime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"isSplit": 0,
"leId": 10000 + random.randint(0, 9999),
"logId": log_id,
"logMeta": "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}",
"logType": "bankstatement",
"loginLeId": 10000 + random.randint(0, 9999),
"lostHeader": [],
"realBankName": bank_name,
"rows": 0,
"source": "http",
"status": -5,
"templateName": template_name,
"totalRecords": random.randint(100, 300),
"trxDateEndId": int(end_date.strftime("%Y%m%d")),
"trxDateStartId": int(start_date.strftime("%Y%m%d")),
"uploadFileName": f"测试文件_{log_id}.pdf",
"uploadStatusDesc": "data.wait.confirm.newaccount"
}
```
#### 3.2.2 重构方法: get_upload_status()
```python
def get_upload_status(self, group_id: int, log_id: int = None) -> dict:
"""
获取文件上传状态(基于 logId 生成确定性数据)
Args:
group_id: 项目ID
log_id: 文件ID可选
Returns:
上传状态响应字典
"""
logs = []
if log_id:
# 使用 logId 作为随机种子,确保相同 logId 返回相同数据
random.seed(log_id)
# 生成确定性的文件记录
record = self._generate_deterministic_record(log_id, group_id)
logs.append(record)
# 返回响应
return {
"code": "200",
"data": {
"logs": logs,
"status": "",
"accountId": 8954,
"currency": "CNY"
},
"status": "200",
"successResponse": True
}
```
### 3.3 测试设计
#### 3.3.1 测试场景
1. **带 logId 查询**: 验证返回非空 logs 数组
2. **不带 logId 查询**: 验证返回空 logs 数组
3. **确定性测试**: 相同 logId 多次调用返回相同数据
4. **字段完整性**: 验证返回的 26 个字段都存在
5. **字段值正确性**: 验证 status=-5, logMeta 格式正确
6. **银行类型随机性**: 验证不同 logId 生成不同银行类型
#### 3.3.2 测试用例示例
```python
def test_get_upload_status_with_log_id():
"""测试带 logId 参数查询"""
response = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
assert response.status_code == 200
data = response.json()
assert data["code"] == "200"
assert len(data["data"]["logs"]) == 1
assert data["data"]["logs"][0]["logId"] == 13994
assert data["data"]["logs"][0]["status"] == -5
assert data["data"]["logs"][0]["logMeta"] == "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}"
def test_get_upload_status_without_log_id():
"""测试不带 logId 参数查询"""
response = client.get("/watson/api/project/bs/upload?groupId=1000")
assert response.status_code == 200
data = response.json()
assert data["code"] == "200"
assert len(data["data"]["logs"]) == 0
def test_deterministic_data():
"""测试相同 logId 返回相同数据"""
response1 = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
response2 = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
log1 = response1.json()["data"]["logs"][0]
log2 = response2.json()["data"]["logs"][0]
# 验证关键字段相同(除了 fileUploadTime
assert log1["logId"] == log2["logId"]
assert log1["bankName"] == log2["bankName"]
assert log1["accountNoList"] == log2["accountNoList"]
assert log1["enterpriseNameList"] == log2["enterpriseNameList"]
```
## 4. 实施要点
### 4.1 实施步骤
1. **修复 FileRecord 类**:修改 `log_meta` 默认值为正确的字符串格式
2. **重构 get_upload_status() 方法**:移除对 `self.file_records` 的依赖
3. **新增 _generate_deterministic_record() 方法**:实现确定性数据生成
4. **更新单元测试**:添加新的测试用例验证功能
5. **运行测试验证**:确保所有测试通过
### 4.2 注意事项
1. **随机种子**: 必须在生成数据前调用 `random.seed(log_id)`
2. **时间字段**: `fileUploadTime` 使用当前时间,每次调用会不同
3. **兼容性**: 不影响其他接口(上传、解析状态检查等)
4. **性能**: 无需优化,当前方案已足够高效
### 4.3 风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 与上传接口数据不一致 | 低 | Mock 服务器允许独立数据源 |
| 随机种子冲突 | 极低 | logId 范围足够大10000+ |
| 字段缺失 | 中 | 严格按文档生成 26 个字段 |
## 5. 验收标准
### 5.1 功能验收
- [ ] 带 logId 参数查询返回非空 logs 数组
- [ ] 不带 logId 参数查询返回空 logs 数组
- [ ] 相同 logId 多次查询返回相同的核心字段值
- [ ] 返回数据包含完整的 26 个字段
- [ ] status 字段值为 -5
- [ ] logMeta 字段中 balanceAmount 为字符串 "-1"
### 5.2 质量验收
- [ ] 所有单元测试通过
- [ ] 代码符合项目编码规范
- [ ] 无语法错误和运行时错误
- [ ] API 文档Swagger UI正确展示接口
### 5.3 文档验收
- [ ] CLAUDE.md 更新(如有必要)
- [ ] 代码注释完整清晰
- [ ] 测试用例覆盖所有场景
## 6. 后续优化建议
### 6.1 可选增强
1. **缓存机制**: 如需提高性能,可基于 logId 缓存生成结果
2. **更多银行类型**: 扩展银行类型和模板选项
3. **异常场景**: 支持通过特殊 logId 触发错误响应
### 6.2 不建议的优化
1. **关联上传记录**: 会增加复杂度,违背 Mock 服务器独立原则
2. **预生成数据池**: 过度设计,当前场景不需要
## 7. 参考资料
- 接口文档: `assets/兰溪-流水分析对接3.md` 第374-516行
- 当前实现: `services/file_service.py` 第265-300行
- FileRecord 模型: `services/file_service.py` 第12-59行