'未知错误', '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; } }