Update mengyastore
This commit is contained in:
111
test/server.py
Normal file
111
test/server.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
简易 Webhook 接收服务:监听 POST /webhook,打印请求内容与部分头信息。
|
||||
|
||||
生产请将手机/脚本转发指向萌芽小店后端:`POST {YOUR_API_BASE}/api/webhooks/mengya-pay`,
|
||||
并在请求头附带与后端一致的 `X-Webhook-Secret`(对应环境变量 `WEBHOOK_MENGYA_SECRET`)。
|
||||
|
||||
本地测试环境变量:
|
||||
HOST 监听地址,默认 0.0.0.0
|
||||
PORT 监听端口,默认 8000
|
||||
WEBHOOK_SECRET 若设置,则要求请求头 X-Webhook-Secret 与其一致,否则返回 403
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import FastAPI, Header, HTTPException, Request, Response
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(message)s",
|
||||
)
|
||||
log = logging.getLogger("webhook")
|
||||
|
||||
app = FastAPI(title="Webhook 测试接收器", docs_url="/docs")
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health() -> dict:
|
||||
return {"status": "ok", "time": _now_iso()}
|
||||
|
||||
|
||||
@app.post("/webhook")
|
||||
async def webhook(
|
||||
request: Request,
|
||||
response: Response,
|
||||
x_webhook_secret: Annotated[str | None, Header(alias="X-Webhook-Secret")] = None,
|
||||
) -> dict:
|
||||
expected = os.environ.get("WEBHOOK_SECRET")
|
||||
if expected is not None:
|
||||
if x_webhook_secret is None:
|
||||
raise HTTPException(status_code=403, detail="missing X-Webhook-Secret")
|
||||
if not secrets.compare_digest(x_webhook_secret, expected):
|
||||
raise HTTPException(status_code=403, detail="invalid X-Webhook-Secret")
|
||||
|
||||
raw = await request.body()
|
||||
ct = request.headers.get("content-type", "")
|
||||
|
||||
try:
|
||||
if "application/json" in ct and raw.strip():
|
||||
body_preview = json.loads(raw.decode())
|
||||
elif raw.strip():
|
||||
body_preview = raw.decode(errors="replace")
|
||||
if len(body_preview) > 2000:
|
||||
body_preview = body_preview[:2000] + "…(truncated)"
|
||||
else:
|
||||
body_preview = None
|
||||
except json.JSONDecodeError:
|
||||
body_preview = raw.decode(errors="replace")
|
||||
|
||||
interesting_headers = {
|
||||
k: v
|
||||
for k, v in request.headers.items()
|
||||
if k.lower()
|
||||
in (
|
||||
"content-type",
|
||||
"user-agent",
|
||||
"x-github-event",
|
||||
"x-gitlab-token",
|
||||
"x-request-id",
|
||||
)
|
||||
}
|
||||
|
||||
event_id = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S%f")
|
||||
info = {
|
||||
"event_id": event_id,
|
||||
"time": _now_iso(),
|
||||
"client": getattr(request.client, "host", None),
|
||||
"method": request.method,
|
||||
"path": str(request.url.path),
|
||||
"query": dict(request.query_params),
|
||||
"content_type": ct or None,
|
||||
"interesting_headers": interesting_headers,
|
||||
"body": body_preview,
|
||||
"body_bytes": len(raw),
|
||||
}
|
||||
log.info("webhook %s | %s", event_id, json.dumps(info, ensure_ascii=False))
|
||||
|
||||
response.headers["X-Webhook-Received"] = event_id
|
||||
return {"ok": True, "event_id": event_id, "received_at": info["time"]}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
import uvicorn
|
||||
|
||||
host = os.environ.get("HOST", "0.0.0.0")
|
||||
port = int(os.environ.get("PORT", "8000"))
|
||||
uvicorn.run("server:app", host=host, port=port, reload=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user