12 KiB
12 KiB
获取单个文件上传状态接口优化设计
文档信息
- 创建日期: 2026-03-12
- 设计者: Claude Code
- 状态: 待实施
1. 需求背景
1.1 接口信息
- 接口路径:
/watson/api/project/bs/upload(GET) - 接口名称: 获取单个文件上传后的状态
- 项目背景: 流水分析 Mock 服务器
1.2 当前问题
当前实现存在以下问题:
- 依赖实际上传记录: 接口依赖
self.file_records(上传时存储的记录),如果没有上传过文件,logs 返回空数组 - 不符合 Mock 服务器定位: Mock 服务器应该独立工作,前端测试时不应依赖其他接口
- 字段值不正确:
logMeta字段中的balanceAmount值为布尔值true,应该为字符串"-1"
1.3 期望行为
根据接口文档(assets/兰溪-流水分析对接3.md 第374-516行):
- 带 logId 参数: 根据 logId 生成固定的文件记录数据(相同 logId 返回相同数据)
- 不带 logId 参数: 返回空的 logs 数组
- 固定成功状态: status=-5, uploadStatusDesc="data.wait.confirm.newaccount"
- 独立性: 不依赖实际上传的文件记录,接口独立工作
2. 解决方案
2.1 设计原则
- 确定性随机: 使用
random.seed(log_id)确保相同 logId 生成相同数据 - 完全独立: 不依赖
self.file_records,在get_upload_status()中直接生成数据 - 文档对齐: 严格遵循接口文档示例的字段和格式
- 简单高效: 代码简洁,易于维护和测试
2.2 核心设计
2.2.1 数据生成策略
基于 logId 的确定性随机生成
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):
log_meta: str = "{\"lostHeader\":[],\"balanceAmount\":true}" # ❌ 错误
修复后:
log_meta: str = "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}" # ✅ 正确
修复2: 独立数据生成
当前实现: 依赖 self.file_records
修复后: 在 get_upload_status() 中独立生成数据,不依赖上传记录
3. 技术设计
3.1 修改文件清单
| 文件 | 修改内容 |
|---|---|
services/file_service.py |
1. 修复 FileRecord.log_meta 默认值 2. 重构 get_upload_status() 方法 3. 新增 _generate_deterministic_record() 方法 |
3.2 核心代码实现
3.2.1 新增方法: _generate_deterministic_record()
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()
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 测试场景
- 带 logId 查询: 验证返回非空 logs 数组
- 不带 logId 查询: 验证返回空 logs 数组
- 确定性测试: 相同 logId 多次调用返回相同数据
- 字段完整性: 验证返回的 26 个字段都存在
- 字段值正确性: 验证 status=-5, logMeta 格式正确
- 银行类型随机性: 验证不同 logId 生成不同银行类型
3.3.2 测试用例示例
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 实施步骤
- 修复 FileRecord 类:修改
log_meta默认值为正确的字符串格式 - 重构 get_upload_status() 方法:移除对
self.file_records的依赖 - 新增 _generate_deterministic_record() 方法:实现确定性数据生成
- 更新单元测试:添加新的测试用例验证功能
- 运行测试验证:确保所有测试通过
4.2 注意事项
- 随机种子: 必须在生成数据前调用
random.seed(log_id) - 时间字段:
fileUploadTime使用当前时间,每次调用会不同 - 兼容性: 不影响其他接口(上传、解析状态检查等)
- 性能: 无需优化,当前方案已足够高效
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 可选增强
- 缓存机制: 如需提高性能,可基于 logId 缓存生成结果
- 更多银行类型: 扩展银行类型和模板选项
- 异常场景: 支持通过特殊 logId 触发错误响应
6.2 不建议的优化
- 关联上传记录: 会增加复杂度,违背 Mock 服务器独立原则
- 预生成数据池: 过度设计,当前场景不需要
7. 参考资料
- 接口文档:
assets/兰溪-流水分析对接3.md第374-516行 - 当前实现:
services/file_service.py第265-300行 - FileRecord 模型:
services/file_service.py第12-59行