公共错误码层
错误码必须是全系统唯一、稳定、可被前端和 AI 共同理解的东西。
ErrorCode
@Getter
@requiredargsconstructor
public enum ErrorCode {
SUCCESS(200, "success"),
PARAM_INVALID(100001, "请求参数不合法"),
PARAM_REQUIRED(100002, "必填参数不能为空"),
PAGE_INVALID(100003, "分页参数不合法"),
SORT_ORDER_INVALID(100004, "排序方向不合法"),
DATASOURCE_NOT_FOUND(200001, "数据源不存在"),
TABLE_SEMANTIC_NOT_FOUND(310001, "表语义元数据不存在"),
TABLE_HIDDEN(310002, "表已隐藏"),
TABLE_PHYSICAL_MISSING(310003, "物理表不存在或已下线"),
COLUMN_SEMANTIC_NOT_FOUND(320001, "列语义元数据不存在"),
COLUMN_PHYSICAL_MISSING(320002, "物理列不存在或已下线"),
RELATION_NOT_FOUND(330001, "逻辑关系不存在"),
RELATION_TYPE_INVALID(330002, "关系类型不合法"),
SCHEMA_READ_FAILED(510001, "读取数据库结构失败"),
SQL_ONLY_SELECT_ALLOWED(500002, "只允许执行 SELECT 查询"),
SYSTEM_ERROR(900001, "系统异常");
private final int code;
private final String defaultMessage;
}
BusinessException
@Getter
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
private final Object detail;
public BusinessException(ErrorCode errorCode) {
this(errorCode, errorCode.getDefaultMessage(), null);
}
public BusinessException(ErrorCode errorCode, String message) {
this(errorCode, message, null);
}
public BusinessException(ErrorCode errorCode, String message, Object detail) {
super(message);
this.errorCode = errorCode;
this.detail = detail;
}
public int getCode() {
return errorCode.getCode();
}
}
这里 detail 很关键。业务接口可以不返回它,AI 工具层可以返回它。
业务接口响应设计
业务 API 继续用现在的 Result,保持前端兼容。
返回格式
{
"code": 310003,
"message": "物理表不存在或已下线,请重新同步表结构后再试。",
"data": null
}
Java 结构
@DaTa
public class Result implements Serializable {
private Integer code;
private String message;
private T data;
public static Result success(T data) {
return new Result<>(200, "success", data);
}
public static Result error(ErrorCode errorCode) {
return new Result<>(errorCode.getCode(), errorCode.getDefaultMessage());
}
public static Result error(ErrorCode errorCode, String message) {
return new Result<>(errorCode.getCode(), message);
}
}
业务接口层关注的是:
code:前端判断错误类型
message:默认展示文案
data:业务数据
业务 API 不需要暴露太多 AI 行为信息,比如 suggestAction,否则前端模型会被污染。
AI 工具响应设计
AI 工具层用另一个模型,不直接复用 Result。
返回格式
{
"success": false,
"code": 310003,
"message": "物理表不存在或已下线,请重新同步表结构后再试。",
"action": "SYNC_TABLE_SCHEMA",
"data": {
"datasourceId": 1,
"tableName": "orders"
}
}
Java 结构
@builder
public record ToolResult(
boolean success,
Integer code,
String message,
String action,
T data) {
public static ToolResult success(T data) {
return ToolResult.builder()
.success(true)
.code(ErrorCode.SUCCESS.getCode())
.message(ErrorCode.SUCCESS.getDefaultMessage())
.data(data)
.build();
}
public static ToolResult failed(
ErrorCode errorCode,
String message,
String action,
T data) {
return ToolResult.builder()
.success(false)
.code(errorCode.getCode())
.message(message)
.action(action)
.data(data)
.build();
}
}
AI 工具层比业务接口多两个字段:
success 让模型一眼知道工具是否成功
action 告诉模型建议下一步做什么
比如:
SYNC_TABLE_SCHEMA
CHECK_DATASOURCE
ASK_USER_CONFIRMATION
RETRY_LATER
CONTACT_ADMIN
总体设计
系统里拆成两层错误响应:
业务接口响应 Business API Result
给前端 / 管理后台 / 普通 HTTP API 使用
AI 工具响应 Tool Result
给 Agent / LLM / MCP Tool / 内部工具调用使用
共同依赖:
ErrorCode
BusinessException
全局异常处理
也就是:
flowchart TD A["Service / Domain Logic"] --> B["throw BusinessException(ErrorCode)"] B --> C["ErrorCode enum"] C --> D["Business API Result"] C --> E["AI Tool Result"] D --> F["前端根据 code 做 i18n / UI 展示"] E --> G["AI 根据 code / action 判断下一步"]公共错误码层
错误码必须是全系统唯一、稳定、可被前端和 AI 共同理解的东西。
ErrorCode
@Getter
@requiredargsconstructor
public enum ErrorCode {
SUCCESS(200, "success"),
PARAM_INVALID(100001, "请求参数不合法"),
PARAM_REQUIRED(100002, "必填参数不能为空"),
PAGE_INVALID(100003, "分页参数不合法"),
SORT_ORDER_INVALID(100004, "排序方向不合法"),
DATASOURCE_NOT_FOUND(200001, "数据源不存在"),
TABLE_SEMANTIC_NOT_FOUND(310001, "表语义元数据不存在"),
TABLE_HIDDEN(310002, "表已隐藏"),
TABLE_PHYSICAL_MISSING(310003, "物理表不存在或已下线"),
COLUMN_SEMANTIC_NOT_FOUND(320001, "列语义元数据不存在"),
COLUMN_PHYSICAL_MISSING(320002, "物理列不存在或已下线"),
RELATION_NOT_FOUND(330001, "逻辑关系不存在"),
RELATION_TYPE_INVALID(330002, "关系类型不合法"),
SCHEMA_READ_FAILED(510001, "读取数据库结构失败"),
SQL_ONLY_SELECT_ALLOWED(500002, "只允许执行 SELECT 查询"),
SYSTEM_ERROR(900001, "系统异常");
private final int code;
private final String defaultMessage;
}
BusinessException
@Getter
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
private final Object detail;
public BusinessException(ErrorCode errorCode) {
this(errorCode, errorCode.getDefaultMessage(), null);
}
public BusinessException(ErrorCode errorCode, String message) {
this(errorCode, message, null);
}
public BusinessException(ErrorCode errorCode, String message, Object detail) {
super(message);
this.errorCode = errorCode;
this.detail = detail;
}
public int getCode() {
return errorCode.getCode();
}
}
这里 detail 很关键。业务接口可以不返回它,AI 工具层可以返回它。
业务接口响应设计
业务 API 继续用现在的 Result,保持前端兼容。
返回格式
{
"code": 310003,
"message": "物理表不存在或已下线,请重新同步表结构后再试。",
"data": null
}
Java 结构
@DaTa
public class Result implements Serializable {
private Integer code;
private String message;
private T data;
public static Result success(T data) {
return new Result<>(200, "success", data);
}
public static Result error(ErrorCode errorCode) {
return new Result<>(errorCode.getCode(), errorCode.getDefaultMessage());
}
public static Result error(ErrorCode errorCode, String message) {
return new Result<>(errorCode.getCode(), message);
}
}
业务接口层关注的是:
code:前端判断错误类型
message:默认展示文案
data:业务数据
业务 API 不需要暴露太多 AI 行为信息,比如 suggestAction,否则前端模型会被污染。
AI 工具响应设计
AI 工具层用另一个模型,不直接复用 Result。
返回格式
{
"success": false,
"code": 310003,
"message": "物理表不存在或已下线,请重新同步表结构后再试。",
"action": "SYNC_TABLE_SCHEMA",
"data": {
"datasourceId": 1,
"tableName": "orders"
}
}
Java 结构
@builder
public record ToolResult(
boolean success,
Integer code,
String message,
String action,
T data) {
public static ToolResult success(T data) {
return ToolResult.builder()
.success(true)
.code(ErrorCode.SUCCESS.getCode())
.message(ErrorCode.SUCCESS.getDefaultMessage())
.data(data)
.build();
}
public static ToolResult failed(
ErrorCode errorCode,
String message,
String action,
T data) {
return ToolResult.builder()
.success(false)
.code(errorCode.getCode())
.message(message)
.action(action)
.data(data)
.build();
}
}
AI 工具层比业务接口多两个字段:
success 让模型一眼知道工具是否成功
action 告诉模型建议下一步做什么
比如:
SYNC_TABLE_SCHEMA
CHECK_DATASOURCE
ASK_USER_CONFIRMATION
RETRY_LATER
CONTACT_ADMIN
Tool Action 设计
建议加一个枚举,避免字符串乱飞。
@Getter
@requiredargsconstructor
public enum ToolAction {
NONE("NONE"),
SYNC_TABLE_SCHEMA("SYNC_TABLE_SCHEMA"),
CHECK_DATASOURCE("CHECK_DATASOURCE"),
CHECK_TABLE_NAME("CHECK_TABLE_NAME"),
ASK_USER_CONFIRMATION("ASK_USER_CONFIRMATION"),
RETRY_LATER("RETRY_LATER"),
CONTACT_ADMIN("CONTACT_ADMIN");
private final String code;
}
AI 看到 action=SYNC_TABLE_SCHEMA,就知道应该说:
这个表的物理结构已经不存在或下线了,需要先同步表结构后再查询。
而不是胡乱猜。
5. 业务异常到 AI 工具异常的转换
核心思路:
Service 抛 BusinessException
↓
普通 Controller:GlobalExceptionHandler 转成 Result
↓
AI Tool:ToolExceptionMapper 转成 ToolResult
ToolExceptionMapper
@component
public class ToolExceptionMapper {
}
6. getTableSchema 示例
业务层不要关心自己是被前端调,还是被 AI 调。它只抛统一异常。
if (Boolean.FALSE.equals(tableInfo.getPhysicalStatus())) {
throw new BusinessException(
ErrorCode.TABLE_PHYSICAL_MISSING,
"物理表不存在或已下线,请重新同步表结构后再试。",
Map.of(
"datasourceId", datasourceId,
"tableName", tableName
));
}
前端接口看到
{
"code": 310003,
"message": "物理表不存在或已下线,请重新同步表结构后再试。",
"data": null
}
AI 工具看到
{
"success": false,
"code": 310003,
"message": "物理表不存在或已下线,请重新同步表结构后再试。",
"action": "SYNC_TABLE_SCHEMA",
"data": {
"datasourceId": 1,
"tableName": "orders"
}
}
同一个错误,不同出口。漂亮,稳。
7. 推荐包结构
io.github.malonetalk.common
Result.java
io.github.malonetalk.exception
ErrorCode.java
BusinessException.java
GlobalExceptionHandler.java
io.github.malonetalk.agent.tool
ToolResult.java
ToolAction.java
ToolExceptionMapper.java
如果想更清楚,也可以放:
io.github.malonetalk.agent.tools.result
8. 落地顺序
我建议按这个顺序改:
新增 ErrorCode
新增 BusinessException
改造 Result 支持 ErrorCode
改造 GlobalExceptionHandler
新增 ToolResult
新增 ToolAction
新增 ToolExceptionMapper
先改 getTableSchema 这条链路
再逐步替换其他 IllegalArgumentException