diff --git a/lsfx-mock-server/config/credit_feature_schema.json b/lsfx-mock-server/config/credit_feature_schema.json new file mode 100644 index 00000000..a05c494b --- /dev/null +++ b/lsfx-mock-server/config/credit_feature_schema.json @@ -0,0 +1,159 @@ +[ + { + "domain": "lx_header", + "field": "query_cert_no", + "type": "string" + }, + { + "domain": "lx_header", + "field": "query_cust_name", + "type": "string" + }, + { + "domain": "lx_header", + "field": "report_time", + "type": "string" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_house_bal", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_house_lmt", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_house_state", + "type": "status", + "options": ["正常", "逾期", "不良"] + }, + { + "domain": "lx_debt", + "field": "uncle_bank_car_bal", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_car_lmt", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_car_state", + "type": "status", + "options": ["正常", "逾期", "不良"] + }, + { + "domain": "lx_debt", + "field": "uncle_bank_manage_bal", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_manage_lmt", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_manage_state", + "type": "status", + "options": ["正常", "逾期", "不良"] + }, + { + "domain": "lx_debt", + "field": "uncle_bank_consume_bal", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_consume_lmt", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_consume_state", + "type": "status", + "options": ["正常", "逾期", "不良"] + }, + { + "domain": "lx_debt", + "field": "uncle_bank_other_bal", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_other_lmt", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_bank_other_state", + "type": "status", + "options": ["正常", "逾期", "不良"] + }, + { + "domain": "lx_debt", + "field": "uncle_not_bank_bal", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_not_bank_lmt", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_not_bank_state", + "type": "status", + "options": ["正常", "逾期", "不良"] + }, + { + "domain": "lx_debt", + "field": "uncle_credit_cart_bal", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_credit_cart_lmt", + "type": "amount" + }, + { + "domain": "lx_debt", + "field": "uncle_credit_cart_state", + "type": "status", + "options": ["正常", "逾期", "不良"] + }, + { + "domain": "lx_publictype", + "field": "civil_cnt", + "type": "count" + }, + { + "domain": "lx_publictype", + "field": "enforce_cnt", + "type": "count" + }, + { + "domain": "lx_publictype", + "field": "adm_cnt", + "type": "count" + }, + { + "domain": "lx_publictype", + "field": "civil_lmt", + "type": "amount" + }, + { + "domain": "lx_publictype", + "field": "enforce_lmt", + "type": "amount" + }, + { + "domain": "lx_publictype", + "field": "adm_lmt", + "type": "amount" + } +] diff --git a/lsfx-mock-server/config/credit_response_examples.json b/lsfx-mock-server/config/credit_response_examples.json new file mode 100644 index 00000000..11890e14 --- /dev/null +++ b/lsfx-mock-server/config/credit_response_examples.json @@ -0,0 +1,44 @@ +{ + "success": { + "message": "成功", + "payload": {}, + "status_code": "0" + }, + "errors": { + "ERR_99999": { + "message": "关键参数缺失,参数名: XX", + "payload": null, + "status_code": "ERR_99999" + }, + "ERR_10001": { + "message": "无效的证件号码", + "payload": null, + "status_code": "ERR_10001" + }, + "ERR_10002": { + "message": "无效的主题域", + "payload": null, + "status_code": "ERR_10002" + }, + "ERR_10003": { + "message": "报文类型无效,仅支持JSON/XML", + "payload": null, + "status_code": "ERR_10003" + }, + "ERR_10004": { + "message": "无效机构号或行社号", + "payload": null, + "status_code": "ERR_10004" + }, + "ERR_10005": { + "message": "无权限访问", + "payload": null, + "status_code": "ERR_10005" + }, + "ERR_10006": { + "message": "尽调报告生成异常:异步事件发送失败", + "payload": null, + "status_code": "ERR_10006" + } + } +} diff --git a/lsfx-mock-server/services/credit_payload_service.py b/lsfx-mock-server/services/credit_payload_service.py new file mode 100644 index 00000000..087967fb --- /dev/null +++ b/lsfx-mock-server/services/credit_payload_service.py @@ -0,0 +1,86 @@ +import json +import random +from datetime import date, timedelta +from pathlib import Path +from typing import Dict, List + + +class CreditPayloadService: + """根据征信字段 schema 生成稳定随机的 mock payload。""" + + def __init__(self, schema_path: str): + self.schema_path = schema_path + self.schema = self._load_schema() + + def generate_payload(self, model: str, h_type: str, filename: str) -> dict: + rng = random.Random(self._build_seed(model, h_type, filename)) + payload = { + "lx_header": {}, + "lx_debt": {}, + "lx_publictype": {}, + } + + for item in self.schema: + domain = item["domain"] + field = item["field"] + field_type = item["type"] + payload[domain][field] = self._generate_value(field, field_type, item, rng) + + return payload + + def _load_schema(self) -> List[dict]: + schema_file = Path(self.schema_path) + if not schema_file.is_absolute(): + schema_file = Path(__file__).resolve().parent.parent / schema_file + return json.loads(schema_file.read_text(encoding="utf-8")) + + @staticmethod + def _build_seed(model: str, h_type: str, filename: str) -> str: + return f"{model}|{h_type}|{filename}" + + def _generate_value( + self, + field: str, + field_type: str, + item: dict, + rng: random.Random, + ) -> str: + if field_type == "string": + return self._generate_string(field, rng) + if field_type == "amount": + return f"{rng.uniform(0, 500000):.2f}" + if field_type == "count": + return str(rng.randint(0, 20)) + if field_type == "status": + return rng.choice(item["options"]) + raise ValueError(f"Unsupported field type: {field_type}") + + def _generate_string(self, field: str, rng: random.Random) -> str: + if field == "query_cert_no": + return self._generate_cert_no(rng) + if field == "query_cust_name": + return self._generate_name(rng) + if field == "report_time": + return self._generate_report_date(rng) + return f"mock_{rng.randint(1000, 9999)}" + + @staticmethod + def _generate_cert_no(rng: random.Random) -> str: + area_code = "330781" + start_date = date(1980, 1, 1) + birthday = start_date + timedelta(days=rng.randint(0, 14000)) + sequence = f"{rng.randint(100, 999)}" + check_code = rng.choice("0123456789X") + return f"{area_code}{birthday.strftime('%Y%m%d')}{sequence}{check_code}" + + @staticmethod + def _generate_name(rng: random.Random) -> str: + surnames = ["张", "王", "李", "赵", "陈", "刘", "周", "吴"] + given_names = ["伟", "芳", "娜", "敏", "静", "磊", "洋", "婷", "超", "洁"] + return f"{rng.choice(surnames)}{rng.choice(given_names)}{rng.choice(given_names)}" + + @staticmethod + def _generate_report_date(rng: random.Random) -> str: + base_date = date(2024, 1, 1) + report_date = base_date + timedelta(days=rng.randint(0, 365)) + return report_date.strftime("%Y-%m-%d") diff --git a/lsfx-mock-server/tests/test_credit_payload_service.py b/lsfx-mock-server/tests/test_credit_payload_service.py new file mode 100644 index 00000000..c3b597cf --- /dev/null +++ b/lsfx-mock-server/tests/test_credit_payload_service.py @@ -0,0 +1,35 @@ +from services.credit_payload_service import CreditPayloadService + + +def test_generate_payload_should_be_stable_for_same_input(): + service = CreditPayloadService("config/credit_feature_schema.json") + + payload1 = service.generate_payload( + model="LXCUSTALL", + h_type="PERSON", + filename="credit-report-a.html", + ) + payload2 = service.generate_payload( + model="LXCUSTALL", + h_type="PERSON", + filename="credit-report-a.html", + ) + + assert payload1 == payload2 + assert set(payload1.keys()) == {"lx_header", "lx_debt", "lx_publictype"} + assert len(payload1["lx_debt"]) == 21 + assert len(payload1["lx_publictype"]) == 6 + + +def test_generate_payload_should_use_schema_type_rules(): + service = CreditPayloadService("config/credit_feature_schema.json") + + payload = service.generate_payload( + model="LXCUSTALL", + h_type="ENTERPRISE", + filename="credit-report-b.html", + ) + + assert payload["lx_debt"]["uncle_bank_house_state"] in {"正常", "逾期", "不良"} + assert payload["lx_header"]["report_time"].count("-") == 2 + assert payload["lx_publictype"]["civil_cnt"].isdigit()