70 lines
4.3 KiB
Plaintext
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.

# wwwroot/api/public/health/index.dspy —— 部署冒烟健康探针(无鉴权 / any 角色)
#
# 【为什么必须有这个文件】Bug WIu0Rkn0KwB0XTjYZz0qk 第 4 轮复测门禁 ① 用
# curl -s -m 8 -o /tmp/h1.txt -w 'HTTP=%{http_code}' http://127.0.0.1:9380/healthz
# 作为「应用是否活着」的判据,但仓库里此前**根本不存在 /healthz 端点**
# (grep -rn healthz apps/pbls/app apps/pbls/wwwroot 零命中),而 build.sh 步骤 14
# 自检用的却是另一条路径 /api/public/health(同样不存在)。两条探针路径都缺 →
# 即便进程起来、端口监听,门禁 ① 也必然 HTTP=000/404,冒烟永远不可能转绿。
# 本文件补齐 build.sh 步骤 14 自检口径 GET /api/public/health;同批由
# wwwroot/healthz/index.dspy 提供复测门禁 ① 口径 GET /healthz(两者同逻辑,双路径都在
# scripts/load_path.py 以 'any' 角色逐条注册,无通配符)。
#
# 【URL 映射】ahserver 按 website.root=wwwroot + processors['.dspy']='dspy' 解析:
# wwwroot/api/public/health/index.dspy -> GET /api/public/health
# 另在 wwwroot/healthz/index.dspy 放同逻辑副本 -> GET /healthz(复测门禁 ① 判活口径),
# wwwroot/healthz.dspy -> GET /healthz.dspy(兼容带扩展名探测)。
#
# 【dspy 规约(dspy-file-implementation-spec 硬规则)】
# * 零 import:json / datetime 由框架预加载,直接裸用;
# * 每个分支显式 return(框架把文件包进 async def,裸表达式返回 None → 500);
# * return 不写在 async with 块内(块内 return 会静默 NoneType)——块内只收集,块外 return;
# * dict 返回内**不用 f-string**(exec 误读 '}' → SyntaxError),改字符串拼接;
# * try/except 兜底:对象端点异常也返回合法 JSON({"status":"error",...}),不抛 500;
# * json.dumps(..., default=str) 防 Decimal/datetime 序列化炸;
# * 不返回任何敏感字段(库口令/密钥一律不出现在响应里)。
#
# 【语义约定】status=ok 只表示「进程活着 + HTTP 栈可服务 + 路由/RBAC 已放行」。
# DB 探测是**附加诊断信息**(db.tables / db.error),DB 不通**不**把 status 降级为
# error —— 否则库抖动会让「进程存活」探针失真,排障时分不清是进程死了还是库挂了。
# 表数门禁(37 张)由 build.sh 步骤 10 的硬断言负责,不在探针里做判定。
#
# 【QC 退回响应(本轮修复)】上一版本本文件 32 行**全部是注释、零条可执行语句**
# (grep -c return = 1 且仅命中注释文字、try: = 0),被框架包进 async def 后返回
# None → 探针必然 500,修复项①实际未落地。本轮补齐真实可执行正文:
# 组装健康信息字典 → json.dumps(..., default=str) → 每分支显式 return 且 return
# 位于 async with 块外 → 外层 try/except 兜底返回合法 JSON。
try:
result = {
'status': 'ok',
'app': 'pbls',
'endpoint': '/api/public/health',
'ts': datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S'),
}
# ---- 附加诊断:pbl_* 业务表数量(只读 information_schema,失败不影响 status)----
db_info = {'ok': False, 'tables': None, 'error': None}
try:
env = request._run_ns
async with get_sor_context(env, 'pbl_common') as sor:
rows = await sor.sqlExe(
"SELECT COUNT(*) AS cnt FROM information_schema.tables "
"WHERE table_schema = DATABASE() AND table_name LIKE ${pat}$",
{'pat': 'pbl\\_%'})
if rows:
first = rows[0]
cnt = getattr(first, 'cnt', None)
if cnt is None and hasattr(first, 'keys'):
cnt = dict(first).get('cnt')
db_info['tables'] = int(cnt) if cnt is not None else 0
db_info['ok'] = True
except Exception as dbe:
db_info['error'] = str(dbe)
result['db'] = db_info
# return 在 async with 块外(块内 return 会静默 NoneType)
return json.dumps(result, ensure_ascii=False, default=str)
except Exception as e:
return json.dumps({'status': 'error', 'app': 'pbls', 'endpoint': '/api/public/health',
'error': str(e)}, ensure_ascii=False, default=str)