统一异常处理
import yueyang.vostok.data.exception.*;
import yueyang.vostok.file.exception.*;
import yueyang.vostok.office.exception.*;
import yueyang.vostok.cluster.*;
import yueyang.vostok.cluster.exception.*;
import yueyang.vostok.config.exception.*;
// Data 模块
try {
Vostok.Data.findAll(User.class);
} catch (VKSqlException e) {
// SQL_ERROR / SQL_TIMEOUT / SQL_CONSTRAINT / SQL_SYNTAX / SQL_CONNECTION
log.error("SQL error: {} - {}", e.getCode(), e.getMessage());
} catch (VKPoolException e) {
log.error("Pool exhausted: {}", e.getMessage());
} catch (VKTxException e) {
log.error("Transaction error: {}", e.getMessage());
} catch (VKException e) {
// 所有其他 Vostok 异常基类
log.error("Vostok error: {} {}", e.getCode(), e.getMessage());
}
// File 模块
try {
Vostok.File.read("missing.txt");
} catch (VKFileException e) {
log.error("File error: {} {}", e.getCode(), e.getMessage());
}
// Office 模块
try {
Vostok.Office.readWordText("word/orders.docx");
} catch (VKOfficeException e) {
log.error("Office error: {} {}", e.getCode(), e.getMessage());
}
// Cluster 模块
try {
Vostok.Cluster.broadcast("order.created", "{}".getBytes()).join();
} catch (VKClusterException e) {
log.error("Cluster error: {} {}", e.getCode(), e.getMessage());
}
Data 模块错误码(VKErrorCode)
| 错误码 | 枚举 | 触发场景 |
| DK-400 | INVALID_ARGUMENT | 传入参数非法(null 主键、空包名等) |
| DK-401 | NOT_INITIALIZED | 模块未初始化时调用操作 |
| DK-402 | CONFIG_ERROR | 配置项缺失或错误(无 url/username 等) |
| DK-410 | META_ERROR | 实体元数据解析失败(@VKEntity 配置错误) |
| DK-500 | SQL_ERROR | SQL 执行通用错误 |
| DK-501 | SQL_TIMEOUT | SQL 执行超时 |
| DK-502 | SQL_CONSTRAINT | 唯一约束、外键约束等违反 |
| DK-503 | SQL_SYNTAX | SQL 语法错误 |
| DK-504 | SQL_CONNECTION | 数据库连接失败 |
| DK-510 | SCAN_ERROR | 实体包扫描失败 |
| DK-520 | POOL_ERROR | 连接池错误(耗尽、泄漏检测等) |
| DK-530 | TX_ERROR | 事务开启/提交/回滚失败 |
| DK-540 | CACHE_ERROR | 缓存操作错误 |
Data 原生 SQL 场景(executeQuery / executeUpdate)
| 场景 | 错误码 | 说明 |
| SQL 语法错误(如 SELEC) | DK-503 SQL_SYNTAX | 由 VKExceptionTranslator 按 SQLState=42xx 映射 |
| 数据库连接异常 | DK-504 SQL_CONNECTION | 连接断开、网络不可达等连接类错误 |
| 执行超时 | DK-501 SQL_TIMEOUT | 受 queryTimeoutMs 或事务剩余超时控制 |
try (DataResult rs = Vostok.Data.executeQuery("SELEC id FROM t_user")) {
// no-op
} catch (VKSqlException e) {
// 常见:DK-503 SQL_SYNTAX
log.error("raw sql failed: {} {} sqlState={}", e.getCode(), e.getMessage(), e.getSqlState());
}
Data 模块异常类型
| 异常类 | 包路径 | 对应错误码 |
VKException | data.exception | 所有运行时异常基类 |
VKArgumentException | data.exception | DK-400 |
VKStateException | data.exception | DK-401 |
VKConfigException | data.exception | DK-402 |
VKMetaException | data.exception | DK-410 |
VKSqlException | data.exception | DK-500 ~ DK-504 |
VKScanException | data.exception | DK-510 |
VKPoolException | data.exception | DK-520 |
VKTxException | data.exception | DK-530 |
File 模块错误码(VKFileErrorCode)
| 错误码 | 枚举 | 触发场景 |
| FK-400 | INVALID_ARGUMENT | 路径参数为 null 或空 |
| FK-401 | NOT_INITIALIZED | File 模块未初始化 |
| FK-402 | CONFIG_ERROR | baseDir 配置错误 |
| FK-403 | STATE_ERROR | 状态异常(如已关闭) |
| FK-404 | NOT_FOUND | 文件或目录不存在 |
| FK-410 | PATH_ERROR | 路径越界(尝试访问 baseDir 之外) |
| FK-500 | IO_ERROR | 文件读写 IO 错误 |
| FK-520 | SECURITY_ERROR | 安全错误(Zip Slip 路径穿越) |
| FK-530 | UNSUPPORTED | 当前存储后端不支持该操作 |
| FK-540 | ZIP_BOMB_RISK | ZIP 炸弹限制触发(解压比过高) |
| FK-550 | IMAGE_DECODE_ERROR | 图片解码失败 |
| FK-551 | IMAGE_ENCODE_ERROR | 图片编码失败 |
| FK-552 | IMAGE_LIMIT_EXCEEDED | 图片尺寸/像素超出限制 |
| FK-553 | UNSUPPORTED_IMAGE_FORMAT | 不支持的图片格式 |
| FK-560 | READ_ONLY_ERROR | 存储处于只读模式,写操作被拒绝 |
| FK-561 | ENCRYPT_ERROR | 文件加密/解密失败 |
| FK-562 | GZIP_ERROR | GZip 压缩/解压失败 |
File 模块异常类型
| 异常类 | 包路径 | 说明 |
VKFileException | file.exception | File 模块所有异常的基类,包含 VKFileErrorCode |
Web 模块(@VKApi 注解路由)错误返回说明
| 场景 | HTTP 状态 | 返回体 |
| 参数绑定失败(缺少必填 Query/Header/Cookie、类型转换失败) | 400 | 统一返回 VKWebResult,statusCode=400 |
| 控制器方法执行异常 | 500(默认) | 统一返回 VKWebResult,默认错误信息为 Internal Server Error |
业务主动返回 VKWebResult.of(418, ...) | 418 | HTTP 状态码与 statusCode 保持一致 |
// 绑定失败示例:GET /api/user?id=abc,id 需要 long
{
"statusCode": 400,
"errorMessage": "Query param 'id' is required",
"data": null,
"requestCostMs": 1,
"traceId": "trace-7b3c",
"requestTime": 1741075200000,
"responseTime": 1741075200001
}
Office 模块错误码(VKOfficeErrorCode)
| 错误码 | 枚举 | 触发场景 |
| OF-400 | INVALID_ARGUMENT | 参数非法(空路径、空 consumer 等) |
| OF-402 | CONFIG_ERROR | Office 配置不合法 |
| OF-403 | STATE_ERROR | 运行状态不满足(如非 local 文件模式) |
| OF-404 | NOT_FOUND | sheet 或资源不存在 |
| OF-500 | IO_ERROR | IO 操作失败 |
| OF-530 | UNSUPPORTED_FORMAT | 不支持的 Office 格式(当前仅 .xlsx / .docx / .pptx / .pdf) |
| OF-564 | PARSE_ERROR | Office 文档解析失败(Excel/Word/PPT/PDF) |
| OF-565 | WRITE_ERROR | Office 文档写入失败(Excel/Word/PPT/PDF) |
| OF-566 | LIMIT_EXCEEDED | 超过行列/字数/图片/大小限制 |
| OF-567 | SECURITY_ERROR | 安全检测失败(路径/魔数/XXE/压缩安全) |
Office 模块异常类型
| 异常类 | 包路径 | 说明 |
VKOfficeException | office.exception | Office 模块统一异常,包含 VKOfficeErrorCode |
Office Excel 模板常见错误场景
| 场景 | 错误码 | 说明 |
| 模板后缀不是 .xlsx | OF-530 UNSUPPORTED_FORMAT | renderExcelTemplate 仅支持 .xlsx |
| 循环起止标记不匹配 / 缺少结束标记 | OF-564 PARSE_ERROR | 例如只有 {{#items as item}} 没有 {{/items}} |
| 模板展开后行数超过限制 | OF-566 LIMIT_EXCEEDED | 由 VKExcelTemplateOptions.maxExpandedRows 或全局限制触发 |
| 输出文件大小超过限制 | OF-566 LIMIT_EXCEEDED | 由 VKExcelTemplateOptions.maxOutputBytes 或全局限制触发 |
| 模板文件伪装 / XXE / 压缩安全风险 | OF-567 SECURITY_ERROR | 路径、魔数、XML 与解包安全检查命中 |
Office 异步任务错误场景
try {
String jobId = Vostok.Office.submitJob(
VKOfficeJobRequest.create(() -> {
throw new RuntimeException("convert failed");
})
);
Vostok.Office.awaitJob(jobId, 30000);
} catch (VKOfficeException e) {
// 常见:STATE_ERROR(运行态不满足)、WRITE_ERROR(任务执行失败)
Vostok.Log.error("office job error: {} {}", e.getCode(), e.getMessage());
}
// 回调无订阅者时,可通过 dead-letter 接收“未处理通知”
Vostok.Office.onJobDeadLetter(n ->
Vostok.Log.warn("office dead-letter: {} {}", n.jobId(), n.status()));
Cluster 模块错误码(VKClusterErrorCode)
| 错误码 | 枚举 | 触发场景 |
| CL-400 | INVALID_ARGUMENT | topic 为空、listener 为空、消息参数非法 |
| CL-402 | CONFIG_ERROR | nodeId / clusterSecret / 端口等配置错误 |
| CL-403 | STATE_ERROR | Cluster 未初始化就调用运行时方法 |
| CL-404 | NOT_FOUND | 节点或资源不存在 |
| CL-451 | AUTH_ERROR | clusterName 不一致、共享密钥 HMAC 校验失败 |
| CL-500 | IO_ERROR | 端口绑定失败、连接 IO 异常 |
| CL-560 | PROTOCOL_ERROR | 非法帧、非法长度、协议版本不兼容 |
| CL-566 | LIMIT_EXCEEDED | 超过最大节点数、消息体超限 |
| CL-568 | BROADCAST_TIMEOUT | 预留给广播超时场景的错误码 |
Cluster 典型错误场景
| 场景 | 错误码 | 说明 |
节点间 clusterSecret 不一致 | CL-451 AUTH_ERROR | 握手时 HMAC 校验失败,连接立即断开 |
clusterName 不一致 | CL-451 AUTH_ERROR | 不同集群直接拒绝互联 |
广播 payload 超过 maxMessageBytes | CL-566 LIMIT_EXCEEDED | 广播发送前直接拒绝 |
| 收到非法帧或帧长度异常 | CL-560 PROTOCOL_ERROR | 协议层拒绝处理并记入 protocolErrors |
模块未初始化就调用 nodes() / broadcast() | CL-403 STATE_ERROR | 必须先执行 Vostok.Cluster.init(...) |
try {
Vostok.Cluster.init(new VKClusterConfig()
.clusterName("prod-order")
.clusterSecret("wrong-secret")
.nodeId("node-01")
.bindHost("127.0.0.1")
.bindPort(18888));
Vostok.Cluster.broadcast("order.created", "{}".getBytes()).join();
} catch (VKClusterException e) {
Vostok.Log.error("cluster failed: {} {}", e.getCode(), e.getMessage());
}
Config 模块错误码(VKConfigErrorCode)
| 错误码 | 枚举 | 触发场景 |
| CK-400 | INVALID_ARGUMENT | 传入参数非法 |
| CK-402 | CONFIG_ERROR | 配置自身错误 |
| CK-404 | KEY_NOT_FOUND | 必需配置项不存在 |
| CK-420 | VALIDATION_ERROR | 配置值未通过 Validator 校验 |
| CK-500 | IO_ERROR | 配置文件读取 IO 错误 |
| CK-510 | PARSE_ERROR | 配置文件解析失败(格式错误) |
Config 模块异常类型
| 异常类 | 包路径 | 说明 |
VKConfigException | config.exception | Config 模块所有异常的基类,包含 VKConfigErrorCode |
获取错误码
// VKException 及子类
try {
Vostok.Data.insert(entity);
} catch (VKException e) {
VKErrorCode code = e.getCode(); // 枚举值
String msg = e.getMessage();
// 例:code = SQL_CONSTRAINT, code.name() = "SQL_CONSTRAINT"
}
// VKFileException
try {
Vostok.File.read("x.txt");
} catch (VKFileException e) {
VKFileErrorCode code = e.getCode();
// 例:code = NOT_FOUND
}
// VKConfigException
try {
Vostok.Config.getString("required.key");
} catch (VKConfigException e) {
VKConfigErrorCode code = e.getCode();
}