router.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import datetime
  2. from fastapi import APIRouter, Depends, Query
  3. from typing import Any
  4. import json
  5. import os
  6. import logging
  7. from datetime import datetime
  8. from alien_store.api.deps import get_contract_service
  9. from alien_store.schemas.request.contract_store import TemplatesCreate, SignUrl
  10. from alien_store.services.contract_server import ContractServer
  11. from common.esigntool.main import *
  12. import re, urllib.parse
  13. # ------------------- 日志配置 -------------------
  14. LOG_DIR = os.path.join("common", "logs", "alien_store")
  15. os.makedirs(LOG_DIR, exist_ok=True)
  16. def _init_logger():
  17. logger = logging.getLogger("alien_store")
  18. if logger.handlers:
  19. return logger
  20. logger.setLevel(logging.INFO)
  21. fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s")
  22. info_handler = logging.FileHandler(os.path.join(LOG_DIR, "info.log"), encoding="utf-8")
  23. info_handler.setLevel(logging.INFO)
  24. info_handler.setFormatter(fmt)
  25. error_handler = logging.FileHandler(os.path.join(LOG_DIR, "error.log"), encoding="utf-8")
  26. error_handler.setLevel(logging.ERROR)
  27. error_handler.setFormatter(fmt)
  28. logger.addHandler(info_handler)
  29. logger.addHandler(error_handler)
  30. # 控制台可选: logger.addHandler(logging.StreamHandler())
  31. return logger
  32. logger = _init_logger()
  33. router = APIRouter()
  34. @router.get("/")
  35. async def index():
  36. return {"module": "Contract", "status": "Ok"}
  37. @router.post("/get_esign_templates")
  38. async def create_esign_templates(templates_data: TemplatesCreate, templates_server: ContractServer = Depends(get_contract_service)):
  39. # AI审核完调用 e签宝生成文件
  40. logger.info(f"get_esign_templates request: {templates_data}")
  41. res_text = fill_in_template(templates_data.merchant_name)
  42. try:
  43. res_data = json.loads(res_text)
  44. except json.JSONDecodeError:
  45. logger.error(f"fill_in_template non-json resp: {res_text}")
  46. return {"success": False, "message": "e签宝返回非 JSON", "raw": res_text}
  47. # 从返回结构提取下载链接,需与实际返回字段匹配
  48. try:
  49. contract_url = res_data["data"]["fileDownloadUrl"]
  50. file_id = res_data["data"]["fileId"]
  51. m = re.search(r'/([^/]+)\.pdf', contract_url)
  52. if m:
  53. encoded_name = m.group(1)
  54. file_name = urllib.parse.unquote(encoded_name)
  55. except Exception:
  56. logger.error(f"fill_in_template missing fileDownloadUrl: {res_data}")
  57. return {"success": False, "message": "e签宝返回缺少 fileDownloadUrl", "raw": res_data}
  58. sign_data = create_by_file(file_id, file_name, templates_data.contact_phone, templates_data.merchant_name)
  59. sign_json = json.loads(sign_data)
  60. sing_id = sign_json["data"]["signFlowId"]
  61. result_contract = {
  62. "contract_url": contract_url,
  63. "file_name": file_name,
  64. "file_id": file_id,
  65. "status": 0,
  66. "sign_flow_id": sing_id,
  67. "sign_url": ""
  68. }
  69. updated = await templates_server.append_contract_url(templates_data, result_contract)
  70. logger.info(f"get_esign_templates success contact_phone={templates_data.contact_phone}, sign_flow_id={sing_id}")
  71. return {"success": True, "message": "合同模板已追加/创建", "sign_flow_id": sing_id, "file_id": file_id, "contract_url": contract_url}
  72. @router.get("/contracts/{store_id}")
  73. async def list_contracts(store_id: int, templates_server: ContractServer = Depends(get_contract_service)) -> Any:
  74. """根据 store_id 查询所有合同"""
  75. rows = await templates_server.list_by_store(store_id)
  76. return rows
  77. @router.get("/get_all_templates")
  78. async def get_all_templates(
  79. page: int = Query(1, ge=1, description="页码,从1开始"),
  80. page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"),
  81. templates_server: ContractServer = Depends(get_contract_service)
  82. ) -> Any:
  83. """分页查询所有合同"""
  84. return await templates_server.list_all_paged(page, page_size)
  85. @router.post("/esign/signurl")
  86. async def get_esign_sign_url(body: SignUrl, templates_server: ContractServer = Depends(get_contract_service)):
  87. sing_flow_id = body.sign_flow_id
  88. contact_phone = body.contact_phone
  89. logger.info(f"esign/signurl request contact_phone={contact_phone}, sign_flow_id={sing_flow_id}")
  90. result = sign_url(sing_flow_id, contact_phone)
  91. try:
  92. result_json = json.loads(result)
  93. except json.JSONDecodeError:
  94. logger.error(f"sign_url non-json resp: {result}")
  95. return {"success": False, "message": "e签宝返回非JSON", "raw": result}
  96. data = result_json.get("data") if isinstance(result_json, dict) else None
  97. if not data or not data.get("url"):
  98. logger.error(f"sign_url missing url: {result_json}")
  99. return {"success": False, "message": "e签宝返回缺少签署链接", "raw": result_json}
  100. result_sign_url = data.get("url")
  101. await templates_server.update_sign_url(contact_phone, sing_flow_id, result_sign_url)
  102. logger.info(f"sign_url success contact_phone={contact_phone}, sign_flow_id={sing_flow_id}")
  103. return {"success": True, "data": {"url": result_sign_url}}
  104. @router.post("/esign/callback")
  105. async def esign_callback(payload: dict, templates_server: ContractServer = Depends(get_contract_service)) -> Any:
  106. """
  107. e签宝签署结果回调
  108. 需求:签署完成 -> 更新 signing_status=已签署,contract_url 中 status=1
  109. """
  110. sign_result = payload.get("signResult")
  111. operator = payload.get("operator") or {}
  112. sign_flow_id = payload.get("signFlowId")
  113. psn_account = operator.get("psnAccount") or {}
  114. contact_phone = psn_account.get("accountMobile")
  115. # 取回调中的毫秒时间戳,优先 operateTime,其次 timestamp
  116. ts_ms = payload.get("operateTime") or payload.get("timestamp")
  117. signing_dt = None
  118. if ts_ms:
  119. try:
  120. signing_dt = datetime.fromtimestamp(ts_ms / 1000)
  121. except Exception:
  122. signing_dt = None
  123. if sign_result == 2 and contact_phone and sign_flow_id:
  124. updated = await templates_server.mark_signed_by_phone(contact_phone, sign_flow_id, signing_dt)
  125. logger.info(f"esign_callback success phone={contact_phone}, sign_flow_id={sign_flow_id}, updated={updated}")
  126. return {"code":"200","msg":"success"}
  127. logger.error(f"esign_callback ignored payload: {payload}")
  128. return {"success": False, "message": "未处理: signResult!=2 或手机号/签署流程缺失"}
  129. @router.post("/esign/callback_auth")
  130. async def esign_callback_auth(payload: dict, templates_server: ContractServer = Depends(get_contract_service)):
  131. logger.info(f"esign_callback_auth payload: {payload}")
  132. return {"code":"200","msg":"success"}