import datetime from fastapi import APIRouter, Depends, Query from typing import Any import json import os import logging from datetime import datetime from alien_store.api.deps import get_contract_service from alien_store.schemas.request.contract_store import TemplatesCreate, SignUrl from alien_store.services.contract_server import ContractServer from common.esigntool.main import * import re, urllib.parse # ------------------- 日志配置 ------------------- LOG_DIR = os.path.join("common", "logs", "alien_store") os.makedirs(LOG_DIR, exist_ok=True) def _init_logger(): logger = logging.getLogger("alien_store") if logger.handlers: return logger logger.setLevel(logging.INFO) fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s") info_handler = logging.FileHandler(os.path.join(LOG_DIR, "info.log"), encoding="utf-8") info_handler.setLevel(logging.INFO) info_handler.setFormatter(fmt) error_handler = logging.FileHandler(os.path.join(LOG_DIR, "error.log"), encoding="utf-8") error_handler.setLevel(logging.ERROR) error_handler.setFormatter(fmt) logger.addHandler(info_handler) logger.addHandler(error_handler) # 控制台可选: logger.addHandler(logging.StreamHandler()) return logger logger = _init_logger() router = APIRouter() @router.get("/") async def index(): return {"module": "Contract", "status": "Ok"} @router.post("/get_esign_templates") async def create_esign_templates(templates_data: TemplatesCreate, templates_server: ContractServer = Depends(get_contract_service)): # AI审核完调用 e签宝生成文件 logger.info(f"get_esign_templates request: {templates_data}") res_text = fill_in_template(templates_data.merchant_name) try: res_data = json.loads(res_text) except json.JSONDecodeError: logger.error(f"fill_in_template non-json resp: {res_text}") return {"success": False, "message": "e签宝返回非 JSON", "raw": res_text} # 从返回结构提取下载链接,需与实际返回字段匹配 try: contract_url = res_data["data"]["fileDownloadUrl"] file_id = res_data["data"]["fileId"] m = re.search(r'/([^/]+)\.pdf', contract_url) if m: encoded_name = m.group(1) file_name = urllib.parse.unquote(encoded_name) except Exception: logger.error(f"fill_in_template missing fileDownloadUrl: {res_data}") return {"success": False, "message": "e签宝返回缺少 fileDownloadUrl", "raw": res_data} sign_data = create_by_file(file_id, file_name, templates_data.contact_phone, templates_data.merchant_name) sign_json = json.loads(sign_data) sing_id = sign_json["data"]["signFlowId"] result_contract = { "contract_url": contract_url, "file_name": file_name, "file_id": file_id, "status": 0, "sign_flow_id": sing_id, "sign_url": "" } updated = await templates_server.append_contract_url(templates_data, result_contract) logger.info(f"get_esign_templates success contact_phone={templates_data.contact_phone}, sign_flow_id={sing_id}") return {"success": True, "message": "合同模板已追加/创建", "sign_flow_id": sing_id, "file_id": file_id, "contract_url": contract_url} @router.get("/contracts/{store_id}") async def list_contracts(store_id: int, templates_server: ContractServer = Depends(get_contract_service)) -> Any: """根据 store_id 查询所有合同""" rows = await templates_server.list_by_store(store_id) return rows @router.get("/get_all_templates") async def get_all_templates( page: int = Query(1, ge=1, description="页码,从1开始"), page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"), templates_server: ContractServer = Depends(get_contract_service) ) -> Any: """分页查询所有合同""" return await templates_server.list_all_paged(page, page_size) @router.post("/esign/signurl") async def get_esign_sign_url(body: SignUrl, templates_server: ContractServer = Depends(get_contract_service)): sing_flow_id = body.sign_flow_id contact_phone = body.contact_phone logger.info(f"esign/signurl request contact_phone={contact_phone}, sign_flow_id={sing_flow_id}") result = sign_url(sing_flow_id, contact_phone) try: result_json = json.loads(result) except json.JSONDecodeError: logger.error(f"sign_url non-json resp: {result}") return {"success": False, "message": "e签宝返回非JSON", "raw": result} data = result_json.get("data") if isinstance(result_json, dict) else None if not data or not data.get("url"): logger.error(f"sign_url missing url: {result_json}") return {"success": False, "message": "e签宝返回缺少签署链接", "raw": result_json} result_sign_url = data.get("url") await templates_server.update_sign_url(contact_phone, sing_flow_id, result_sign_url) logger.info(f"sign_url success contact_phone={contact_phone}, sign_flow_id={sing_flow_id}") return {"success": True, "data": {"url": result_sign_url}} @router.post("/esign/callback") async def esign_callback(payload: dict, templates_server: ContractServer = Depends(get_contract_service)) -> Any: """ e签宝签署结果回调 需求:签署完成 -> 更新 signing_status=已签署,contract_url 中 status=1 """ sign_result = payload.get("signResult") operator = payload.get("operator") or {} sign_flow_id = payload.get("signFlowId") psn_account = operator.get("psnAccount") or {} contact_phone = psn_account.get("accountMobile") # 取回调中的毫秒时间戳,优先 operateTime,其次 timestamp ts_ms = payload.get("operateTime") or payload.get("timestamp") signing_dt = None if ts_ms: try: signing_dt = datetime.fromtimestamp(ts_ms / 1000) except Exception: signing_dt = None if sign_result == 2 and contact_phone and sign_flow_id: updated = await templates_server.mark_signed_by_phone(contact_phone, sign_flow_id, signing_dt) logger.info(f"esign_callback success phone={contact_phone}, sign_flow_id={sign_flow_id}, updated={updated}") return {"code":"200","msg":"success"} logger.error(f"esign_callback ignored payload: {payload}") return {"success": False, "message": "未处理: signResult!=2 或手机号/签署流程缺失"} @router.post("/esign/callback_auth") async def esign_callback_auth(payload: dict, templates_server: ContractServer = Depends(get_contract_service)): logger.info(f"esign_callback_auth payload: {payload}") return {"code":"200","msg":"success"}