mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-01 23:42:48 +08:00
327 lines
11 KiB
PHP
327 lines
11 KiB
PHP
<?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;
|
||
}
|
||
}
|