AutoRpa/rpa_oms/app/main.py

170 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 订单中心接口服务。本期开放接口 1、3 收件;接口 2 只在 outbound 组装报文。
#
# 路径:
# - POST /api/oms/v1/sales-goods
# - POST /api/oms/v1/purchase-contracts
# 鉴权:X-Oms-Token,内网共享密钥,不上 OAuth。
# 请求里不跑选品、不录合同。
# 对外 401/503 用固定文案;ORA 原文只进审计表。
from __future__ import annotations
import logging
import sys
import threading
from pathlib import Path
from typing import Annotated, Any
from fastapi import Depends, FastAPI, Header, Request
from fastapi.responses import JSONResponse
# 把仓库根目录放进 sys.path,才能 import rpa_oracle。
def _put_repo_on_path() -> None:
root = Path(__file__).resolve().parents[2]
if str(root) not in sys.path:
sys.path.insert(0, str(root))
_put_repo_on_path()
from rpa_oracle.env import load_env
from rpa_oracle.errors import RepoUnavailable
from .auth import token_ok
from .inbound.audit import cause_text, write_audit
from .inbound.envelope import EnvelopeError, parse_envelope
from .inbound.messages import AUTH_FAILED, DB_UNAVAILABLE
from .inbound.oracle_contract_repo import OraclePurchaseContractRepo
from .inbound.oracle_repo import OracleSalesGoodsRepo
from .inbound.purchase_contract import receive_purchase_contracts
from .inbound.sales_goods import receive_sales_goods
load_env(Path(__file__).resolve().parents[1] / ".env")
logger = logging.getLogger("rpa_oms")
app = FastAPI(title="洁达 RPA × 订单中心", version="0.1")
_pool = None
_pool_lock = threading.Lock()
# 进程内共用 OMS 连接池。缺配置抛「数据库不可用」。
#
# 懒加载加锁:并发的首个请求只建一个池,输家的池不会被丢掉漏连接。
def get_pool():
global _pool
if _pool is not None:
return _pool
with _pool_lock:
if _pool is None:
from rpa_oracle.pool import create_pool
_pool = create_pool(role="oms")
return _pool
# 依赖注入阶段库不可用时,仍回固定 503,不把异常原文带出去。
@app.exception_handler(RepoUnavailable)
def handle_repo_unavailable(_request: Request, _exc: RepoUnavailable):
return JSONResponse(
status_code=503,
content={"code": 503, "msg": DB_UNAVAILABLE, "results": []},
)
@app.get("/health", description="进程存活探活,不连库。")
def health():
return {"ok": True}
@app.post("/api/oms/v1/sales-goods", description="接收接口 1 销售商品,落表 1。请求里不跑选品。")
def post_sales_goods(
payload: dict[str, Any] | None = None,
x_oms_token: Annotated[str | None, Header()] = None,
pool=Depends(get_pool),
):
return _post_inbound(
payload,
x_oms_token,
pool,
api_no=1,
receive=receive_sales_goods,
repo_cls=OracleSalesGoodsRepo,
)
@app.post("/api/oms/v1/purchase-contracts", description="接收接口 3 采购合同,落表 3。请求里不录合同。")
def post_purchase_contracts(
payload: dict[str, Any] | None = None,
x_oms_token: Annotated[str | None, Header()] = None,
pool=Depends(get_pool),
):
return _post_inbound(
payload,
x_oms_token,
pool,
api_no=3,
receive=receive_purchase_contracts,
repo_cls=OraclePurchaseContractRepo,
)
# 接口 1/3 共用:鉴权、拆信封、整批收件、写审计。混批 HTTP 仍 200。
def _post_inbound(payload, x_oms_token, pool, api_no: int, receive, repo_cls):
if not token_ok(x_oms_token):
body = {"code": 401, "msg": AUTH_FAILED, "results": []}
_audit_best_effort(pool, api_no, 401, payload, body, None, x_oms_token)
return JSONResponse(status_code=401, content=body)
try:
rows = parse_envelope(payload if payload is not None else {})
except EnvelopeError as exc:
body = {"code": 422, "msg": exc.message, "results": []}
_audit_best_effort(pool, api_no, 422, payload, body, None, x_oms_token)
return JSONResponse(status_code=422, content=body)
try:
with pool.acquire() as conn:
results = receive(rows, repo_cls(conn))
body = {"code": 200, "msg": _summary(results), "results": results}
write_audit(
conn,
api_no=api_no,
http_code=200,
payload=payload,
response=body,
error_text=None,
token=x_oms_token,
)
return body
except RepoUnavailable as exc:
body = {"code": 503, "msg": DB_UNAVAILABLE, "results": []}
_audit_best_effort(
pool, api_no, 503, payload, body, cause_text(exc), x_oms_token
)
return JSONResponse(status_code=503, content=body)
# 401/422/503 路径再借一条连接写审计。失败只记日志。
def _audit_best_effort(pool, api_no, http_code, payload, body, error_text, token) -> None:
try:
with pool.acquire() as conn:
write_audit(
conn,
api_no=api_no,
http_code=http_code,
payload=payload,
response=body,
error_text=error_text,
token=token,
)
except Exception:
logger.exception("inbound audit write failed")
# 按本批 resultCode 拼整包 msg:全成功 / 含重复 / 其它混批。
def _summary(results: list[dict]) -> str:
codes = {item["resultCode"] for item in results}
if codes <= {"ACCEPTED"}:
return "批量接收完成"
if codes <= {"ACCEPTED", "DUPLICATE"} and "DUPLICATE" in codes:
return "批量接收完成,存在重复数据"
return "批量接收完成"