Files
ulthon_admin/extend/base/common/service/ErrorHandlerBase.php
2026-03-26 20:22:34 +08:00

327 lines
11 KiB
PHP
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.

<?php
namespace base\common\service;
use Exception;
use Throwable;
abstract class ErrorHandlerBase
{
/**
* 错误码注册表
*/
protected array $errorCodes = [
// 通用错误 (ERR_GEN_XXX)
'ERR_GEN_UNKNOWN' => '未知错误',
'ERR_GEN_INVALID_PARAM' => '参数错误',
'ERR_GEN_MISSING_PARAM' => '缺少必需参数',
'ERR_GEN_OPERATION_FAILED' => '操作失败',
'ERR_GEN_PERMISSION_DENIED' => '权限不足',
'ERR_GEN_NOT_FOUND' => '资源不存在',
'ERR_GEN_TIMEOUT' => '操作超时',
// 数据库错误 (ERR_DB_XXX)
'ERR_DB_CONNECTION' => '数据库连接失败',
'ERR_DB_QUERY' => '数据库查询失败',
'ERR_DB_INSERT' => '数据插入失败',
'ERR_DB_UPDATE' => '数据更新失败',
'ERR_DB_DELETE' => '数据删除失败',
'ERR_DB_TRANSACTION' => '数据库事务执行失败',
'ERR_DB_SCHEME_MISMATCH' => '数据库结构与期望不符',
// 文件系统错误 (ERR_FS_XXX)
'ERR_FS_NOT_FOUND' => '文件不存在',
'ERR_FS_READ_FAILED' => '文件读取失败',
'ERR_FS_WRITE_FAILED' => '文件写入失败',
'ERR_FS_DELETE_FAILED' => '文件删除失败',
'ERR_FS_PERMISSION' => '文件权限不足',
// 网络错误 (ERR_NET_XXX)
'ERR_NET_CONNECTION' => '网络连接失败',
'ERR_NET_TIMEOUT' => '网络请求超时',
'ERR_NET_RESPONSE' => '网络响应错误',
// 配置错误 (ERR_CFG_XXX)
'ERR_CFG_MISSING' => '配置缺失',
'ERR_CFG_INVALID' => '配置无效',
];
/**
* 修复建议注册表
*/
protected array $suggestions = [
// 通用错误修复建议
'ERR_GEN_UNKNOWN' => '未知错误,请稍后重试或联系管理员',
'ERR_GEN_INVALID_PARAM' => '请检查传入的参数是否符合要求',
'ERR_GEN_MISSING_PARAM' => '请确保所有必需参数都已提供',
'ERR_GEN_OPERATION_FAILED' => '请稍后重试,如果问题持续存在请联系管理员',
'ERR_GEN_PERMISSION_DENIED' => '请检查您是否有执行此操作的权限',
'ERR_GEN_NOT_FOUND' => '请确认请求的资源ID是否正确',
'ERR_GEN_TIMEOUT' => '请检查网络连接或稍后重试',
// 数据库错误修复建议
'ERR_DB_CONNECTION' => '请检查数据库连接配置和网络连接',
'ERR_DB_QUERY' => '请检查SQL语句语法和数据库表结构',
'ERR_DB_INSERT' => '请检查数据是否符合表结构和约束条件',
'ERR_DB_UPDATE' => '请确保要更新的数据存在且符合约束条件',
'ERR_DB_DELETE' => '请确保要删除的数据存在',
'ERR_DB_TRANSACTION' => '请检查事务中的操作是否都执行成功',
'ERR_DB_SCHEME_MISMATCH' => '请运行数据库迁移命令: php think migrate:run',
// 文件系统错误修复建议
'ERR_FS_NOT_FOUND' => '请检查文件路径是否正确',
'ERR_FS_READ_FAILED' => '请检查文件是否存在且有读取权限',
'ERR_FS_WRITE_FAILED' => '请检查目录是否存在且有写入权限',
'ERR_FS_DELETE_FAILED' => '请检查文件是否存在且有删除权限',
'ERR_FS_PERMISSION' => '请检查文件或目录的权限设置',
// 网络错误修复建议
'ERR_NET_CONNECTION' => '请检查网络连接和目标服务器状态',
'ERR_NET_TIMEOUT' => '请检查网络连接或增加超时时间',
'ERR_NET_RESPONSE' => '请检查请求参数和服务器响应',
// 配置错误修复建议
'ERR_CFG_MISSING' => '请在配置文件中添加缺失的配置项',
'ERR_CFG_INVALID' => '请检查配置项的值是否符合要求',
];
/**
* 获取结构化错误信息
*
* @param string $errorCode 错误码
* @param string|null $errorMessage 错误消息(覆盖默认错误消息)
* @param array $context 上下文信息
* @return array 结构化错误信息
*/
public function getError(string $errorCode, ?string $errorMessage = null, array $context = []): array
{
$defaultMessage = $this->errorCodes[$errorCode] ?? $this->errorCodes['ERR_GEN_UNKNOWN'];
$actualMessage = $errorMessage ?? $defaultMessage;
$error = [
'success' => false,
'error_code' => $errorCode,
'error_message' => $actualMessage,
];
// 添加文件和行信息(如果存在)
if (isset($context['file'])) {
$error['file'] = $context['file'];
}
if (isset($context['line'])) {
$error['line'] = $context['line'];
}
// 添加修复建议
$suggestion = $this->suggestions[$errorCode] ?? $this->suggestions['ERR_GEN_UNKNOWN'];
$error['suggestion'] = $suggestion;
// 添加额外的上下文信息
if (!empty($context)) {
$error['context'] = $context;
}
return $error;
}
/**
* 从异常生成结构化错误信息
*
* @param Throwable $exception 异常对象
* @param string|null $customErrorCode 自定义错误码
* @return array 结构化错误信息
*/
public function getErrorForException(Throwable $exception, ?string $customErrorCode = null): array
{
// 尝试从异常消息推断错误码
$errorCode = $customErrorCode ?? $this->inferErrorCodeFromException($exception);
$error = [
'success' => false,
'error_code' => $errorCode,
'error_message' => $exception->getMessage(),
];
// 添加文件和行信息
if ($exception->getFile()) {
$error['file'] = $exception->getFile();
}
if ($exception->getLine()) {
$error['line'] = $exception->getLine();
}
// 添加修复建议
$suggestion = $this->suggestions[$errorCode] ?? $this->suggestions['ERR_GEN_UNKNOWN'];
$error['suggestion'] = $suggestion;
// 添加异常类型
$error['exception_type'] = get_class($exception);
// 添加堆栈跟踪(在开发模式下)
if ($this->isDebugMode()) {
$error['trace'] = $this->formatTrace($exception);
}
return $error;
}
/**
* 从异常推断错误码
*
* @param Throwable $exception 异常对象
* @return string 错误码
*/
protected function inferErrorCodeFromException(Throwable $exception): string
{
$message = $exception->getMessage();
$class = get_class($exception);
// 根据异常类型推断
if (strpos($class, 'Db') !== false || strpos($class, 'Query') !== false) {
if (strpos($message, 'connection') !== false || strpos($message, 'connect') !== false) {
return 'ERR_DB_CONNECTION';
}
if (strpos($message, 'SQLSTATE') !== false) {
return 'ERR_DB_QUERY';
}
return 'ERR_DB_OPERATION_FAILED';
}
if (strpos($class, 'File') !== false || strpos($class, 'Stream') !== false) {
if (strpos($message, 'No such file') !== false || strpos($message, 'not found') !== false) {
return 'ERR_FS_NOT_FOUND';
}
if (strpos($message, 'permission') !== false) {
return 'ERR_FS_PERMISSION';
}
return 'ERR_FS_READ_FAILED';
}
if (strpos($class, 'Network') !== false || strpos($class, 'Curl') !== false) {
if (strpos($message, 'timeout') !== false) {
return 'ERR_NET_TIMEOUT';
}
return 'ERR_NET_CONNECTION';
}
// 根据消息内容推断
if (strpos($message, 'permission') !== false || strpos($message, 'denied') !== false) {
return 'ERR_GEN_PERMISSION_DENIED';
}
if (strpos($message, 'not found') !== false || strpos($message, '不存在') !== false) {
return 'ERR_GEN_NOT_FOUND';
}
if (strpos($message, 'timeout') !== false) {
return 'ERR_GEN_TIMEOUT';
}
return 'ERR_GEN_UNKNOWN';
}
/**
* 格式化堆栈跟踪
*
* @param Throwable $exception 异常对象
* @return array 格式化的堆栈跟踪
*/
protected function formatTrace(Throwable $exception): array
{
$trace = [];
foreach ($exception->getTrace() as $index => $frame) {
$traceItem = [
'index' => $index,
];
if (isset($frame['file'])) {
$traceItem['file'] = $frame['file'];
}
if (isset($frame['line'])) {
$traceItem['line'] = $frame['line'];
}
if (isset($frame['function'])) {
$traceItem['function'] = $frame['function'];
}
if (isset($frame['class'])) {
$traceItem['class'] = $frame['class'];
}
if (isset($frame['type'])) {
$traceItem['type'] = $frame['type'];
}
$trace[] = $traceItem;
}
return $trace;
}
/**
* 检查是否为调试模式
*
* @return bool
*/
protected function isDebugMode(): bool
{
try {
$app = \think\facade\App::instance();
return (bool)($app->config->get('app.app_debug') ?? false);
} catch (\Exception $e) {
return false;
}
}
/**
* 输出错误作为JSON
*
* @param array $error 错误信息
* @return string JSON格式的错误信息
*/
public function outputError(array $error): string
{
$options = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
return json_encode($error, $options);
}
/**
* 注册自定义错误码
*
* @param string $errorCode 错误码
* @param string $errorMessage 错误消息
* @param string|null $suggestion 修复建议
* @return void
*/
public function registerErrorCode(string $errorCode, string $errorMessage, ?string $suggestion = null): void
{
$this->errorCodes[$errorCode] = $errorMessage;
if ($suggestion !== null) {
$this->suggestions[$errorCode] = $suggestion;
}
}
/**
* 批量注册错误码
*
* @param array $errors 错误码数组,格式:['ERR_XXX' => ['message' => '...', 'suggestion' => '...']]
* @return void
*/
public function registerErrorCodes(array $errors): void
{
foreach ($errors as $errorCode => $config) {
$this->errorCodes[$errorCode] = $config['message'] ?? '未知错误';
if (isset($config['suggestion'])) {
$this->suggestions[$errorCode] = $config['suggestion'];
}
}
}
/**
* 获取所有已注册的错误码
*
* @return array 错误码数组
*/
public function getRegisteredErrorCodes(): array
{
return $this->errorCodes;
}
}