Refactor credit parse to use remote HTML paths

This commit is contained in:
wkc
2026-05-13 14:20:42 +08:00
parent b822cc202e
commit be443d1b31
18 changed files with 473 additions and 171 deletions

View File

@@ -110,22 +110,31 @@ response = requests.post(
### 征信解析 Mock
```bash
curl -s -X POST http://localhost:8000/xfeature-mngs/conversation/htmlEval \
-F model=LXCUSTALL \
-F hType=PERSON \
-F file=@./sample-credit.html
curl -s -X POST http://localhost:8000/api/service/interface/invokeService/xfeature \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d serialNum=CCDI_CREDIT_1 \
-d orgCode=902000 \
-d runType=1 \
-d remotePath=http://127.0.0.1:62318/profile/credit-html/sample-credit.html \
-d model=LXCUSTALL
```
成功时返回:
```json
{
"message": "成功",
"status_code": "0",
"payload": {
"lx_header": {},
"lx_debt": {},
"lx_publictype": {}
"success": true,
"code": 1000,
"data": {
"mappingOutputFields": {
"message": "成功",
"status_code": "0",
"payload": {
"lx_header": {},
"lx_debt": {},
"lx_publictype": {}
}
}
}
}
```
@@ -133,10 +142,13 @@ curl -s -X POST http://localhost:8000/xfeature-mngs/conversation/htmlEval \
调试错误码时,可在 `model` 中追加错误标记:
```bash
curl -s -X POST http://localhost:8000/xfeature-mngs/conversation/htmlEval \
-F model=error_ERR_10001 \
-F hType=PERSON \
-F file=@./sample-credit.html
curl -s -X POST http://localhost:8000/api/service/interface/invokeService/xfeature \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d serialNum=CCDI_CREDIT_1 \
-d orgCode=902000 \
-d runType=1 \
-d remotePath=http://127.0.0.1:62318/profile/credit-html/sample-credit.html \
-d model=error_ERR_10001
```
健康检查:
@@ -258,7 +270,7 @@ pytest tests/ -v --cov=. --cov-report=html
| 4 | POST | `/watson/api/project/upload/getpendings` | 检查解析状态 |
| 5 | POST | `/watson/api/project/batchDeleteUploadFile` | 删除文件 |
| 6 | POST | `/watson/api/project/getBSByLogId` | 获取银行流水 |
| 7 | POST | `/xfeature-mngs/conversation/htmlEval` | 征信解析 Mock |
| 7 | POST | `/api/service/interface/invokeService/xfeature` | 征信解析 Mock |
| 8 | GET | `/credit/health` | 征信解析健康检查 |
## ⚠️ 错误码列表

View File

@@ -26,7 +26,7 @@ app = FastAPI(
- **解析状态** - 轮询检查文件解析状态
- **文件删除** - 批量删除上传的文件
- **流水查询** - 分页获取银行流水数据
- **征信解析** - 上传 HTML 并返回结构化征信 payload
- **征信解析** - 读取 HTML 远程地址并返回结构化征信 payload
### 错误模拟
@@ -40,7 +40,7 @@ app = FastAPI(
2. 上传文件: POST /watson/api/project/remoteUploadSplitFile
3. 轮询解析状态: POST /watson/api/project/upload/getpendings
4. 获取流水: POST /watson/api/project/getBSByLogId
5. 征信解析: POST /xfeature-mngs/conversation/htmlEval
5. 征信解析: POST /api/service/interface/invokeService/xfeature
""",
version=settings.APP_VERSION,
docs_url="/docs",

View File

@@ -1,6 +1,8 @@
from typing import Optional
from urllib.parse import urlparse
from urllib.request import urlopen
from fastapi import APIRouter, File, Form, UploadFile
from fastapi import APIRouter, Form
from services.credit_debug_service import CreditDebugService
from services.credit_html_identity_service import CreditHtmlIdentityService
@@ -12,26 +14,30 @@ debug_service = CreditDebugService("config/credit_response_examples.json")
identity_service = CreditHtmlIdentityService()
@router.post("/xfeature-mngs/conversation/htmlEval")
@router.post("/api/service/interface/invokeService/xfeature")
async def html_eval(
serialNum: Optional[str] = Form(None),
orgCode: Optional[str] = Form(None),
runType: Optional[str] = Form(None),
remotePath: Optional[str] = Form(None),
model: Optional[str] = Form(None),
hType: Optional[str] = Form(None),
file: Optional[UploadFile] = File(None),
):
error_response = debug_service.validate_request(
serial_num=serialNum,
org_code=orgCode,
run_type=runType,
remote_path=remotePath,
model=model,
h_type=hType,
file_present=file is not None,
)
if error_response:
return error_response
html_content = await file.read()
html_content = fetch_remote_html(remotePath)
subject_identity = identity_service.extract_identity(html_content)
payload = payload_service.generate_payload(
model=model,
h_type=hType,
filename=file.filename or "credit.html",
h_type="PERSON",
filename=remote_filename(remotePath),
subject_identity=subject_identity,
)
return debug_service.build_success_response(payload)
@@ -40,3 +46,14 @@ async def html_eval(
@router.get("/credit/health")
async def credit_health():
return {"status": "healthy", "service": "credit-mock"}
def fetch_remote_html(remote_path: str) -> bytes:
with urlopen(remote_path, timeout=5) as response:
return response.read()
def remote_filename(remote_path: str) -> str:
path = urlparse(remote_path).path
filename = path.rsplit("/", 1)[-1]
return filename or "credit.html"

View File

@@ -9,19 +9,29 @@ class CreditDebugService:
"""处理征信解析接口的调试标记、参数校验与响应封装。"""
VALID_MODEL = "LXCUSTALL"
VALID_HTYPES = {"PERSON", "ENTERPRISE"}
def __init__(self, template_path: str):
self.template_path = template_path
self.templates = self._load_templates()
def validate_request(self, model: Optional[str], h_type: Optional[str], file_present: bool):
def validate_request(
self,
serial_num: Optional[str],
org_code: Optional[str],
run_type: Optional[str],
remote_path: Optional[str],
model: Optional[str],
):
if not serial_num:
return self.build_missing_param_response("serialNum")
if not org_code:
return self.build_missing_param_response("orgCode")
if not run_type:
return self.build_missing_param_response("runType")
if not remote_path:
return self.build_missing_param_response("remotePath")
if not model:
return self.build_missing_param_response("model")
if not file_present:
return self.build_missing_param_response("file")
if not h_type:
return self.build_missing_param_response("hType")
error_code = self.detect_error_marker(model)
if error_code:
@@ -29,22 +39,21 @@ class CreditDebugService:
if model != self.VALID_MODEL:
return self.build_error_response("ERR_10002")
if h_type not in self.VALID_HTYPES:
return self.build_error_response("ERR_10003")
return None
def build_success_response(self, payload: dict) -> dict:
response = copy.deepcopy(self.templates["success"])
response["payload"] = payload
return response
return self.wrap_mapping_response(response)
def build_missing_param_response(self, param_name: str) -> dict:
response = self.build_error_response("ERR_99999")
response["message"] = response["message"].replace("XX", param_name)
mapping_output_fields = response["data"]["mappingOutputFields"]
mapping_output_fields["message"] = mapping_output_fields["message"].replace("XX", param_name)
return response
def build_error_response(self, error_code: str) -> dict:
return copy.deepcopy(self.templates["errors"][error_code])
return self.wrap_mapping_response(copy.deepcopy(self.templates["errors"][error_code]))
def detect_error_marker(self, model: str) -> Optional[str]:
matched = re.search(r"error_(ERR_\d+)", model)
@@ -55,6 +64,15 @@ class CreditDebugService:
return error_code
return None
def wrap_mapping_response(self, mapping_output_fields: dict) -> dict:
return {
"success": True,
"code": 1000,
"data": {
"mappingOutputFields": mapping_output_fields,
},
}
def _load_templates(self) -> dict:
template_file = Path(self.template_path)
if not template_file.is_absolute():