From aa17a14c4e9503fc3f8bcd4cd8a77a38b5ef7f8a Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Tue, 3 Mar 2026 08:59:26 +0800 Subject: [PATCH 1/8] feat(mock): initialize project structure and configuration --- .../__pycache__/settings.cpython-313.pyc | Bin 0 -> 1315 bytes .../config/responses/bank_statement.json | 106 ++++++++++++++++++ .../config/responses/parse_status.json | 41 +++++++ lsfx-mock-server/config/responses/token.json | 15 +++ lsfx-mock-server/config/responses/upload.json | 49 ++++++++ lsfx-mock-server/config/settings.py | 30 +++++ lsfx-mock-server/requirements.txt | 7 ++ 7 files changed, 248 insertions(+) create mode 100644 lsfx-mock-server/config/__pycache__/settings.cpython-313.pyc create mode 100644 lsfx-mock-server/config/responses/bank_statement.json create mode 100644 lsfx-mock-server/config/responses/parse_status.json create mode 100644 lsfx-mock-server/config/responses/token.json create mode 100644 lsfx-mock-server/config/responses/upload.json create mode 100644 lsfx-mock-server/config/settings.py create mode 100644 lsfx-mock-server/requirements.txt diff --git a/lsfx-mock-server/config/__pycache__/settings.cpython-313.pyc b/lsfx-mock-server/config/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65b851489b86ee5ee32c84483ec050aa1c00a6ca GIT binary patch literal 1315 zcmZWp-D@LN6u;BlB-1u&nkG$~y0&S%q7fU`)q*S{n{CG3^dkwKC@KS&>||PoO=s$v z$!d!a*@s<&ve1`>eNfRCQ4n##eOmk<7V;7=?31)>-d0-h)pKtWW${9ObAIQXJLhw9 z8jX$-Jdumv+h30m@`oV96C4Pqmr=M!bfPO8WL*IzBjk~dk@WxsGGvx45j`+RbZRK8 zf&Lw!23;bneL^6(8_b4!f@KvRxl;2i-SU0g+4YdC<$d3Fom#^jxXT9}kkg+~xJL{E zicVleS0L~b1_!DKAZUb)uufk_4?PH~WFg7IFlvmUO_gjE#-(jcvT=wQQM5%Qi^7Ct z6OzRsCfTH9ahQ~BO0opRB})n>i=M`!Q>)lEabQZHfyC}?HqF&xbv@C-W(?u@_7BGo zTD{wMpZ@mq(+7{5Db$@jymj)+ugC2>C*K@xx#o?NukIdy@f}z5l?p56x3t6H>$wZL z3w&HC-q*JEV!2f1!Gd<>y{m^|v`8zxR@STb;^SA2(}_fhES+jt!pQupP^B(X8TIP}Q(_d?+!?G2J>Y7@ua$ z^J~6svYPLMeZA>h9%C>q62~9mdf}2`nsvJmIkQn~)-B@`7jF1qS)S4Gc0OI%!||+m z7TmPp{|Zx&g~!YP`^w_U<6UB7e%;3x`}jK?VelisA+f+HGoqiGM7>|31TN; zM!>89agT=?tGgy9tO^$JeBfAr&2d~&lcvXlMvxb3G5?bMiY)KMRrRmDaVk*`cp>XXaZQM~M!d?@py!TKmlo zO`$zL*DAD^J2c&$O!lQDYBP(E>CAm}&UDj@t*xUsI&=}$3(Jpb>VBX@m%4Kct_$8V&7%nT>aNUjQ78)xcYW~ um)?O_&>$Aq%OE@pD2nogy!wQ^a284_=i8s15q$i0kwlVTUb>~qH~a(kms4N> literal 0 HcmV?d00001 diff --git a/lsfx-mock-server/config/responses/bank_statement.json b/lsfx-mock-server/config/responses/bank_statement.json new file mode 100644 index 0000000..27549c7 --- /dev/null +++ b/lsfx-mock-server/config/responses/bank_statement.json @@ -0,0 +1,106 @@ +{ + "success_response": { + "code": "200", + "data": { + "bankStatementList": [ + { + "accountId": 0, + "accountMaskNo": "101015251071645", + "accountingDate": "2024-02-01", + "accountingDateId": 20240201, + "archivingFlag": 0, + "attachments": 0, + "balanceAmount": 4814.82, + "bank": "ZJRCU", + "bankComments": "", + "bankStatementId": 12847662, + "bankTrxNumber": "1a10458dd5c3366d7272285812d434fc", + "batchId": 19135, + "cashType": "1", + "commentsNum": 0, + "crAmount": 0, + "cretNo": "230902199012261247", + "currency": "CNY", + "customerAccountMaskNo": "597671502", + "customerBank": "", + "customerId": -1, + "customerName": "小店", + "customerReference": "", + "downPaymentFlag": 0, + "drAmount": 245.8, + "exceptionType": "", + "groupId": 16238, + "internalFlag": 0, + "leId": 16308, + "leName": "张传伟", + "overrideBsId": 0, + "paymentMethod": "", + "sourceCatalogId": 0, + "split": 0, + "subBankstatementId": 0, + "toDoFlag": 0, + "transAmount": 245.8, + "transFlag": "P", + "transTypeId": 0, + "transformAmount": 0, + "transformCrAmount": 0, + "transformDrAmount": 0, + "transfromBalanceAmount": 0, + "trxBalance": 0, + "trxDate": "2024-02-01 10:33:44", + "userMemo": "财付通消费_小店" + }, + { + "accountId": 0, + "accountMaskNo": "101015251071645", + "accountingDate": "2024-02-02", + "accountingDateId": 20240202, + "archivingFlag": 0, + "attachments": 0, + "balanceAmount": 5000.00, + "bank": "ZJRCU", + "bankComments": "", + "bankStatementId": 12847663, + "bankTrxNumber": "2b20568ee6d4477e8383396923e545gd", + "batchId": 19135, + "cashType": "1", + "commentsNum": 0, + "crAmount": 185.18, + "cretNo": "230902199012261247", + "currency": "CNY", + "customerAccountMaskNo": "123456789", + "customerBank": "", + "customerId": -1, + "customerName": "支付宝", + "customerReference": "", + "downPaymentFlag": 0, + "drAmount": 0, + "exceptionType": "", + "groupId": 16238, + "internalFlag": 0, + "leId": 16308, + "leName": "张传伟", + "overrideBsId": 0, + "paymentMethod": "", + "sourceCatalogId": 0, + "split": 0, + "subBankstatementId": 0, + "toDoFlag": 0, + "transAmount": 185.18, + "transFlag": "R", + "transTypeId": 0, + "transformAmount": 0, + "transformCrAmount": 0, + "transformDrAmount": 0, + "transfromBalanceAmount": 0, + "trxBalance": 0, + "trxDate": "2024-02-02 14:22:18", + "userMemo": "支付宝转账_支付宝" + } + ], + "totalCount": 131 + }, + "status": "200", + "successResponse": true + } +} diff --git a/lsfx-mock-server/config/responses/parse_status.json b/lsfx-mock-server/config/responses/parse_status.json new file mode 100644 index 0000000..cecd238 --- /dev/null +++ b/lsfx-mock-server/config/responses/parse_status.json @@ -0,0 +1,41 @@ +{ + "success_response": { + "code": "200", + "data": { + "parsing": false, + "pendingList": [ + { + "accountNoList": [], + "bankName": "ZJRCU", + "dataTypeInfo": ["CSV", ","], + "downloadFileName": "230902199012261247_20260201_20260201_1772096608615.csv", + "enterpriseNameList": [], + "filePackageId": "cde6c7cf5cab48e8892f0c1c36b2aa7d", + "fileSize": 53101, + "fileUploadBy": 448, + "fileUploadByUserName": "admin@support.com", + "fileUploadTime": "2026-02-27 09:50:18", + "isSplit": 0, + "leId": 16210, + "logId": "{log_id}", + "logMeta": "{\"lostHeader\":[],\"balanceAmount\":true}", + "logType": "bankstatement", + "loginLeId": 16210, + "lostHeader": [], + "realBankName": "ZJRCU", + "rows": 0, + "source": "http", + "status": -5, + "templateName": "ZJRCU_T251114", + "totalRecords": 131, + "trxDateEndId": 20240228, + "trxDateStartId": 20240201, + "uploadFileName": "230902199012261247_20260201_20260201_1772096608615.csv", + "uploadStatusDesc": "data.wait.confirm.newaccount" + } + ] + }, + "status": "200", + "successResponse": true + } +} diff --git a/lsfx-mock-server/config/responses/token.json b/lsfx-mock-server/config/responses/token.json new file mode 100644 index 0000000..a655c67 --- /dev/null +++ b/lsfx-mock-server/config/responses/token.json @@ -0,0 +1,15 @@ +{ + "success_response": { + "code": "200", + "data": { + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.mock_token_{project_id}", + "projectId": "{project_id}", + "projectNo": "{project_no}", + "entityName": "{entity_name}", + "analysisType": 0 + }, + "message": "create.token.success", + "status": "200", + "successResponse": true + } +} diff --git a/lsfx-mock-server/config/responses/upload.json b/lsfx-mock-server/config/responses/upload.json new file mode 100644 index 0000000..47820ac --- /dev/null +++ b/lsfx-mock-server/config/responses/upload.json @@ -0,0 +1,49 @@ +{ + "success_response": { + "code": "200", + "data": { + "accountsOfLog": { + "{log_id}": [ + { + "bank": "BSX", + "accountName": "测试账户", + "accountNo": "6222021234567890", + "currency": "CNY" + } + ] + }, + "uploadLogList": [ + { + "accountNoList": [], + "bankName": "BSX", + "dataTypeInfo": ["CSV", ","], + "downloadFileName": "测试流水.csv", + "enterpriseNameList": [], + "filePackageId": "14b13103010e4d32b5406c764cfe3644", + "fileSize": 46724, + "fileUploadBy": 448, + "fileUploadByUserName": "admin@support.com", + "fileUploadTime": "{upload_time}", + "leId": 10724, + "logId": "{log_id}", + "logMeta": "{\"lostHeader\":[],\"balanceAmount\":true}", + "logType": "bankstatement", + "loginLeId": 10724, + "realBankName": "BSX", + "rows": 0, + "source": "http", + "status": -5, + "templateName": "BSX_T240925", + "totalRecords": 280, + "trxDateEndId": 20240905, + "trxDateStartId": 20230914, + "uploadFileName": "测试流水.csv", + "uploadStatusDesc": "data.wait.confirm.newaccount" + } + ], + "uploadStatus": 1 + }, + "status": "200", + "successResponse": true + } +} diff --git a/lsfx-mock-server/config/settings.py b/lsfx-mock-server/config/settings.py new file mode 100644 index 0000000..7fb2d3b --- /dev/null +++ b/lsfx-mock-server/config/settings.py @@ -0,0 +1,30 @@ +from pydantic_settings import BaseSettings +from typing import Optional + + +class Settings(BaseSettings): + """全局配置类""" + + # 应用配置 + APP_NAME: str = "流水分析Mock服务" + APP_VERSION: str = "1.0.0" + DEBUG: bool = True + + # 服务器配置 + HOST: str = "0.0.0.0" + PORT: int = 8000 + + # 模拟配置 + PARSE_DELAY_SECONDS: int = 4 # 文件解析延迟秒数 + MAX_FILE_SIZE: int = 10485760 # 10MB + + # 测试数据配置 + INITIAL_PROJECT_ID: int = 1000 + INITIAL_LOG_ID: int = 10000 + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + + +settings = Settings() diff --git a/lsfx-mock-server/requirements.txt b/lsfx-mock-server/requirements.txt new file mode 100644 index 0000000..ca3203e --- /dev/null +++ b/lsfx-mock-server/requirements.txt @@ -0,0 +1,7 @@ +fastapi==0.104.1 +uvicorn[standard]==0.24.0 +pydantic==2.5.0 +python-multipart==0.0.6 +pytest>=7.0.0 +pytest-cov>=4.0.0 +httpx>=0.25.0 From e6bc2d64dd8077aa5e81ec9c4159eaa911a5163f Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Tue, 3 Mar 2026 09:02:33 +0800 Subject: [PATCH 2/8] feat(models,utils): implement data models and utility classes --- lsfx-mock-server/models/__init__.py | 1 + lsfx-mock-server/models/request.py | 50 +++++ lsfx-mock-server/models/response.py | 187 ++++++++++++++++++ lsfx-mock-server/utils/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 179 bytes .../error_simulator.cpython-313.pyc | Bin 0 -> 2241 bytes .../response_builder.cpython-313.pyc | Bin 0 -> 3249 bytes lsfx-mock-server/utils/error_simulator.py | 49 +++++ lsfx-mock-server/utils/response_builder.py | 69 +++++++ 9 files changed, 357 insertions(+) create mode 100644 lsfx-mock-server/models/__init__.py create mode 100644 lsfx-mock-server/models/request.py create mode 100644 lsfx-mock-server/models/response.py create mode 100644 lsfx-mock-server/utils/__init__.py create mode 100644 lsfx-mock-server/utils/__pycache__/__init__.cpython-313.pyc create mode 100644 lsfx-mock-server/utils/__pycache__/error_simulator.cpython-313.pyc create mode 100644 lsfx-mock-server/utils/__pycache__/response_builder.cpython-313.pyc create mode 100644 lsfx-mock-server/utils/error_simulator.py create mode 100644 lsfx-mock-server/utils/response_builder.py 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 0000000000000000000000000000000000000000..7eee897c4589868485fd6df20cefc55107b96fe8 GIT binary patch literal 179 zcmey&%ge<81V@dQWeNi6#~=<2FhUuhS%8eG4CxG-jD9N_ikN`B&mgH=K`vG?$;m01 z5JE3GC$TgoHKshjD7&O6HMKYgjEsy$%s>_ZkNq$v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..71739c6b58c8d46ebf9156d376667977c81aa3d9 GIT binary patch literal 2241 zcmah~>r)d~6u;TLLx6;rJRBPWR?|ub1*}u8{Q&fV9h|bIW2c$!#La@yyu7<>Yd>Lq z;eey9AR-i@?O1G8uxd*I^^Z-4&$;KE zdy~!2cOY1QZn_xuSrPi3d@L_hPRQ1SFo;;h8g`&I12(iVnDLNrW`_|Q%aIqc#!AGR zni%SVP2G&oyhJ{HjACjD^O9oQnUKQqSTNGgQxK(kbP%&&02xF9gbggh3~Rtfmcb_0 z$eLKQpJ6SmRp&O=u5$;Qr*kKpuk!-7Q0Fezt@9$bSmz~v8#V_lY-zyCmIZ79s~O@n zn%Gs9Xs@Y>=76vr5al_9BZsJX3X#WFVrO@iZ?#g;ig6sX;iw!5N^!hI;BqODrGeqa zsVQ}IRvVtuCMUGM3+nKsVs5P8Sie!u13)m5cr!#%q%3?qBq-LXAd10mf#NpF#T3U1 zJQk#8Ku|n2H_Co6tEZ+?x9+3|CYL6DOpjdEE>CEK)2W%+`iJxV+R(UmeNgQkQRil< znR+(K)w<_5i&Jxun>v3gHPc7I+S%z9;LY-80Mo$f^GDR)W7^p1^tCy4tPet@W=^W3 zlZ#&;QLmmS)>CKIp83W8!|KQl_1IAw?j^aFgu8V{J#jL9eJ(wB0lc(b0@OiT?Cgw@IF#A6^S_Ov6GN|LJ9~eB*aCC8;D{)5RAxzVu3o#IK~@5x4g*U8{wCgfDEEev_5AE z0jk@;GBLyZISoP`xuJio0KtoPxt1?OjOk>S{euv>lj$HMvtqHvrd$TK5W#1XuK+;% zvPZkwr(PLKU%r@{`7Skc1*%8;qKzM4ygKc0kUsPdKb$|WUi?HG8-0vb`;V%pzx4=Y zR=HiUpmP9RyfdJVjCqK{k-FUnPT=aO^LP_DzE|KSjymWw>-E%a_jsvDAd(<1+i4pp zASXN8KhC4h>jR+|eM{stKBK~5D6uF6F~3JKcjLI6@ELIt*c59bC`kg2DduQU;`fN8 z;2w`hd9UT=0MCcQl=%5bP!0)!594^BgattiM8vL->Y{OeU!5r60|NfHLYBf2F+h_M zm+z@RaZo7^3DT28MuT{tfE91+Q}@~b3`_~6CT;}MgZ?T)uF{3#RS$|hz1I7#q9OZd z_H*m*xqQ9mU-MiG#pOd=Kizt6|C#M~oz)A4#aVDdii74tF8ot-M&>}DGYkTmv;Ur| zt$+&Fp#MR2BC>7F&}m=|L;}v_xc*I^RKE$P%nvp~;-yZ9E69+a^lXW8hQpDG%X6%F_D8tzxD zqw;iHvf|a=wg=_j@y*Hd7Z$44j)#*~^$V4?( zqc{QN82Z`tT*ii~ypz$Sd)q=~^<95+(%qs9A0*wJF1(#|cj!V_(%r2K?-l7K%&``nedqvi*zs%j&;*>>t!e-XCu}XM^$XwXuY>%a^1)qB9gz>jL~2) V{D!K3LH55b8w@3XARu~-e*nOSznlO7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6b7db9a9e6a83794f2fe8b2e3112075287103ad7 GIT binary patch literal 3249 zcmb7G>r-3D72o@m&;x;m5!f}B33gCo9u44P$EKkM*9GIcT<~Oyk{emN!V2Pw>|Qyw zJDuS}+98>tL+voZPTJs1#wOF3^38T|?dC5)rY&Skr)eAE`crV#Y3hFJ*?V;ri+K82 z(AmAaXV0EJ=XcK8yw~eO(1f-tiKm3R1X!gjvt`bA3GKgKRAH z3wMaf*qq=NWRBDj)yoZo`@KyLp&9G5>bN+-TK+6 z#b;5e@R85JU>1cD<`jf^g~NivV=*k%qp;k;SzLqV1@4EsN6jfhlN*6rjACcFOp(Ga zMGm{$Ma8iVy(o4R_G1sMIm7P4T3N}Ow`8BIWUX9rW8bJJ_l;JemVsVx!30Z zdh=$mPx zW2k4Sci_aa?x9|_=Np4mOigGy71=RVOr|2SkbuE-h?d0@Nll#q8A->q@XIMYW?)St;UtNFr)fMD9cv;QKBM6;PNa=Ql7wvlsHZH&W~*8=;~DjK?^0V|wyJNcY|UFa6Pi;N zzH-OgmU(H-A9!Q``TcX>x^VC_0hPC}c-!FrUxeNEo$790_%-k9u5eJNtw9M08ITxf z{uq$wx##&=fYdPG4q*ym;T-3O`1=0>O@I}$VP*FXD8#4FfyjH4=W{pDufKU~efpi; z^!z6`r|UtZsg7wdqTAKtz6N&z(` z*JRP)XV=UCuDjrpBhXuI7>yL;&5i~;3L?EM@^o&ZSCxR~P(k2beqQeybf(Err z13xx;|YGiTaeD{jt1D#0K7qDofWsnuo6ch;W*{KOS$ibWa>(g zgiefz9@VHo3{0g2;F?ee)*#)&q!?qxNK7V(5pq%KOeC2$(=SOUp+=2qXQ=>l)NOK~ zPO-#JxiQL}p(2w+WeaoETM($45(v92!z62yleii_F*qI3r#FYQz3-s|Nb|vDDjG?W zu4a4Hl)4)PkY54$=OkM5)z0mH*T3T1o%QWrt*x7pSA%=5dMg}H%;D9BJ@aH< zTX<@I?@a${p#Haky{o$(zgl;>F5_NQZu>uYc6ry4OY(gO+SRh*L^X9Y-nGi@bG`4j zuT(Z>D;rlk58ZNKcQ1N>6F73i`@Wdr=D3T87AlvwKY6F-$?M)bfg?*5M@qJQ)Olz* z&~-z~h!?%{vEKoglnrz(RdlWTYo?Ap$U9KY*SQCO4s;Qgv_2HK9`?!~?%Q(MFS0j5 zGzF>88Pl{0HiGO6pgVQ0$ME|Jvl-1s^ zLlryT8hZP=OV2HIF7Iex_H`^dJMzGRN>V~G^SuBoCFQpmkZh0DKF@GaWbH{bXuqzC zGYH;H!;m|f@OG{h!nTLo2cZku@C-~@PlolWncQn{TaSoGr9$(`P|SyB;WP{D|B(+Z z;e|{{D@ZyT)d*4T@&xW+xd;as`7)3~b+r7$JY`o#%Rkj-Ia4=_p?fxa=+S)pn7~dT zP}HhX)nhCE)~vrZvt!x+)hXwiuWrTHcE{JY>Zw}V`gqpUcwZ1ITpyQL-It)>Kth=d zx54K3;L%w@ovLXbRi!Rf9Z$v5P|a29QPr2yktBO-!Iw`};|WZRWJ1^U6ihf&HI|C1 zDrT-_BFsmCO67?N)^r0ynu7-fq!l9(9oLN0sTke~Q<(Ypb0cAP54u%VEk-qB7&vh% zZD<4}v+J2^TDieAFC*+nrhxnaeI(ZY$sd?=Ug%o!H)j2fEB=pF literal 0 HcmV?d00001 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 + ) From 0d4fcd089b13b19a129d7da6971167166018875d Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Tue, 3 Mar 2026 09:26:07 +0800 Subject: [PATCH 3/8] feat(services): implement token, file, and statement services --- lsfx-mock-server/services/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 182 bytes .../__pycache__/token_service.cpython-313.pyc | Bin 0 -> 1980 bytes lsfx-mock-server/services/file_service.py | 151 ++++++++++++++++++ .../services/statement_service.py | 33 ++++ lsfx-mock-server/services/token_service.py | 49 ++++++ 6 files changed, 234 insertions(+) create mode 100644 lsfx-mock-server/services/__init__.py create mode 100644 lsfx-mock-server/services/__pycache__/__init__.cpython-313.pyc create mode 100644 lsfx-mock-server/services/__pycache__/token_service.cpython-313.pyc create mode 100644 lsfx-mock-server/services/file_service.py create mode 100644 lsfx-mock-server/services/statement_service.py create mode 100644 lsfx-mock-server/services/token_service.py diff --git a/lsfx-mock-server/services/__init__.py b/lsfx-mock-server/services/__init__.py new file mode 100644 index 0000000..a70b302 --- /dev/null +++ b/lsfx-mock-server/services/__init__.py @@ -0,0 +1 @@ +# Services package diff --git a/lsfx-mock-server/services/__pycache__/__init__.cpython-313.pyc b/lsfx-mock-server/services/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb801d9364e69ab8f48d532815baaf9d45f551b5 GIT binary patch literal 182 zcmey&%ge<81ThxNGDU#&V-N=h7@>^MEI`IohI9r^M!%H|MNB~6XOPsbP#3G1~AkI&4@EQycT jE2zB1VUwGmQks)$SHudm1>}li5aS~=BO_xGGmr%U`8hEB literal 0 HcmV?d00001 diff --git a/lsfx-mock-server/services/__pycache__/token_service.cpython-313.pyc b/lsfx-mock-server/services/__pycache__/token_service.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33682397bf953405157f9fa697ca00d053400325 GIT binary patch literal 1980 zcmZuy&2Jk;6rc63?e#jTL%!^!5lPxejguy-sund-L23ztBq&AR_(Cm>-1Wq4)KU)o3#0`ltcZjJPMVu(D)qpHH?wOuZ8DPIy!pMI z=lAh@yF2yu8iF-<>RaE0(l{EEsMN*vz<6Lxrn1oGab+w> zgF;uvLt|kY?m`K42fubpM^u4mdW1&vS~BX^iI8+cFPjG04uf0gB@hDLy1lY>bzzn#+{me6?ta*^^guK}Q>QK5z&<0pPotUQ!uBB{_IoZQCIiP6c?GjHQ} zQs>6rczqI&j<~T|TD(XMhK-_QF+$xC;{_D8u}NXd=0SD2?~V*-3?pZHV$djLogB%$ zU!>EF5@Kfx_S6RhGeu*1z=k{t-3xFW^0za>KlYD!v5&a z-OV3A*0hwUng5`$ls{i6-~Xcg!&l``?rGaR05`}uHIlNcRY_y!p6OQ6jS`EQYz|x5 z8R9CrEXyXtlt)Z?k=)h`d5$|J89w~ z!9A(fyEb<$l8bS?&Y&dA2o{05Z6XnW@CJ9AxI6nj5Z6$p8Aa;X)Q$&g$Fj7lcK;5% zYH3?*Nv^geOWKnVYCgQyH1wcpXuYm!J>I<@e|at5yBhCZPjs#&`c@Nt|3-q%T1l-m zpu_#&9bGwk>+-`xuauO<=zR3X*s9w3S1ewN(MI?)5&Ltzg1_ass+8Igd@5&39RP`898fD~+h3dGX5pmBp*`SHJ%7VXW_0wNF$C z{zK4(cYPvR$6pPcOYmyYFrF#qoC4SDFuvqu3%(_W@svqzRxmAM6~P?FI9D_|Zu22)0|fr+o9AaX%Buh record + self.parsing_status = {} # logId -> is_parsing + self.log_counter = settings.INITIAL_LOG_ID + + async def upload_file( + self, group_id: int, file: UploadFile, background_tasks: BackgroundTasks + ) -> Dict: + """上传文件并启动后台解析任务 + + Args: + group_id: 项目ID + file: 上传的文件 + background_tasks: FastAPI后台任务 + + Returns: + 上传响应字典 + """ + # 生成唯一logId + self.log_counter += 1 + log_id = self.log_counter + + # 获取当前时间 + upload_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # 立即存储文件记录(初始状态:解析中) + self.file_records[log_id] = { + "logId": log_id, + "groupId": group_id, + "status": -5, + "uploadStatusDesc": "parsing", + "uploadFileName": file.filename, + "fileSize": 0, # 简化处理 + "bankName": "MOCK", + "uploadTime": upload_time, + } + + # 标记为解析中 + self.parsing_status[log_id] = True + + # 启动后台任务,延迟解析 + background_tasks.add_task( + self._simulate_parsing, log_id, settings.PARSE_DELAY_SECONDS + ) + + # 构建响应 + response = ResponseBuilder.build_success_response( + "upload", log_id=log_id, upload_time=upload_time + ) + + return response + + def _simulate_parsing(self, log_id: int, delay_seconds: int): + """后台任务:模拟文件解析过程 + + Args: + log_id: 日志ID + delay_seconds: 延迟秒数 + """ + time.sleep(delay_seconds) + + # 解析完成,更新状态 + if log_id in self.file_records: + self.file_records[log_id]["uploadStatusDesc"] = ( + "data.wait.confirm.newaccount" + ) + self.parsing_status[log_id] = False + + def check_parse_status(self, group_id: int, inprogress_list: str) -> Dict: + """检查文件解析状态 + + Args: + group_id: 项目ID + inprogress_list: 文件ID列表(逗号分隔) + + Returns: + 解析状态响应字典 + """ + # 解析logId列表 + log_ids = [int(x.strip()) for x in inprogress_list.split(",") if x.strip()] + + # 检查是否还在解析中 + is_parsing = any( + self.parsing_status.get(log_id, False) for log_id in log_ids + ) + + # 获取待处理列表 + pending_list = [ + self.file_records[log_id] + for log_id in log_ids + if log_id in self.file_records + ] + + return { + "code": "200", + "data": {"parsing": is_parsing, "pendingList": pending_list}, + "status": "200", + "successResponse": True, + } + + def delete_files(self, group_id: int, log_ids: List[int], user_id: int) -> Dict: + """删除文件 + + Args: + group_id: 项目ID + log_ids: 文件ID列表 + user_id: 用户ID + + Returns: + 删除响应字典 + """ + # 删除文件记录 + for log_id in log_ids: + self.file_records.pop(log_id, None) + self.parsing_status.pop(log_id, None) + + return { + "code": "200", + "data": {"message": "delete.files.success"}, + "status": "200", + "successResponse": True, + } + + def fetch_inner_flow(self, request: FetchInnerFlowRequest) -> Dict: + """拉取行内流水(模拟无数据场景) + + Args: + request: 拉取流水请求 + + Returns: + 流水响应字典 + """ + # 模拟无行内流水文件场景 + return { + "code": "200", + "data": {"code": "501014", "message": "无行内流水文件"}, + "status": "200", + "successResponse": True, + } diff --git a/lsfx-mock-server/services/statement_service.py b/lsfx-mock-server/services/statement_service.py new file mode 100644 index 0000000..2b51231 --- /dev/null +++ b/lsfx-mock-server/services/statement_service.py @@ -0,0 +1,33 @@ +from models.request import GetBankStatementRequest +from utils.response_builder import ResponseBuilder +from typing import Dict + + +class StatementService: + """流水数据服务""" + + def get_bank_statement(self, request: GetBankStatementRequest) -> Dict: + """获取银行流水列表 + + Args: + request: 获取银行流水请求 + + Returns: + 银行流水响应字典 + """ + # 加载模板 + template = ResponseBuilder.load_template("bank_statement") + statements = template["success_response"]["data"]["bankStatementList"] + total_count = len(statements) + + # 模拟分页 + start = (request.pageNow - 1) * request.pageSize + end = start + request.pageSize + page_data = statements[start:end] + + return { + "code": "200", + "data": {"bankStatementList": page_data, "totalCount": total_count}, + "status": "200", + "successResponse": True, + } diff --git a/lsfx-mock-server/services/token_service.py b/lsfx-mock-server/services/token_service.py new file mode 100644 index 0000000..6149d60 --- /dev/null +++ b/lsfx-mock-server/services/token_service.py @@ -0,0 +1,49 @@ +from models.request import GetTokenRequest +from utils.response_builder import ResponseBuilder +from config.settings import settings +from typing import Dict + + +class TokenService: + """Token管理服务""" + + def __init__(self): + self.project_counter = settings.INITIAL_PROJECT_ID + self.tokens = {} # projectId -> token_data + + def create_token(self, request: GetTokenRequest) -> Dict: + """创建Token + + Args: + request: 获取Token请求 + + Returns: + Token响应字典 + """ + # 生成唯一项目ID + self.project_counter += 1 + project_id = self.project_counter + + # 构建响应 + response = ResponseBuilder.build_success_response( + "token", + project_id=project_id, + project_no=request.projectNo, + entity_name=request.entityName + ) + + # 存储token信息 + self.tokens[project_id] = response.get("data") + + return response + + def get_project(self, project_id: int) -> Dict: + """获取项目信息 + + Args: + project_id: 项目ID + + Returns: + 项目信息字典 + """ + return self.tokens.get(project_id) From 1bc65f9830ae5403b94f0c29f5b806d2da249c36 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Tue, 3 Mar 2026 09:27:50 +0800 Subject: [PATCH 4/8] feat(routers): implement all 6 API endpoints --- lsfx-mock-server/requirements.txt | 1 + lsfx-mock-server/routers/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 181 bytes .../routers/__pycache__/api.cpython-313.pyc | Bin 0 -> 4154 bytes lsfx-mock-server/routers/api.py | 99 ++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 lsfx-mock-server/routers/__init__.py create mode 100644 lsfx-mock-server/routers/__pycache__/__init__.cpython-313.pyc create mode 100644 lsfx-mock-server/routers/__pycache__/api.cpython-313.pyc create mode 100644 lsfx-mock-server/routers/api.py diff --git a/lsfx-mock-server/requirements.txt b/lsfx-mock-server/requirements.txt index ca3203e..75b7aa5 100644 --- a/lsfx-mock-server/requirements.txt +++ b/lsfx-mock-server/requirements.txt @@ -1,6 +1,7 @@ fastapi==0.104.1 uvicorn[standard]==0.24.0 pydantic==2.5.0 +pydantic-settings==2.1.0 python-multipart==0.0.6 pytest>=7.0.0 pytest-cov>=4.0.0 diff --git a/lsfx-mock-server/routers/__init__.py b/lsfx-mock-server/routers/__init__.py new file mode 100644 index 0000000..873f7bb --- /dev/null +++ b/lsfx-mock-server/routers/__init__.py @@ -0,0 +1 @@ +# Routers package diff --git a/lsfx-mock-server/routers/__pycache__/__init__.cpython-313.pyc b/lsfx-mock-server/routers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1ef67c6da37078bb5d45fab6a128470502c8081 GIT binary patch literal 181 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 Date: Tue, 3 Mar 2026 09:28:30 +0800 Subject: [PATCH 5/8] feat(main): implement FastAPI application entry point --- lsfx-mock-server/.env.example | 16 +++++++ lsfx-mock-server/main.py | 80 +++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 lsfx-mock-server/.env.example create mode 100644 lsfx-mock-server/main.py diff --git a/lsfx-mock-server/.env.example b/lsfx-mock-server/.env.example new file mode 100644 index 0000000..40f4d98 --- /dev/null +++ b/lsfx-mock-server/.env.example @@ -0,0 +1,16 @@ +# 应用配置 +APP_NAME=流水分析Mock服务 +APP_VERSION=1.0.0 +DEBUG=true + +# 服务器配置 +HOST=0.0.0.0 +PORT=8000 + +# 模拟配置 +PARSE_DELAY_SECONDS=4 +MAX_FILE_SIZE=10485760 + +# 初始ID配置 +INITIAL_PROJECT_ID=1000 +INITIAL_LOG_ID=10000 diff --git a/lsfx-mock-server/main.py b/lsfx-mock-server/main.py new file mode 100644 index 0000000..6aa9ac7 --- /dev/null +++ b/lsfx-mock-server/main.py @@ -0,0 +1,80 @@ +""" +流水分析Mock服务器 - 主应用入口 + +基于 FastAPI 实现的 Mock 服务器,用于模拟流水分析平台的 7 个核心接口 +""" +from fastapi import FastAPI +from routers import api +from config.settings import settings + +# 创建 FastAPI 应用实例 +app = FastAPI( + title=settings.APP_NAME, + description=""" +## 流水分析 Mock 服务器 + +模拟流水分析平台的 7 个核心接口,用于开发和测试。 + +### 主要功能 + +- **Token管理** - 创建项目并获取访问Token +- **文件上传** - 上传流水文件,支持异步解析(4秒延迟) +- **行内流水** - 拉取行内流水数据 +- **解析状态** - 轮询检查文件解析状态 +- **文件删除** - 批量删除上传的文件 +- **流水查询** - 分页获取银行流水数据 + +### 错误模拟 + +在请求参数中包含 `error_XXXX` 标记可触发对应的错误响应。 + +例如:`projectNo: "test_error_40101"` 将返回 40101 错误。 + +### 使用方式 + +1. 获取Token: POST /account/common/getToken +2. 上传文件: POST /watson/api/project/remoteUploadSplitFile +3. 轮询解析状态: POST /watson/api/project/upload/getpendings +4. 获取流水: POST /watson/api/project/getBSByLogId + """, + version=settings.APP_VERSION, + docs_url="/docs", + redoc_url="/redoc", +) + +# 包含 API 路由 +app.include_router(api.router, tags=["流水分析接口"]) + + +@app.get("/", summary="服务根路径") +async def root(): + """服务根路径,返回基本信息""" + return { + "service": settings.APP_NAME, + "version": settings.APP_VERSION, + "swagger_docs": "/docs", + "redoc": "/redoc", + "status": "running", + } + + +@app.get("/health", summary="健康检查") +async def health_check(): + """健康检查端点""" + return { + "status": "healthy", + "service": settings.APP_NAME, + "version": settings.APP_VERSION, + } + + +if __name__ == "__main__": + import uvicorn + + # 启动服务器 + uvicorn.run( + app, + host=settings.HOST, + port=settings.PORT, + log_level="debug" if settings.DEBUG else "info", + ) From 651e4540af32f27ca9491e4505f5ac556d2e055c Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Tue, 3 Mar 2026 09:29:14 +0800 Subject: [PATCH 6/8] test: add comprehensive test suite --- lsfx-mock-server/tests/__init__.py | 1 + lsfx-mock-server/tests/conftest.py | 30 +++++++++++++++++++ lsfx-mock-server/tests/test_api.py | 46 ++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 lsfx-mock-server/tests/__init__.py create mode 100644 lsfx-mock-server/tests/conftest.py create mode 100644 lsfx-mock-server/tests/test_api.py diff --git a/lsfx-mock-server/tests/__init__.py b/lsfx-mock-server/tests/__init__.py new file mode 100644 index 0000000..d4839a6 --- /dev/null +++ b/lsfx-mock-server/tests/__init__.py @@ -0,0 +1 @@ +# Tests package diff --git a/lsfx-mock-server/tests/conftest.py b/lsfx-mock-server/tests/conftest.py new file mode 100644 index 0000000..37c764b --- /dev/null +++ b/lsfx-mock-server/tests/conftest.py @@ -0,0 +1,30 @@ +""" +Pytest 配置和共享 fixtures +""" +import pytest +from fastapi.testclient import TestClient +import sys +import os + +# 添加项目根目录到 sys.path +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from main import app + + +@pytest.fixture +def client(): + """创建测试客户端""" + return TestClient(app) + + +@pytest.fixture +def sample_token_request(): + """示例 Token 请求""" + return { + "projectNo": "test_project_001", + "entityName": "测试企业", + "userId": "902001", + "userName": "902001", + "orgCode": "902000", + } diff --git a/lsfx-mock-server/tests/test_api.py b/lsfx-mock-server/tests/test_api.py new file mode 100644 index 0000000..c4283d0 --- /dev/null +++ b/lsfx-mock-server/tests/test_api.py @@ -0,0 +1,46 @@ +""" +API 端点测试 +""" + + +def test_root_endpoint(client): + """测试根路径""" + response = client.get("/") + assert response.status_code == 200 + data = response.json() + assert data["status"] == "running" + assert "swagger_docs" in data + + +def test_health_check(client): + """测试健康检查端点""" + response = client.get("/health") + assert response.status_code == 200 + data = response.json() + assert data["status"] == "healthy" + + +def test_get_token_success(client, sample_token_request): + """测试获取 Token - 成功场景""" + response = client.post("/account/common/getToken", json=sample_token_request) + assert response.status_code == 200 + data = response.json() + assert data["code"] == "200" + assert "token" in data["data"] + assert "projectId" in data["data"] + + +def test_get_token_error_40101(client): + """测试获取 Token - 错误场景 40101""" + request_data = { + "projectNo": "test_error_40101", + "entityName": "测试企业", + "userId": "902001", + "userName": "902001", + "orgCode": "902000", + } + response = client.post("/account/common/getToken", json=request_data) + assert response.status_code == 200 + data = response.json() + assert data["code"] == "40101" + assert data["successResponse"] == False From 1983d93a5dd5909a8c3bb72d647a82601e8d6d15 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Tue, 3 Mar 2026 09:30:50 +0800 Subject: [PATCH 7/8] docs: add README and deployment configuration --- lsfx-mock-server/.gitignore | 45 ++++++ lsfx-mock-server/Dockerfile | 19 +++ lsfx-mock-server/README.md | 236 ++++++++++++++++++++++++++++ lsfx-mock-server/docker-compose.yml | 17 ++ 4 files changed, 317 insertions(+) create mode 100644 lsfx-mock-server/.gitignore create mode 100644 lsfx-mock-server/Dockerfile create mode 100644 lsfx-mock-server/README.md create mode 100644 lsfx-mock-server/docker-compose.yml diff --git a/lsfx-mock-server/.gitignore b/lsfx-mock-server/.gitignore new file mode 100644 index 0000000..516df1b --- /dev/null +++ b/lsfx-mock-server/.gitignore @@ -0,0 +1,45 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual Environment +venv/ +ENV/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Environment +.env + +# Testing +.pytest_cache/ +.coverage +htmlcov/ + +# OS +.DS_Store +Thumbs.db diff --git a/lsfx-mock-server/Dockerfile b/lsfx-mock-server/Dockerfile new file mode 100644 index 0000000..8d2ffa8 --- /dev/null +++ b/lsfx-mock-server/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.11-slim + +# 设置工作目录 +WORKDIR /app + +# 复制依赖文件 +COPY requirements.txt . + +# 安装依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制项目文件 +COPY . . + +# 暴露端口 +EXPOSE 8000 + +# 启动命令 +CMD ["python", "main.py"] diff --git a/lsfx-mock-server/README.md b/lsfx-mock-server/README.md new file mode 100644 index 0000000..e6ebeb8 --- /dev/null +++ b/lsfx-mock-server/README.md @@ -0,0 +1,236 @@ +# 流水分析 Mock 服务器 + +基于 Python + FastAPI 的独立 Mock 服务器,用于模拟流水分析平台的 7 个核心接口。 + +## ✨ 特性 + +- ✅ **完整的接口模拟** - 实现所有 7 个核心接口 +- ✅ **文件解析延迟** - 使用 FastAPI 后台任务模拟 4 秒解析延迟 +- ✅ **错误场景触发** - 通过 `error_XXXX` 标记触发所有 8 个错误码 +- ✅ **自动 API 文档** - Swagger UI 和 ReDoc 自动生成 +- ✅ **配置驱动** - JSON 模板文件,易于修改响应数据 +- ✅ **零配置启动** - 开箱即用,无需数据库 + +## 🚀 快速开始 + +### 1. 安装依赖 + +```bash +pip install -r requirements.txt +``` + +### 2. 启动服务 + +```bash +python main.py +``` + +或使用 uvicorn(支持热重载): + +```bash +uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +### 3. 访问 API 文档 + +- **Swagger UI**: http://localhost:8000/docs +- **ReDoc**: http://localhost:8000/redoc + +## 📖 使用示例 + +### 正常流程 + +```python +import requests + +# 1. 获取 Token +response = requests.post( + "http://localhost:8000/account/common/getToken", + json={ + "projectNo": "test_project_001", + "entityName": "测试企业", + "userId": "902001", + "userName": "902001", + "orgCode": "902000" + } +) +token_data = response.json() +project_id = token_data["data"]["projectId"] + +# 2. 上传文件 +files = {"file": ("test.csv", open("test.csv", "rb"), "text/csv")} +response = requests.post( + "http://localhost:8000/watson/api/project/remoteUploadSplitFile", + files=files, + data={"groupId": project_id} +) +log_id = response.json()["data"]["uploadLogList"][0]["logId"] + +# 3. 轮询检查解析状态 +import time +for i in range(10): + response = requests.post( + "http://localhost:8000/watson/api/project/upload/getpendings", + json={"groupId": project_id, "inprogressList": str(log_id)} + ) + result = response.json() + if not result["data"]["parsing"]: + print("解析完成") + break + time.sleep(1) + +# 4. 获取银行流水 +response = requests.post( + "http://localhost:8000/watson/api/project/getBSByLogId", + json={ + "groupId": project_id, + "logId": log_id, + "pageNow": 1, + "pageSize": 10 + } +) +``` + +### 错误场景测试 + +```python +# 触发 40101 错误(appId错误) +response = requests.post( + "http://localhost:8000/account/common/getToken", + json={ + "projectNo": "test_error_40101", # 包含错误标记 + "entityName": "测试企业", + "userId": "902001", + "userName": "902001", + "orgCode": "902000" + } +) +# 返回: {"code": "40101", "message": "appId错误", ...} +``` + +## 🔧 配置说明 + +### 环境变量 + +创建 `.env` 文件(参考 `.env.example`): + +```bash +# 应用配置 +APP_NAME=流水分析Mock服务 +APP_VERSION=1.0.0 +DEBUG=true + +# 服务器配置 +HOST=0.0.0.0 +PORT=8000 + +# 模拟配置 +PARSE_DELAY_SECONDS=4 +MAX_FILE_SIZE=10485760 +``` + +### 响应模板 + +修改 `config/responses/` 下的 JSON 文件可以自定义响应数据: + +- `token.json` - Token 响应模板 +- `upload.json` - 上传文件响应模板 +- `parse_status.json` - 解析状态响应模板 +- `bank_statement.json` - 银行流水响应模板 + +## 🐳 Docker 部署 + +### 使用 Docker + +```bash +# 构建镜像 +docker build -t lsfx-mock-server . + +# 运行容器 +docker run -d -p 8000:8000 --name lsfx-mock lsfx-mock-server +``` + +### 使用 Docker Compose + +```bash +docker-compose up -d +``` + +## 📁 项目结构 + +``` +lsfx-mock-server/ +├── main.py # 应用入口 +├── config/ +│ ├── settings.py # 全局配置 +│ └── responses/ # 响应模板 +├── models/ +│ ├── request.py # 请求模型 +│ └── response.py # 响应模型 +├── services/ +│ ├── token_service.py # Token 管理 +│ ├── file_service.py # 文件上传和解析 +│ └── statement_service.py # 流水数据管理 +├── routers/ +│ └── api.py # API 路由 +├── utils/ +│ ├── error_simulator.py # 错误模拟 +│ └── response_builder.py # 响应构建器 +└── tests/ # 测试套件 +``` + +## 🧪 运行测试 + +```bash +# 运行所有测试 +pytest tests/ -v + +# 生成覆盖率报告 +pytest tests/ -v --cov=. --cov-report=html +``` + +## 🔌 API 接口列表 + +| 接口 | 方法 | 路径 | 描述 | +|------|------|------|------| +| 1 | POST | `/account/common/getToken` | 获取 Token | +| 2 | POST | `/watson/api/project/remoteUploadSplitFile` | 上传文件 | +| 3 | POST | `/watson/api/project/getJZFileOrZjrcuFile` | 拉取行内流水 | +| 4 | POST | `/watson/api/project/upload/getpendings` | 检查解析状态 | +| 5 | POST | `/watson/api/project/batchDeleteUploadFile` | 删除文件 | +| 6 | POST | `/watson/api/project/getBSByLogId` | 获取银行流水 | + +## ⚠️ 错误码列表 + +| 错误码 | 描述 | +|--------|------| +| 40101 | appId错误 | +| 40102 | appSecretCode错误 | +| 40104 | 可使用项目次数为0,无法创建项目 | +| 40105 | 只读模式下无法新建项目 | +| 40106 | 错误的分析类型,不在规定的取值范围内 | +| 40107 | 当前系统不支持的分析类型 | +| 40108 | 当前用户所属行社无权限 | +| 501014 | 无行内流水文件 | + +## 🛠️ 开发指南 + +### 添加新接口 + +1. 在 `models/request.py` 和 `models/response.py` 中添加模型 +2. 在 `services/` 中添加服务类 +3. 在 `routers/api.py` 中添加路由 +4. 在 `config/responses/` 中添加响应模板 +5. 编写测试 + +### 修改响应数据 + +直接编辑 `config/responses/` 下的 JSON 文件,重启服务即可生效。 + +## 📝 License + +MIT + +## 🤝 Contributing + +欢迎提交 Issue 和 Pull Request! diff --git a/lsfx-mock-server/docker-compose.yml b/lsfx-mock-server/docker-compose.yml new file mode 100644 index 0000000..5155c3b --- /dev/null +++ b/lsfx-mock-server/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.8' + +services: + lsfx-mock-server: + build: . + container_name: lsfx-mock-server + ports: + - "8000:8000" + environment: + - APP_NAME=流水分析Mock服务 + - APP_VERSION=1.0.0 + - DEBUG=true + - HOST=0.0.0.0 + - PORT=8000 + - PARSE_DELAY_SECONDS=4 + - MAX_FILE_SIZE=10485760 + restart: unless-stopped From a1f062d09dc7e463e886f6e26fdb843b08c5ffc8 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Tue, 3 Mar 2026 09:32:03 +0800 Subject: [PATCH 8/8] test: add integration tests for full workflow --- .../tests/integration/__init__.py | 1 + .../tests/integration/test_full_workflow.py | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 lsfx-mock-server/tests/integration/__init__.py create mode 100644 lsfx-mock-server/tests/integration/test_full_workflow.py diff --git a/lsfx-mock-server/tests/integration/__init__.py b/lsfx-mock-server/tests/integration/__init__.py new file mode 100644 index 0000000..a265048 --- /dev/null +++ b/lsfx-mock-server/tests/integration/__init__.py @@ -0,0 +1 @@ +# Integration tests package diff --git a/lsfx-mock-server/tests/integration/test_full_workflow.py b/lsfx-mock-server/tests/integration/test_full_workflow.py new file mode 100644 index 0000000..02a9b3a --- /dev/null +++ b/lsfx-mock-server/tests/integration/test_full_workflow.py @@ -0,0 +1,113 @@ +""" +集成测试 - 完整的接口调用流程测试 +""" +import pytest +import time + + +def test_complete_workflow(client): + """测试完整的接口调用流程""" + # 1. 获取 Token + response = client.post( + "/account/common/getToken", + json={ + "projectNo": "integration_test_001", + "entityName": "集成测试企业", + "userId": "902001", + "userName": "902001", + "orgCode": "902000", + }, + ) + assert response.status_code == 200 + token_data = response.json() + assert token_data["code"] == "200" + project_id = token_data["data"]["projectId"] + token = token_data["data"]["token"] + assert token is not None + + # 2. 上传文件(模拟) + # 注意:在测试环境中,我们跳过实际的文件上传,直接测试其他接口 + + # 3. 检查解析状态 + response = client.post( + "/watson/api/project/upload/getpendings", + json={"groupId": project_id, "inprogressList": "10001"}, + ) + assert response.status_code == 200 + status_data = response.json() + assert "parsing" in status_data["data"] + + # 4. 获取银行流水 + response = client.post( + "/watson/api/project/getBSByLogId", + json={ + "groupId": project_id, + "logId": 10001, + "pageNow": 1, + "pageSize": 10, + }, + ) + assert response.status_code == 200 + statement_data = response.json() + assert statement_data["code"] == "200" + assert "bankStatementList" in statement_data["data"] + assert "totalCount" in statement_data["data"] + + +def test_all_error_codes(client): + """测试所有错误码""" + error_codes = ["40101", "40102", "40104", "40105", "40106", "40107", "40108"] + + for error_code in error_codes: + response = client.post( + "/account/common/getToken", + json={ + "projectNo": f"test_error_{error_code}", + "entityName": "测试企业", + "userId": "902001", + "userName": "902001", + "orgCode": "902000", + }, + ) + assert response.status_code == 200 + data = response.json() + assert data["code"] == error_code, f"错误码 {error_code} 未正确触发" + assert data["successResponse"] == False + + +def test_pagination(client): + """测试分页功能""" + # 获取 Token + response = client.post( + "/account/common/getToken", + json={ + "projectNo": "pagination_test", + "entityName": "分页测试", + "userId": "902001", + "userName": "902001", + "orgCode": "902000", + }, + ) + project_id = response.json()["data"]["projectId"] + + # 测试第一页 + response = client.post( + "/watson/api/project/getBSByLogId", + json={"groupId": project_id, "logId": 10001, "pageNow": 1, "pageSize": 1}, + ) + page1 = response.json() + + # 测试第二页 + response = client.post( + "/watson/api/project/getBSByLogId", + json={"groupId": project_id, "logId": 10001, "pageNow": 2, "pageSize": 1}, + ) + page2 = response.json() + + # 验证总记录数相同 + assert page1["data"]["totalCount"] == page2["data"]["totalCount"] + + # 验证页码不同 + if page1["data"]["totalCount"] > 1: + assert len(page1["data"]["bankStatementList"]) == 1 + assert len(page2["data"]["bankStatementList"]) >= 0