hyperf自定义异常处理器需精准识别db异常(如pdoexception、queryexception等)、提取脱敏上下文、统一响应格式并记录结构化日志,严禁暴露原始sql和敏感信息。

要让 Hyperf 的自定义异常处理器能清晰打印数据库报错(比如 PDOException、QueryException、DbException 等),关键不是简单捕获再 echo,而是精准识别 DB 类异常 + 提取有效上下文 + 安全脱敏 + 统一响应格式。直接暴露原始 SQL 或连接信息会带来安全风险,而忽略堆栈和查询上下文又不利于排查。
识别并匹配数据库异常类型
Hyperf 不会自动区分 DB 异常,必须在 isValid() 中显式判断:
- 常见 DB 异常类包括:
PDOException、Hyperf\Database\Exception\QueryException、Hyperf\Database\Exception\ConnectionException、Illuminate\Database\QueryException(若用了 Laravel 包) -
isValid()必须严格限定范围,避免误吞其他异常。例如不要写return true,而应写:return $throwable instanceof \PDOException || $throwable instanceof \Hyperf\Database\Exception\QueryException; - 注意:某些 DB 错误会被包装成更上层异常(如事务中抛出的
RuntimeException套着PDOException),此时需用$throwable->getPrevious()向下追溯
提取可调试但不泄露敏感信息的上下文
原始异常消息(如 SQLSTATE[23000]: Integrity constraint violation)有用,但完整 SQL、密码、host、port 等绝不能直出:
- 从
$throwable中提取:getMessage()、getCode()、getFile()、getLine() - 若为
QueryException,可通过$throwable->getSql()获取 SQL —— 但需脱敏:
• 替换VALUES (xxx, 'password123', ...)中的敏感字段值为[hidden]
• 移除或截断长 SQL(如超 500 字符则用... [truncated]) - 从容器获取当前请求信息:
$this->request->getMethod()、$this->request->url()、$this->request->all()(注意过滤 token、password 等字段)
构造安全、结构化、可读的响应体
HTTP 状态码仍建议用语义化状态(如 400 表参数错误、500 表服务端问题),业务错误码放 JSON body,不要把 DB 错误码(如 23000)直接当 HTTP status:
- 响应示例:
{ "code": 1002, "message": "数据库写入失败", "detail": "唯一键冲突", "request_id": "xxx", "trace_id": "xxx" } -
code是你定义的业务错误码(非 SQLSTATE),便于前端分类处理detail可填精简后的错误原因(如“主键重复”、“外键不存在”),而非原始 SQLSTATE - 务必调用
$this->stopPropagation(),防止被后续更宽泛的 Handler(如兜底 Throwable Handler)二次处理
同步记录带上下文的结构化日志
仅返回给前端不够,DB 报错必须落盘可查:
- 日志内容至少包含:
• 请求方法 + 路径 + 脱敏参数
• 异常类名 + 消息 + SQL(已脱敏)
• 文件/行号 + 追溯栈($throwable->getTraceAsString())
• 当前用户 ID(如有)、trace_id - 推荐用 Hyperf 日志组件写入:
$this->logger->error('DB Error', [...]),而非手写 file_put_contents - 避免在 try-catch 内手动 throw 新异常覆盖原始堆栈;如需包装,用
new AppDbException($msg, $code, $original)并保留$original作为 previous











