diff --git a/lsfx-mock-server/models/__init__.py b/lsfx-mock-server/models/__init__.py new file mode 100644 index 0000000..f3d9f4b --- /dev/null +++ b/lsfx-mock-server/models/__init__.py @@ -0,0 +1 @@ +# Models package diff --git a/lsfx-mock-server/models/request.py b/lsfx-mock-server/models/request.py new file mode 100644 index 0000000..3b0be3d --- /dev/null +++ b/lsfx-mock-server/models/request.py @@ -0,0 +1,50 @@ +from pydantic import BaseModel, Field +from typing import Optional, List + + +class GetTokenRequest(BaseModel): + """获取Token请求模型""" + projectNo: str = Field(..., description="项目编号,格式:902000_当前时间戳") + entityName: str = Field(..., description="项目名称") + userId: str = Field(..., description="操作人员编号,固定值") + userName: str = Field(..., description="操作人员姓名,固定值") + orgCode: str = Field(..., description="行社机构号,固定值") + entityId: Optional[str] = Field(None, description="企业统信码或个人身份证号") + xdRelatedPersons: Optional[str] = Field(None, description="信贷关联人信息") + jzDataDateId: Optional[str] = Field("0", description="拉取指定日期推送过来的金综链流水") + innerBSStartDateId: Optional[str] = Field("0", description="拉取行内流水开始日期") + innerBSEndDateId: Optional[str] = Field("0", description="拉取行内流水结束日期") + analysisType: Optional[int] = Field(-1, description="分析类型") + departmentCode: Optional[str] = Field(None, description="客户经理所属营业部/分理处的机构编码") + + +class FetchInnerFlowRequest(BaseModel): + """拉取行内流水请求模型""" + groupId: int = Field(..., description="项目id") + customerNo: str = Field(..., description="客户身份证号") + dataChannelCode: str = Field(..., description="校验码") + requestDateId: int = Field(..., description="发起请求的时间") + dataStartDateId: int = Field(..., description="拉取开始日期") + dataEndDateId: int = Field(..., description="拉取结束日期") + uploadUserId: int = Field(..., description="柜员号") + + +class CheckParseStatusRequest(BaseModel): + """检查文件解析状态请求模型""" + groupId: int = Field(..., description="项目id") + inprogressList: str = Field(..., description="文件id列表,逗号分隔") + + +class GetBankStatementRequest(BaseModel): + """获取银行流水请求模型""" + groupId: int = Field(..., description="项目id") + logId: int = Field(..., description="文件id") + pageNow: int = Field(..., description="当前页码") + pageSize: int = Field(..., description="查询条数") + + +class DeleteFilesRequest(BaseModel): + """删除文件请求模型""" + groupId: int = Field(..., description="项目id") + logIds: List[int] = Field(..., description="文件id数组") + userId: int = Field(..., description="用户柜员号") diff --git a/lsfx-mock-server/models/response.py b/lsfx-mock-server/models/response.py new file mode 100644 index 0000000..9efe0d2 --- /dev/null +++ b/lsfx-mock-server/models/response.py @@ -0,0 +1,187 @@ +from pydantic import BaseModel, Field +from typing import Optional, List, Dict, Any + + +# ==================== Token相关模型 ==================== + +class TokenData(BaseModel): + """Token数据""" + token: str = Field(..., description="token") + projectId: int = Field(..., description="见知项目Id") + projectNo: str = Field(..., description="项目编号") + entityName: str = Field(..., description="项目名称") + analysisType: int = Field(0, description="分析类型") + + +class GetTokenResponse(BaseModel): + """获取Token响应""" + code: str = Field("200", description="返回码") + data: Optional[TokenData] = Field(None, description="返回数据") + message: str = Field("create.token.success", description="返回消息") + status: str = Field("200", description="状态") + successResponse: bool = Field(True, description="是否成功响应") + + +# ==================== 文件上传相关模型 ==================== + +class AccountInfo(BaseModel): + """账户信息""" + bank: str = Field(..., description="银行") + accountName: str = Field(..., description="账户名称") + accountNo: str = Field(..., description="账号") + currency: str = Field(..., description="币种") + + +class UploadLogItem(BaseModel): + """上传日志项""" + accountNoList: List[str] = Field(default=[], description="账号列表") + bankName: str = Field(..., description="银行名称") + dataTypeInfo: List[str] = Field(default=[], description="数据类型信息") + downloadFileName: str = Field(..., description="下载文件名") + enterpriseNameList: List[str] = Field(default=[], description="企业名称列表") + filePackageId: str = Field(..., description="文件包ID") + fileSize: int = Field(..., description="文件大小") + fileUploadBy: int = Field(..., description="上传者ID") + fileUploadByUserName: str = Field(..., description="上传者用户名") + fileUploadTime: str = Field(..., description="上传时间") + leId: int = Field(..., description="企业ID") + logId: int = Field(..., description="日志ID") + logMeta: str = Field(..., description="日志元数据") + logType: str = Field(..., description="日志类型") + loginLeId: int = Field(..., description="登录企业ID") + realBankName: str = Field(..., description="真实银行名称") + rows: int = Field(0, description="行数") + source: str = Field(..., description="来源") + status: int = Field(-5, description="状态值") + templateName: str = Field(..., description="模板名称") + totalRecords: int = Field(0, description="总记录数") + trxDateEndId: int = Field(..., description="交易结束日期ID") + trxDateStartId: int = Field(..., description="交易开始日期ID") + uploadFileName: str = Field(..., description="上传文件名") + uploadStatusDesc: str = Field(..., description="上传状态描述") + + +class UploadFileResponse(BaseModel): + """上传文件响应""" + code: str = Field("200", description="返回码") + data: Optional[Dict[str, Any]] = Field(None, description="返回数据") + status: str = Field("200", description="状态") + successResponse: bool = Field(True, description="是否成功响应") + + +# ==================== 检查解析状态相关模型 ==================== + +class PendingItem(BaseModel): + """待处理项""" + accountNoList: List[str] = Field(default=[], description="账号列表") + bankName: str = Field(..., description="银行名称") + dataTypeInfo: List[str] = Field(default=[], description="数据类型信息") + downloadFileName: str = Field(..., description="下载文件名") + enterpriseNameList: List[str] = Field(default=[], description="企业名称列表") + filePackageId: str = Field(..., description="文件包ID") + fileSize: int = Field(..., description="文件大小") + fileUploadBy: int = Field(..., description="上传者ID") + fileUploadByUserName: str = Field(..., description="上传者用户名") + fileUploadTime: str = Field(..., description="上传时间") + isSplit: int = Field(0, description="是否分割") + leId: int = Field(..., description="企业ID") + logId: int = Field(..., description="日志ID") + logMeta: str = Field(..., description="日志元数据") + logType: str = Field(..., description="日志类型") + loginLeId: int = Field(..., description="登录企业ID") + lostHeader: List[str] = Field(default=[], description="丢失的头部") + realBankName: str = Field(..., description="真实银行名称") + rows: int = Field(0, description="行数") + source: str = Field(..., description="来源") + status: int = Field(-5, description="状态值") + templateName: str = Field(..., description="模板名称") + totalRecords: int = Field(0, description="总记录数") + trxDateEndId: int = Field(..., description="交易结束日期ID") + trxDateStartId: int = Field(..., description="交易开始日期ID") + uploadFileName: str = Field(..., description="上传文件名") + uploadStatusDesc: str = Field(..., description="上传状态描述") + + +class CheckParseStatusResponse(BaseModel): + """检查解析状态响应""" + code: str = Field("200", description="返回码") + data: Optional[Dict[str, Any]] = Field(None, description="返回数据,包含parsing和pendingList") + status: str = Field("200", description="状态") + successResponse: bool = Field(True, description="是否成功响应") + + +# ==================== 银行流水相关模型 ==================== + +class BankStatementItem(BaseModel): + """银行流水项""" + accountId: int = Field(0, description="账号ID") + accountMaskNo: str = Field(..., description="账号") + accountingDate: str = Field(..., description="记账日期") + accountingDateId: int = Field(..., description="记账日期ID") + archivingFlag: int = Field(0, description="归档标志") + attachments: int = Field(0, description="附件数") + balanceAmount: float = Field(..., description="余额") + bank: str = Field(..., description="银行") + bankComments: str = Field("", description="银行注释") + bankStatementId: int = Field(..., description="流水ID") + bankTrxNumber: str = Field(..., description="银行交易号") + batchId: int = Field(..., description="批次ID") + cashType: str = Field("1", description="现金类型") + commentsNum: int = Field(0, description="评论数") + crAmount: float = Field(0, description="贷方金额") + cretNo: str = Field(..., description="证件号") + currency: str = Field("CNY", description="币种") + customerAccountMaskNo: str = Field(..., description="客户账号") + customerBank: str = Field("", description="客户银行") + customerId: int = Field(-1, description="客户ID") + customerName: str = Field(..., description="客户名称") + customerReference: str = Field("", description="客户参考") + downPaymentFlag: int = Field(0, description="首付标志") + drAmount: float = Field(0, description="借方金额") + exceptionType: str = Field("", description="异常类型") + groupId: int = Field(0, description="项目ID") + internalFlag: int = Field(0, description="内部标志") + leId: int = Field(..., description="企业ID") + leName: str = Field(..., description="企业名称") + overrideBsId: int = Field(0, description="覆盖流水ID") + paymentMethod: str = Field("", description="支付方式") + sourceCatalogId: int = Field(0, description="来源目录ID") + split: int = Field(0, description="分割") + subBankstatementId: int = Field(0, description="子流水ID") + toDoFlag: int = Field(0, description="待办标志") + transAmount: float = Field(..., description="交易金额") + transFlag: str = Field("P", description="交易标志") + transTypeId: int = Field(0, description="交易类型ID") + transformAmount: int = Field(0, description="转换金额") + transformCrAmount: int = Field(0, description="转换贷方金额") + transformDrAmount: int = Field(0, description="转换借方金额") + transfromBalanceAmount: int = Field(0, description="转换余额") + trxBalance: int = Field(0, description="交易余额") + trxDate: str = Field(..., description="交易日期") + userMemo: str = Field(..., description="用户备注") + + +class GetBankStatementResponse(BaseModel): + """获取银行流水响应""" + code: str = Field("200", description="返回码") + data: Optional[Dict[str, Any]] = Field(None, description="返回数据,包含bankStatementList和totalCount") + status: str = Field("200", description="状态") + successResponse: bool = Field(True, description="是否成功响应") + + +# ==================== 其他响应模型 ==================== + +class FetchInnerFlowResponse(BaseModel): + """拉取行内流水响应""" + code: str = Field("200", description="返回码") + data: Optional[Dict[str, Any]] = Field(None, description="返回数据") + status: str = Field("200", description="状态") + successResponse: bool = Field(True, description="是否成功响应") + + +class DeleteFilesResponse(BaseModel): + """删除文件响应""" + code: str = Field("200", description="返回码") + data: Optional[Dict[str, str]] = Field(None, description="返回数据") + status: str = Field("200", description="状态") + successResponse: bool = Field(True, description="是否成功响应") diff --git a/lsfx-mock-server/utils/__init__.py b/lsfx-mock-server/utils/__init__.py new file mode 100644 index 0000000..dd7ee44 --- /dev/null +++ b/lsfx-mock-server/utils/__init__.py @@ -0,0 +1 @@ +# Utils package diff --git a/lsfx-mock-server/utils/__pycache__/__init__.cpython-313.pyc b/lsfx-mock-server/utils/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..7eee897 Binary files /dev/null and b/lsfx-mock-server/utils/__pycache__/__init__.cpython-313.pyc differ diff --git a/lsfx-mock-server/utils/__pycache__/error_simulator.cpython-313.pyc b/lsfx-mock-server/utils/__pycache__/error_simulator.cpython-313.pyc new file mode 100644 index 0000000..71739c6 Binary files /dev/null and b/lsfx-mock-server/utils/__pycache__/error_simulator.cpython-313.pyc differ diff --git a/lsfx-mock-server/utils/__pycache__/response_builder.cpython-313.pyc b/lsfx-mock-server/utils/__pycache__/response_builder.cpython-313.pyc new file mode 100644 index 0000000..6b7db9a Binary files /dev/null and b/lsfx-mock-server/utils/__pycache__/response_builder.cpython-313.pyc differ diff --git a/lsfx-mock-server/utils/error_simulator.py b/lsfx-mock-server/utils/error_simulator.py new file mode 100644 index 0000000..b5b2b94 --- /dev/null +++ b/lsfx-mock-server/utils/error_simulator.py @@ -0,0 +1,49 @@ +from typing import Dict, Optional +import re + + +class ErrorSimulator: + """错误场景模拟器""" + + # 错误码映射表 + ERROR_CODES = { + "40101": {"code": "40101", "message": "appId错误"}, + "40102": {"code": "40102", "message": "appSecretCode错误"}, + "40104": {"code": "40104", "message": "可使用项目次数为0,无法创建项目"}, + "40105": {"code": "40105", "message": "只读模式下无法新建项目"}, + "40106": {"code": "40106", "message": "错误的分析类型,不在规定的取值范围内"}, + "40107": {"code": "40107", "message": "当前系统不支持的分析类型"}, + "40108": {"code": "40108", "message": "当前用户所属行社无权限"}, + "501014": {"code": "501014", "message": "无行内流水文件"}, + } + + @staticmethod + def detect_error_marker(value: str) -> Optional[str]: + """检测字符串中的错误标记 + + 规则:如果字符串包含 error_XXXX,则返回 XXXX + 例如: + - "project_error_40101" -> "40101" + - "test_error_501014" -> "501014" + """ + if not value: + return None + + pattern = r'error_(\d+)' + match = re.search(pattern, value) + if match: + return match.group(1) + return None + + @staticmethod + def build_error_response(error_code: str) -> Optional[Dict]: + """构建错误响应""" + if error_code in ErrorSimulator.ERROR_CODES: + error_info = ErrorSimulator.ERROR_CODES[error_code] + return { + "code": error_info["code"], + "message": error_info["message"], + "status": error_info["code"], + "successResponse": False + } + return None diff --git a/lsfx-mock-server/utils/response_builder.py b/lsfx-mock-server/utils/response_builder.py new file mode 100644 index 0000000..50e50d0 --- /dev/null +++ b/lsfx-mock-server/utils/response_builder.py @@ -0,0 +1,69 @@ +import json +from pathlib import Path +from typing import Dict, Any +import copy + + +class ResponseBuilder: + """响应构建器""" + + TEMPLATE_DIR = Path(__file__).parent.parent / "config" / "responses" + + @staticmethod + def load_template(template_name: str) -> Dict: + """加载 JSON 模板 + + Args: + template_name: 模板名称(不含.json扩展名) + + Returns: + 模板字典 + """ + file_path = ResponseBuilder.TEMPLATE_DIR / f"{template_name}.json" + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + + @staticmethod + def replace_placeholders(template: Dict, **kwargs) -> Dict: + """递归替换占位符 + + Args: + template: 模板字典 + **kwargs: 占位符键值对 + + Returns: + 替换后的字典 + """ + def replace_value(value): + if isinstance(value, str): + result = value + for key, val in kwargs.items(): + placeholder = f"{{{key}}}" + if placeholder in result: + result = result.replace(placeholder, str(val)) + return result + elif isinstance(value, dict): + return {k: replace_value(v) for k, v in value.items()} + elif isinstance(value, list): + return [replace_value(item) for item in value] + return value + + # 深拷贝模板,避免修改原始数据 + return replace_value(copy.deepcopy(template)) + + @staticmethod + def build_success_response(template_name: str, **kwargs) -> Dict: + """构建成功响应 + + Args: + template_name: 模板名称 + **kwargs: 占位符键值对 + + Returns: + 响应字典 + """ + template = ResponseBuilder.load_template(template_name) + return ResponseBuilder.replace_placeholders( + template["success_response"], + **kwargs + )