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] 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 + )