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

12 KiB
Raw Blame History

获取单个文件上传状态接口优化设计

文档信息

  • 创建日期: 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 的确定性随机生成

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 测试场景

  1. 带 logId 查询: 验证返回非空 logs 数组
  2. 不带 logId 查询: 验证返回空 logs 数组
  3. 确定性测试: 相同 logId 多次调用返回相同数据
  4. 字段完整性: 验证返回的 26 个字段都存在
  5. 字段值正确性: 验证 status=-5, logMeta 格式正确
  6. 银行类型随机性: 验证不同 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 实施步骤

  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行