Constant、ErrorCode 与 I18n 约定
遵守常量、错误码和语言资源约定,业务号段以全局登记为准。
浏览全部手册
本页目录
文档来源与 Markdown 原文
权威正文:ineed-core/docs/core/constant-errorcode-i18n-guide.md。网站按工作区快照同步,原文中的历史日期和验证范围保留。
开始编码前核对同版本源码;跨仓文件引用可在源码定位目录查找。
下载 Markdown 原文 ↓文档定位
本文档定义业务模块中的常量、模块内错误码和国际化资源的统一规则,包含:
- 框架通用错误码与业务模块私有错误码的编号边界
- 业务模块
{Object}Constant、Urls、Resource、ErrorCode的落点 - 应用错误码权威登记入口(避免不同模块重复占用)
- i18n 资源文件的格式、顺序和 key 约定
如果需要直接复制代码骨架,使用 module-constant-i18n-template.md。
一、框架通用错误码(根级 ErrorCode)
总体规则
- 框架通用错误码使用负数数字字符串,定义在
ineed-core-base/src/main/java/com/ineed/base/exception/ErrorCode.java - 业务模块私有错误码使用 6 位正整数数字字符串
- 新增业务错误码必须按模块分段分配,不允许随意占号
- 错误文案占位符统一使用
MessageFormat风格:{0}、{1}、{2}
框架保留区间
| 区间 | 类型 | 说明 |
|---|---|---|
-1 ~ -99 |
系统通用错误 | 未知错误、不支持操作、非法操作、配置缺失、任务运行中 |
-100 ~ -199 |
数据通用错误 | 数据重复、不存在、已删除、被占用、版本冲突、固定/锁定/保留/同步数据限制、树结构限制 |
-200 ~ -299 |
请求/参数错误 | 参数为空、参数取值错误、参数超过限制等非 Bean Validation 场景 |
-300 ~ -399 |
数据库/基础设施错误 | SQL 执行、文件处理、网关调用等底层或外部基础设施错误 |
-400 ~ -499 |
租户/上下文错误 | 租户无效、上下文缺失、上下文不匹配等 |
-500 ~ -599 |
验证/校验错误 | Bean Validation、领域校验、验证码错误 |
-800 ~ -999 |
认证/会话/授权错误 | 登录、账号状态、会话、密码、凭证、访问授权等通用安全错误 |
子段划分:
-800 ~ -999 内部:
| 区间 | 类型 |
|---|---|
-800 ~ -849 |
登录 / 账号 / 会话 |
-850 ~ -879 |
密码 / 凭证 |
-880 ~ -899 |
授权 / 访问控制 |
-500 ~ -599 内部:
| 区间 | 类型 |
|---|---|
-500 ~ -549 |
Bean Validation / 参数校验 |
-550 ~ -559 |
验证码 |
框架通用错误边界
以下场景应继续复用根级 ErrorCode:
- 数据不存在、重复、被使用、版本冲突
- 参数为空、取值错误、Bean Validation 失败
- 租户上下文无效、未授权访问
- 账号密码错误、账号锁定/禁用/过期
以下场景应放入模块私有错误码:
- 某个业务对象的特定规则
- 某个业务流程的状态机限制
- 某个模块私有的授权/密码治理/第三方集成错误
二、应用模块错误码的权威来源
应用私有错误码的格式、范围、状态和新增流程以工作区 design/standards/error-code-registry.md 为唯一来源。Core 只维护框架通用错误机制与使用约定。
原本文中的 BBNNNN 分段规则和业务块号表已退出维护,不能用于新代码。实际业务域的编号方式不同,不根据旧模块名或示例数字推导可用号段。
新增错误时:先判断是否能复用通用错误 → 查全局登记与真实模块常量 → 按登记流程分配 → 同步常量、语言资源与测试。已发布错误不随意改号或复用。
三、业务登记与迁移边界
本页不保留应用模块号段副本。UK/FK 约束继续使用 Core 通用错误与模块约束 i18n key,不为每个数据库约束创建私有错误码。
模板中的 <REGISTERED_ERROR_1> 是占位符,必须替换为登记表确认过的真实号码。遇到历史代码与登记表不一致时,核实兼容策略并在所属模块修正,不为适配旧代码恢复另一套全局登记。
四、Constant 分层边界
CoreConstant(框架级)
ineed-core-base/src/main/java/com/ineed/base/CoreConstant.java 只承载跨模块稳定契约:
- 根级 URL / WS 前缀
- 通用状态位(
YESNO、DataStatus、AccountStatus) - 通用动作路径(
CoreConstant.Urls) - 通用动作资源标识(
CoreConstant.ResourceKey) - HTTP Header Key、上下文 Key
约束:不放具体业务对象的模块路径、私有资源标识、私有错误码。
CoreDataConstant(数据层)
ineed-core-data-base/src/main/java/com/ineed/core/data/CoreDataConstant.java 承载跨数据栈共享的数据层常量:
- 通用列名
Columns - 通用属性名
Fields - 通用数据层 SpEL
Spel
约束:只放 JPA / JDBC / MyBatis-Flex / R2DBC 共享契约,不塞入业务域私有字段。
业务模块 {Object}Constant
放在 core 层根包下的 {Object}Constant.java 中:
public final class UserConstant {
private UserConstant() { super(); }
public static final class Urls {
public static final String USER = AuthorizationCoreConstant.Urls.AUTHORIZATION + "/user";
}
public static final class Resource {
public static final String KEY_USER = "authorization.user";
public static final String NAME_USER = "用户";
}
public static final class ErrorCode {
public static final String USER_GROUP_ALREADY_ASSIGNED = "<REGISTERED_ERROR_1>";
}
}
默认允许的嵌套类:Urls、Resource、ErrorCode。按需增加:CacheKey、Topic、HeaderKey、PropertyKey。
不放:临时变量、日志文案、SQL 片段、展示文案、magic value。
Urls 约定
只定义模块的稳定资源路径:
- 优先复用上级常量组合路径
- 通用动作后缀复用
CoreConstant.Urls - 不重复声明框架已稳定的动作路径(
/enable、/disable、/order/bottom等)
Resource 约定
集中表达资源标识和资源展示名:
KEY_*使用小写英文和点分层,如authorization.userKEY_*子资源沿用点分层,如platform.tenant.configNAME_*放业务展示名,用于getResourceName()、OpenAPI 摘要和权限资源展示- 不把按钮文案放进
Resource - 不重复定义通用动作资源名(
create、update、delete等)
五、模块内 ErrorCode 落点
同一 {Object}Constant.ErrorCode 中的编号必须落在当前模块预留块号内,不混入根级负数编号。
抛出示例:
throw StatefulException.of(UserConstant.ErrorCode.USER_GROUP_ALREADY_ASSIGNED);
throw StatefulException.of(UserConstant.ErrorCode.BUILTIN_USER_ROLE_CANNOT_REMOVE, roleName);
与数据库约束消息的关系
唯一索引、外键等约束不建议为每个约束新增错误码:
- 错误码使用框架通用的数据重复或数据库约束错误,不为每个数据库约束新增模块私有错误码
- 唯一约束文案通过
{tableName}.uk.{dbConstraintName}映射 - 外键约束必须区分引用目标不存在、引用目标正在被使用两类语义
- 历史
constraint.*key 只作为兼容回退
示例:
isec_permission.uk.uk_isec_permission_key=权限标识已经存在
isec_role_permission.fk.not-found.fk_role_permission_role=角色不存在,不能建立角色权限关系
isec_role_permission.fk.in-use.fk_role_permission_role=角色数据被角色权限关系使用,不能删除或更新
约束 i18n key 常量化
唯一约束和外键约束的 i18n key 必须沉淀到模块 {Object}Constant 中,业务代码只引用常量,不在 Manager、Service、Repository 或 Mapper 中散落字符串字面量。
固定使用一个内部类:
public static final class I18nKey {
public static final String USER_ACCOUNT = "isec_user.uk.uk_isec_user_account";
public static final String USER_GROUP_USED_BY_USER =
"isec_user_group.fk.in-use.fk_isec_user_group_user_id";
private I18nKey() {
super();
}
}
使用示例。重复数据业务校验传实体类名和字段名,由 core 统一转换对象与字段文案;外键被占用类主动校验可以传 FK i18n key,直接输出对应约束文案:
throw StatefulException.of(ErrorCode.DATA_DUPLICATE, this.getEntityClass().getName(), "account");
throw StatefulException.of(ErrorCode.DATA_IN_USE, UserConstant.I18nKey.USER_GROUP_USED_BY_USER);
约束:
I18nKey只放代码需要主动引用的模块内 i18n key,当前主要包括唯一约束文案 key 和外键约束文案 key- UK 常量命名按业务含义表达唯一索引覆盖的字段,例如
APP_VERSION、USER_ACCOUNT - FK 常量命名按“引用关系 + 方向”表达,例如
ROLE_USED_BY_ROLE_PERMISSION - 常量值必须与
messages*.properties和message.multiproperties中的 key 完全一致 - 常量值必须以数据库真实表名开头,末段必须使用数据库真实约束名,不得改成字段名、Java 属性名或业务别名
- 数据库约束错误继续使用框架通用错误码,例如
ErrorCode.DATA_DUPLICATE、ErrorCode.DATA_IN_USE;不要为了每个 UK/FK 新增模块私有错误码
六、i18n 资源文件约定
目录结构
src/main/resources/
i18n/ineed/{module-segments}/
message.multiproperties
messages.properties
messages_en.properties
messages_zh_TW.properties
message.multiproperties是编辑源,多语言并排维护- 运行时实际加载的是
messages*.properties I18nConfiguration自动扫描classpath*:i18n/**/messages.properties
生成顺序(固定)
- Errors
- Common
- Entity name
- Entity fields
- Entity unique constraints
- Entity foreign-key constraints
message.multiproperties 中也必须使用同样顺序,通过 Comment 分组标注:Errors、Common、Entity - Xxx。
i18n Key 约定
错误码文案:key 直接使用错误码本身
<REGISTERED_ERROR_1>=用户已经分配到该分组,不允许重复分配
对象与字段文案:使用 {basePackage}.{Entity} 格式
com.ineed.config.dictionary.DictionaryDefinition=字典定义
com.ineed.config.dictionary.DictionaryDefinition.applicationKey=应用标识
{basePackage}是业务基础包,不包含contract.ao、jpa.entity、jdbc.entity、mybatisflex.entity、r2dbc.entity等实现分层片段{Entity}使用实体类简单名,保持 UpperCamelCase{fieldName}直接使用 Java 字段名- 对象和字段 key 必须跨
AO / Query / VO / Entity复用 - 新生成代码不得使用持久化实现类全名 key,例如
com.ineed.security.authorization.accesscontrol.jdbc.entity.Role.rolePurpose applicationKey仍用于META-INF/ineed/modules/*.json、权限permissionKey、菜单资源标识等业务标识,不作为对象/字段 i18n key 前缀
数据库约束文案:
{tableName}.uk.{dbConstraintName}=唯一约束文案
{tableName}.fk.not-found.{dbConstraintName}=引用目标不存在时的外键文案
{tableName}.fk.in-use.{dbConstraintName}=引用目标正在被使用时的外键文案
uk后缀、fk.not-found/fk.in-use后缀末段都必须使用数据库返回的真实约束名{tableName}必须使用数据库真实表名- 外键 key 中的
{tableName}必须是引用方表,也就是 FK 所在表;不要使用被引用目标表 not-found对应子表INSERT/UPDATE时引用的父表数据不存在,例如 MySQLCannot add or update a child rowin-use对应父表DELETE/UPDATE时仍被子表数据引用,例如 MySQLCannot delete or update a parent rowSQLException国际化解析会优先使用 SQL 错误中解析出的{tableName}精确查找;解析不到表名时才按约束名后缀兼容查找- 历史
{basePackage}.{Entity}.fk.{dbConstraintName}只作为兼容回退,新模块必须生成表名格式的两类语义化 FK key - 不再新增根级
constraint.*key
占位符格式
统一使用 MessageFormat 风格:{0}、{1}、{2}。不再新增 %1、%s、{}。
生成要求
创建新业务模块时,如果存在以下任一情况,应同时生成 {Object}Constant 与 i18n 资源:
- 新建了模块私有控制器资源根路径
- 新建了模块私有资源标识
- 新增了模块私有业务错误码
- 需要对象名、字段名、重复校验、校验失败的国际化文案
最少输出物:
{Object}Constant.javamessage.multipropertiesmessages.propertiesmessages_en.propertiesmessages_zh_TW.properties
生成顺序:先写 {Object}Constant → 接 Controller 注解 → 再补模块内 ErrorCode → 再补 message.multiproperties → 再补 messages*.properties。
七、历史兼容区间
i18n/ineed/error/v1/messages*.properties 作为历史兼容资源保留,状态为 legacy/frozen:
- 历史正数错误码可以继续保留
- 新模块不要参考历史编号无序追加
- 不要向
error/v1继续追加新错误码
八、自检清单
Urls里没有重复定义框架通用动作后缀Resource同时承载KEY_*稳定资源标识和NAME_*业务展示名- 通用错误继续复用根级
ErrorCode - 新增业务错误码不是负数,且是 6 位正整数
- 同一模块的新错误码落在同一个块号区间内
- 模块号位已登记到 design 全局错误码登记表,代码中的错误码落在登记区间内
- 模块私有业务错误码已配齐
message.multiproperties+ 三份messages*.properties - 新增文案占位符统一使用
{0}、{1}风格 - 对象名、字段名 i18n key 使用
{basePackage}.{Entity}格式,字段追加.{fieldName};新代码不得使用持久化实现类全名 key - 控制器没有直接写资源路径/资源标识字面量
- 所有出现在
messages*.properties的模块私有 key 已同步到message.multiproperties
九、I18n Java API 使用指南
架构分层
I18n 按调用栈分为三层,核心逻辑共享,locale 解析按同步/响应式分别处理:
ineed-core-base I18nResolver 通用核心(需显式传入 Locale)
I18nExceptionHandler 异常国际化(需显式传入 Locale)
ineed-core-context I18nHandler 同步包装(从 ThreadLocal 获取 locale)
└─ ContextHandler.getLocale() → OperateInfo.locale
ineed-core-context- ReactiveI18nHandler 响应式包装(从 Reactor Context 获取 locale)
reactive └─ ReactiveContextHandler.getLocale() → OperateInfo.locale
Locale 来源链路:请求头 Content-Language → OperateInfoRequestResolver 解析 → 写入 OperateInfo.locale → WebMvcOperateInfoFilter/WebFluxOperateInfoFilter 分别存入 ThreadLocal / Reactor Context。
各层调用场景
| 调用方所在模块 | 使用类 | 返回类型 |
|---|---|---|
| Controller / Service / Manager(同步) | I18nHandler |
直接返回值 |
| Controller / Service / Manager(响应式) | ReactiveI18nHandler |
Mono<T> |
| 纯逻辑工具、不依赖上下文 | I18nResolver(显式传 Locale) |
直接返回值 |
同步代码(WebMVC / Service / Manager)
import com.ineed.base.context.I18nHandler;
// 获取国际化消息
String message = I18nHandler.getMessage("error.code.key");
String messageWithArgs = I18nHandler.getMessage("parameter.incorrect", "昵称");
// 获取属性名称文案
String fieldLabel = I18nHandler.getPropertyMessage("com.ineed.demo.contract.ao.SampleAO", "nickname");
// 获取校验错误消息
String validationMsg = I18nHandler.getValidationMessage(validationResult);
// 获取重复数据错误消息
String duplicateMsg = I18nHandler.getDuplicateMessage(entity, Set.of("account", "mobileNumber"));
// 处理异常,返回国际化的 ErrorMessage
ErrorMessage error = I18nHandler.handleException(exception);
响应式代码(WebFlux)
import com.ineed.base.context.ReactiveI18nHandler;
// 所有方法返回 Mono,在响应式链中安全使用
Mono<String> message = ReactiveI18nHandler.getMessage("error.code.key");
Mono<String> fieldLabel = ReactiveI18nHandler.getPropertyMessage(
"com.ineed.demo.contract.ao.SampleAO", "nickname");
Mono<String> validationMsg = ReactiveI18nHandler.getValidationMessage(validationResult);
// 异常处理
ReactiveI18nHandler.handleException(exception)
.flatMap(errorMessage -> ...);
直接使用 I18nResolver(不依赖上下文)
仅当已有显式 Locale 且不需要从上下文获取时使用:
import com.ineed.base.i18n.I18nResolver;
String message = I18nResolver.getMessage("key", locale);
String property = I18nResolver.getPropertyMessage(className, property, locale);
String duplicate = I18nResolver.getDuplicateMessage(object, fields, locale);
String validation = I18nResolver.getValidationMessage(result, locale);
异常处理
I18nExceptionHandler 也需要显式 Locale。优先使用 I18nHandler.handleException() / ReactiveI18nHandler.handleException(),它们会自动从上下文获取 locale:
// 同步
ErrorMessage error = I18nHandler.handleException(exception);
// 响应式
Mono<ErrorMessage> error = ReactiveI18nHandler.handleException(exception);
仅在已有显式 Locale 时直接调用:
ErrorMessage error = I18nExceptionHandler.invokeException(exception, locale);
服务层与 Manager 层使用
I18nHandler 和 ReactiveI18nHandler 都放在 ineed-core-context / ineed-core-context-reactive 模块中,不被 web 层独占。Service 和 Manager 层可以直接依赖这两个 context 模块获取国际化消息,无需反向依赖 web 模块。
选择指引
- 同步链(WebMVC 请求线程、同步 Service/Manager):直接用
I18nHandler - 响应式链(WebFlux 请求、Reactor 链内的 Service/Manager):用
ReactiveI18nHandler,返回Mono<> - 没有请求上下文(定时任务、消息消费者、工具类):直接传
Locale给I18nResolver或I18nExceptionHandler.invokeException(exception, locale)
最后更新:2026-05-17
仍有疑问?按反馈清单整理复现信息 →