# 获取单个文件上传状态接口优化设计 ## 文档信息 - **创建日期**: 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 默认值
2. 重构 get_upload_status() 方法
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行