Refactor credit parse to use remote HTML paths
This commit is contained in:
@@ -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` | 征信解析健康检查 |
|
||||
|
||||
## ⚠️ 错误码列表
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user