
Eliteforge Java Coding Spec
- 40 installs
- Updated July 24, 2026
- cloudsen/eliteforge-skills
Applies the EliteForge Java coding specification to guide and check how Java code is written and structured.
About
Enforces EliteForge Java coding conventions when writing or reviewing Java code. A developer uses it on projects that follow the EliteForge Java specification.
- EliteForge-specific Java coding rules
- Applies during authoring and review of Java code
Eliteforge Java Coding Spec by the numbers
- 40 all-time installs (skills.sh)
- Ranked #44 of 89 Java & JVM skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cloudsen/eliteforge-skills --skill eliteforge-java-coding-specAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 40 |
|---|---|
| Last updated | July 24, 2026 |
| Repository | cloudsen/eliteforge-skills ↗ |
What it does
Applies the EliteForge Java coding specification to guide and check how Java code is written and structured.
Files
EliteForge Java 编码规范 v1.0.0
目标
在 Java 开发任务中,依据规范提供明确的决策指引:什么应该做、什么禁止做、什么必须用什么工具/写法。引用规范条款时给出对应的 spec 行号。
执行原则
1. 命中即强制执行 规范中标注为"禁止"的条款,任何场景都不得妥协;标注为"必须"的条款不得降级替代。
2. 代码示例优先取规范原文 需要示例时,先从 references/java-coding-spec-v1.md 中提取对应条款的原文示例,不自行臆造。
3. 最小化加载 先用检索定位 spec 行号,再局部读取;避免整篇加载。
4. 明确标注来源 回答中标注对应的 spec 条款编号(如 §3.2 POJO类)。
快速检索
# 查看 spec 行号索引
cat references/toc.md
# 按关键字定位条款
rg -n "禁止|必须|推荐|使用|Spring|POJO|枚举|日志|事务|并发|MyBatis|服务间调用|Maven|国际化|网关|接口管理|工程" references/java-coding-spec-v1.md
# 按行号读取条款(示例:100-200行)
sed -n '100,200p' references/java-coding-spec-v1.md高频问题映射
| 用户问题 | 规范条款 | 核心结论 |
|---|---|---|
| 枚举怎么写 | §3.3 | Enum后缀 + implements BizEnum + @Getter/@RequiredArgsConstructor |
| POJO 类怎么写 | §3.2 | UpperCamelCase + Serializable + serialVersionUID + @Accessors(chain=true) |
| 工具类用什么 | §3.4 | Apache Commons / JDK / 统一框架工具,禁止 hutool |
| 日志怎么打 | §3.6 | @Slf4j + 占位符,禁止 e.printStackTrace() |
| 循环里能调接口吗 | §3.5 | 禁止,必须封装批量接口 |
| 线程池怎么创建 | §3.7 | ThreadPoolExecutor + TransmittableThreadLocal,禁止 Executors |
| MyBatis-Plus 怎么用 | §3.8 | 禁用注解,复杂查询用 xml,keepGlobalFormat=true |
| 事务怎么处理 | §3.9 | public 方法,seata TCC,围住最小必要代码 |
| RestTemplate 能用吗 | §3.10 | 禁止,必须用 RestClient |
| Bean 怎么注入 | §3.10 | @RequiredArgsConstructor,禁止 @Autowired |
| 服务间调用用什么 | §3.11 | HttpExchangeClient + RestClient,统一框架 starter |
| Maven 版本要求 | §3.12 | JDK21 + Maven 3.9.10+ + maven wrapper |
| Controller 怎么分层 | §7.2/§7.3 | Param入参/Vo出参,分离 api/innerapi/openapi |
| 接口路径前缀 | §7.1 | /api 前端 /inner-api 内部 /open-api 开放 |
| 数据库表设计 | §4 | 主键 id + 审计字段 + tenant_id/archived/version 按需 |
| 国际化文件放哪 | §7.6 | biz/resources/i18n/ |
| smart-doc 配置 | §6 | smart-doc.json / smart-doc-innerapi.json / smart-doc-openapi.json |
| 数据库适配 SQL 放哪 | §7.7 | resources/mapper/{mysql,dm,kingbase,oceanbase}/ |
核心约束速查
禁止清单(任何场景均不得妥协)
| 禁止项 | 必须替代 | 条款 |
|---|---|---|
| hutool | Apache Commons / JDK / 统一框架工具 | §3.4 |
| e.printStackTrace() / System.out | @Slf4j + log 占位符 | §3.6 |
| 循环/递归里调接口 | 批量接口封装 | §3.5 |
| Executors 创建线程池 | ThreadPoolExecutor | §3.7 |
| RestTemplate | Spring6 RestClient | §3.10 |
| @Value 注解读 yaml | Properties 类 | §3.10 |
| @Select/@Insert/@Update 注解 | MyBatis xml | §3.8 |
| context-path | yaml 路由配置 | §3.10 |
| lambdaQuery 多次单表查询 | xml 复杂查询 | §3.8 |
| Controller 层写业务逻辑 | Manager/Service 层 | §3.10 |
| @author JavaDoc | @since | §3.1 |
| 覆盖方式替换框架 Configuration | 扩展类或 yaml 配置 | §3.10 |
必须清单
| 必须项 | 说明 | 条款 |
|---|---|---|
| serialVersionUID | POJO 实现 Serializable 时必须定义 | §3.2 |
| @Accessors(chain=true) | Vo/Param/Dto 必须 | §3.2 |
| @since | 新增内容必须标识版本 | §3.1 |
| JSpecify 注解 | @Nullable/@NonNull/@NullMarked | §3.1 |
| 每成员 javadoc | POJO 枚举字段必须 | §3.1/§3.2/§3.3 |
| BizEnum | 前端/Entity 用枚举必须实现 | §3.3 |
| ResVo<T> | Controller 和服务间调用统一响应 | §3.2/§3.11 |
| HttpExchangeClient | 服务间调用必须 | §3.11 |
| keepGlobalFormat=true | @TableField 必须 | §3.8 |
| 函数式 wrapper | MyBatis-Plus wrapper 必须 | §3.8 |
| seata TCC | 分布式事务必须 | §3.9 |
| JDK21 + Maven 3.9.10+ | 必须版本 | §3.12 |
| maven wrapper | 必须使用项目 wrapper | §3.12 |
| 审计字段 | 业务表必须有 | §4 |
| api/inner-api/open-api 前缀 | Controller 路径必须 | §7.1 |
| 前端接口 Param入参/Vo出参 | Controller 必须 | §7.2 |
| 内部接口 Param入参/Dto出参 | InnerController 必须 | §7.2 |
| 开放接口独立 Dto/Param | OpenController 必须 | §7.2 |
| 并发修改加 version | 数据库必须有乐观锁字段 | §4 |
工具链速查
| 场景 | 工具 | 条款 |
|---|---|---|
| 字符串拼接 | java.lang.String.join() | §3.4 |
| 集合判空 | CollectionUtils.isEmpty() / MapUtils.isEmpty() | §3.4 |
| 数组判空 | ArrayUtils.isEmpty() | §3.4 |
| 字符串判空 | StringUtils.isBlank() / isNotBlank() | §3.4 |
| 字符串相等 | Strings.CS.equals() / Strings.CI.equals() | §3.4 |
| 二维元组 | org.apache.commons.lang3.tuple.Pair<L,R> | §3.4 |
| 时间工具 | cn.cisdigital.elite.forge.infra.commons.util.TimeUtils | §3.4 |
| JSON 工具 | JsonUtils | §3.4 |
| 国际化 | I18nUtils | §3.4 |
| 上下文 | ContextStore | §3.4 |
| 服务间调用 | cisdigital-elite-forge-infra-httpexchange-spring-boot3-starter | §3.11 |
| 线程上下文 | TransmittableThreadLocal + TtlExecutors | §3.7 |
| 日期格式化 | DateTimeFormatter(非 SimpleDateFormat) | §3.7 |
分层模型速查
POJO 类分层
Entity → ORM 实体映射,实现 Serializable
Dto → 内部数据传输
Param → Controller 入参,必须 jsr 303 validation
Vo → Controller 出参各层入参出参约束
| 层级 | 入参 | 出参 |
|---|---|---|
| repository | Param / Dto | Entity / Dto |
| service | Param / Dto | Dto / Vo |
| 前端接口 Controller | Param | Vo |
| 内部接口 InnerController | Param | Dto |
| 开放接口 OpenController | Param | Dto |
Maven 模块结构
<service>-model POJO + MapStruct
<service>-client HttpExchangeClient 接口定义
<service>-xxx-starter 业务相关 starter
<service>-xxx-sdk 业务相关 sdk
<service>-biz 业务模块(controller/service/repository)
<service>-boot 启动模块biz 代码分层
- biz/common/exception
- biz/common/util
- biz/模块A/controller/{innerapi, api, openapi}
- biz/模块A/service/xxxService
- biz/模块A/repository/{mapper/xxxMapper, xxxRepository}接口路径前缀
/api→ 前端接口(需网关鉴权)/inner-api→ 服务间内部调用(无需鉴权)/open-api→ 开放接口(需开放平台鉴权)
输出约束
- 回答时必须标注规范条款编号(§章节号)
- 涉及代码示例时,从
references/java-coding-spec-v1.md对应条款提取,不自行编写 - 涉及框架/工具选型时,优先引用统一框架能力,参见
eliteforge-framework-specification - 涉及版本号时引用规范原文,不臆造具体版本
- 规范未覆盖的场景,标注"需用户补充或需外部确认"
参考资料
- 规范全文(行号索引):
references/toc.md - 规范全文:
references/java-coding-spec-v1.md
interface:
display_name: "EliteForge Java编码规范"
short_description: "统一璀璨工坊 Java 编码规范,覆盖代码风格、POJO/枚举/工具类、日志、并发、MyBatis-Plus、事务、Spring、服务间调用、Maven、工程结构等。"
default_prompt: "根据EliteForge Java编码规范回答问题,涉及代码示例时从 references/ 对应条款提取,涉及框架能力时优先结合 eliteforge-framework-specification,禁止自行臆造示例或放松禁止条款。"
2 风格共识
2.1 文件风格
文件风格由 .editorconfig 确保:
- 缩进
- java: 4 个空格
- yaml: 2 个空格
- properties: 2 个空格
- xml: 2 个空格
- json: 2 个空格
- factories: 2 个空格
- Makefile: tab
- 去掉多余的前后空格
- 文件最后一行为空行
- 每一行最大长度:150
- 换行符: LF
- 文件编码:UTF-8
2.2 代码风格
采用自定义的 eclipse jdt format 统一格式化 Java 代码,通过 spotless-maven-plugin 确保代码能够有效格式化。
2.3 Import 风格
vscode 配置
"java.sources.organizeImports.starThreshold": 5,
"java.sources.organizeImports.staticStarThreshold": 3,
"java.completion.importOrder": [
"",
"javax",
"java",
"#"
],3 编码共识
3.1 代码注释
- 标识可以为空和不能为空使用
JSpecify的注解,如:@NonNull、@Nullable、@NullMarked - 类、类属性、类方法的注释(特别注意枚举、POJO 类、接口、抽象类)必须使用 Javadoc 规范,使用
/** 内容 */格式 - JavaDoc 只允许使用标准标签,和smartdoc 的特殊标签
- 合理通过 html 标签,保证换行、代码链接等格式
- 禁用
@authorJavaDoc - 使用
@since标识新版本添加的内容,值为增加次功能的迭代版本
```java title="标准类注释" /**
- 国际化资源加载策略
*
- <p>
- 支持读取jar包及项目内的国际化资源加载器
- </p>
*
- @since 1.0.0
*/ @Slf4j public class ProjectResourceBundleMessageSource extends ResourceBundleMessageSource {}
/**
- 检查给定的布尔条件,如果不满足条件则抛出业务异常{@link BusinessException}
*
- @param condition 布尔计算的结果
- @param errorCode 错误码枚举 {@link cn.cisdigital.datakits.framework.model.interfaces.ErrorCode}
- @param msgFormats 消息模板
- @since 1.0.0
*/ public void checkArgument(boolean condition, ErrorCode errorCode, Object... msgFormats) {}
### 3.2 POJO 类
- Controller 的请求响应统一使用 `cn.cisdigital.elite.forge.infra.commons.model.vo.ResVo<T>` 对象,如,`ResVo<UserVo>`
- 分页 Param 参数类,直接继承 Mybaits-Plus 的 `com.baomidou.mybatisplus.extension.plugins.pagination.Page<T>` 类,避免额外转换
- 类名使用 UpperCamelCase 风格,领域后缀也准守,如,UserDto 而不是 UserDTO
- 必须实现 Serializable,并定义`serialVersionUID`
- 当类的结构发生变化,更新 `serialVersionUID`
- Vo、Param、Dto 类上使用 `@Accessors(chain = true)` 注解
- 复杂 POJO 类上使用 `@Builder` 注解
- Validation 注解的 message 须写国际化 KEY
- POJO 类中的任何布尔类型(基础类型)的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误
- 没特殊需求时,字段必须使用包装数据类型
- 每个成员都要有 javadoc 注释
/**
- PromQL时刻查询参数
*
- @since 1.0.0
*/ @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class InstantQueryParam implements Serializable {
private static final long serialVersionUID = 1L;
/**
- 波塞冬项目英文名
*
- @since 1.0.0
*/ @NotBlank(message = "resource.meter.valid.project_not_blank") private String project;
/**
- 查询超时时间
*
- @since 1.0.0
*/ private Duration timeout; }
### 3.3 枚举类
- 类名带上 `Enum` 后缀
- 给前端接口和 Entity 类使用的枚举类,必须 `implements BizEnum`
- `messageKey` 字段使用 `国际化KEY`
- 变量全为 final 类型
- 成员名称需要全大写
- 单词间用下划线隔开
- 每个成员都要有 javadoc 注释,特别简单意思明确的可以不写
- 使用@Getter 和@RequiredArgsConstructor 注解
/**
- 环境枚举
*
- @since 1.0.0
*/ @Getter @RequiredArgsConstructor public enum EnvironmentEnum implements BizEnum {
/**
- 1 生产环境
*
- @since 1.0.0
/ PROD(1, "datakits.framework.model.env.prod"), /*
- 2 开发环境
*
- @since 1.0.0
*/ DEV(2, "datakits.framework.model.env.dev"), ;
private final int code; private final String messageKey;
// 可以自行添加一些静态方法 }
### 3.4 工具类
- 造轮子前,先在开发群里问一下有没有类似的,或者开源类似的能力,**不要重复造轮子**
- 禁用 hutool。Apache 、Google 、Spring 发布的工具包已经够用
- 优先使用 JDK 自带的工具类, 如拼接字符串,可以直接 `java.lang.String.join(xxx)`
- 判断元素是否存在,统一用 `apache commons` :
- 判断对象是否存在,可以 object == null、object != null 或 Objects.isNull(object)、Objects.noneNull(object) 或 Optional
- 判断集合是否存在,统一用 apache commons collection4 工具类:CollectionUtils.isEmpty(collection)、MapUtils.isEmpty(map)
- 判断数组是否存在,统一用 apache commons lang3 工具类:ArrayUtils.isEmpty(array)、ArrayUtils.isNotEmpty(array)
- 判断 String 是否存在,统一用 apache commons lang3 工具类:StringUtils.isBlank(str)、StringUtils.isNotBlank(str)
- 判断字符串相等:
- 大小写敏感:`org.apache.commons.lang3.Strings.CS.equals(a, b)`
- 大小写不敏感:`org.apache.commons.lang3.Strings.CI.equals(a, b)`
- 二维元组使用:`org.apache.commons.lang3.tuple.Pair<L, R>`
- 时间工具:`cn.cisdigital.elite.forge.infra.commons.util.TimeUtils`
- Spring 上下文工具:`cn.cisdigital.elite.forge.infra.commons.util.SpringUtils`
- 序列化工具:`cn.cisdigital.elite.forge.infra.commons.util.JsonUtils`
- 国际化工具:`cn.cisdigital.elite.forge.infra.commons.util.I18nUtils`
- 上下文信息: `cn.cisdigital.elite.forge.infra.commons.context.ContextStore`
### 3.5 控制语句
- 非特殊情况下,禁止在循环和递归里调用其他服务的接口、对单个元素的查询等操作,必须封装批量处理接口
- 禁止复杂嵌套,如果条件不满足,应该立即中止,避免无意义的缩进
public void complexCheck(UserDto userDto) { if(userDto == null) { return; } // 后续逻辑 }
- 将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性
boolean invalidProjectName = StringUtils.isNotBlank(projectNamePrefix) && StringUtils.isNotBlank(property.getProjectName()) && !property.getProjectName().startsWith(projectNamePrefix); if (invalidProjectName) { throw new RuntimeException("项目名错误!后端项目名必须以" + projectNamePrefix + "开头"); }
### 3.6 日志打印
- 使用 `logback` 作为日志框架
- 通过 `@Slf4j` lombok 注解和 `log` 对象来打印日志
- 传统部署方式下,INFO 和 ERROR 分别输入到不同的日志文件;容器化部署方式下,全部日志只打印到控制台
- 禁止出现 e.printStackTrace()、System.out 相关的日志打印
- 禁止吞并原始异常信息
- 核心业务流程的日志,必须带有唯一识别的关键字,方便直接过滤出来排查问题
- 希望某个特定模块的日志只写入一个独立的日志文件,而不希望它与主日志文件混在一起时,配置 `additivity="false"`
<logger name="com.example.moduleA" level="DEBUG" additivity="false"> <appender-ref ref="MODULE_A_FILE" /> </logger>
- 涉及外部组件的日志必须打印必要的上下文信息
public static fianl String MQ_LOG_TEMPLATE = "[{}] 接收到mq消息, broker={}, topic={}, group={}, message={}"; // 消息队列 log.debug(MQ_LOG_TEMPLATE, "数据质量扫描结果", nameServer, topic, group, message);
- 对 trace/debug 级别的日志输出,不允许字符串拼接,必须使用条件输出形式或者使用占位符的方式,避免性能损耗
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
- **沉默是金**,非必要的日志都使用 `debug` 级别,出问题时通过 `actuator`动态调整日志级别
> 写之前请思考:这些日志真的有人看吗?看到这条日志能不能给问题排查带来好处?
curl --location --request POST 'http://<host>/actuator/loggers/cn.cisdigital.datakits.di.task.biz.repository.mapper' \ --header 'Content-Type: application/json' \ --data '{ "configuredLevel": "DEBUG" }'
### 3.7 并发处理
- 线程安全的类,标记 `javax.annotation.concurrent.ThreadSafe` 注解,没标记的类都当作线程不安全
- 线程资源必须通过线程池提供,不允许直接使用 Executors 去创建(容易 OOM),而是通过 ThreadPoolExecutor 的方式
- 需要在线程之间传递上下文时,使用 `TransmittableThreadLocal` 和 `TtlExecutors`
- 需要在线程池中的线程之间,传递上下文时,注意包装ttl
- 创建线程池时,指定有意义的线程名称
- 创建线程池时,需要提供 `Micrometer监控指标`
- 单例类必须无状态,其中的方法也都要线程安全
- 使用 `DateTimeFormatter` 而不是 `SimpleDateFormat`
import com.alibaba.ttl.TtlRunnable; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.TaskDecorator; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
- 应用自己定义的异步线程池
*/ @Configuration public class AsyncExecutorConfig {
@Bean public TaskDecorator ttlTaskDecorator() { return runnable -> TtlRunnable.get(runnable, true, true); }
@Bean("applicationTaskExecutor") public ThreadPoolTaskExecutor applicationTaskExecutor(TaskDecorator ttlTaskDecorator) { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(8); executor.setMaxPoolSize(32); executor.setQueueCapacity(200); executor.setThreadNamePrefix("app-"); executor.setTaskDecorator(ttlTaskDecorator); executor.initialize(); return executor; } }
### 3.8 Mybatis-Plus
- 禁止用 lambdaQuery 进行多次的单表查询,复杂查询必须使用 xml 编写 sql,一次性查出需要的数据,避免性能问题
- xml 中,没有特殊需求不要用 resultMap 映射,直接用 `resultType` 映射
- `@TableField` 中,必须**开启全局格式化,且字段名不能包裹引用符**。如:@TableField("`meta_id`")改为@TableField(value = "meta_id", keepGlobalFormat = true)
- 在 mapper 文件夹下面,创建与数据库同名的文件夹(如 mysql,dm),然后适配所有文件夹中的 sql
- 禁用 `@Select`、`@Insert`、`@Update` 注解
- wrapper 必须用函数式接口,禁止用 String 传字段参数
- xml 中的sql,如果有用到枚举,必须用 ognl 表达式,不能直接写数字
wt.state = ${@cn.cisdigital.datakits.framework.workflow.abs.constant.TaskInstanceStateEnum@DOING.getCode}
### 3.9 事务
- 事务注解加在 public 方法
- 非必要,不用分布式事务
- 分布式事务使用 seata 实现,只允许使用 TCC 事务模式,长事务用 SAGA 模式,尽最大可能避免长事务
- 事务只围住最小必要的数据库读写代码,耗时等无关操作必须从事务脱离
- 非必要优先使用乐观锁(数据库中的version 字段)
### 3.10 Spring
- 禁用 `context-path`,防止融合部署时,url 冲突
- 禁止使用 `RestTemplate` ,使用 Spring6 新的 RestClient 或 WebClient
- Bean 注入统一用 `@RequiredArgsConstructor` 完成构造器注入
- 无特殊情况不要用原生设计模式,要与 Spring 的 IOC 和依赖注入结合
- 禁止在 controller 层写逻辑代码
- 禁止使用 `@Value` 注解读取 yaml 配置参数,必须写 `Properties` 类
- 有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务
- 禁止以覆盖的方式,替换底层框架相关的 Configuration。只允许通过扩展类或者 yaml 配置方式修改底层框架行为
@Slf4j @Component @RequiredArgsConstructor public class A { private final B b; }
/**
- 告警适配器管理器
*
- @since 1.0.0
*/ @Slf4j @Component public class AlertAdaptorManager {
private static final String ALET_LOG_TEMPLATE = "[ ALERT ] 收到告警消息, provider={}, converter={}, handler={}";
/**
- Key:适配器的名字,Value:告警适配器实现
*/ private final Map<String, AlertAdaptor<?>> handlers;
public AlertAdaptorManager(List<AlertAdaptor<?>> handlers) { this.handlers = handlers.stream() .collect(Collectors.toMap(AlertAdaptor::name, Function.identity())); }
/**
- 告警处理统一入口
*
- @param provider 告警消息生产者
- @param converter 告警消息转换器名字
- @param handler 告警消息处理器名字
- @param msg 告警消息
- @since 1.0.0
*/ @SuppressWarnings("unchecked") public <T> void handle(String provider, String converter, String handler, Map<String, Object> msg) { log.info(ALET_LOG_TEMPLATE, provider, converter, handler); AlertAdaptor<T> adaptor = (AlertAdaptor<T>) handlers.get(provider); if (adaptor == null) { throw new AlertException(AlertErrorCode.ALERT_ADAPTOR_NOT_FOUND); } adaptor.adapt(converter, handler, adaptor.parseMessage(msg)); } }
### 3.11 服务间调用
- 服务间调用采用统一框架提供的 `cisdigital-elite-forge-infra-httpexchange-spring-boot3-starter`
- 服务间调用底层使用 RestClient
- 服务间调用接口定义只允许使用以下注解
- @RequestHeader:给请求添加 header,但不覆盖已有值的 header
- @RequestBody:post 请求的请求体
- @RequestParam:get 请求参数,不要超过 3 个字段
- @RequestPart:多部分请求中的一个部分,主要是处理文件片段
- `@HttpExchangeClient` 注解中的 `name` 属性,需要指定为波塞冬上的应用名,非特殊情况, 波塞冬上的应用名=后端项目名
- `@HttpExchangeClient` 注解中的 `path` 属性,需要指定为 `/产品前缀/服务名前缀`
- `@HttpExchangeClient` 注解中的 `mockUrl` 属性,需要指定为Torna接口管理平台,对应接口的mock地址
- 接口返回值,跟 Controller 层保持一致,都使用 `ResVo` 作为统一响应
- 服务间调用会透传业务网管中定义的 header 信息,其中 `App-Id` 会取 `@HttpExchangeClient` 注解的 `name` 属性发送给下游服务
/**
- xxx服务,xxx模块的内部接口定义
*
- @since 1.0.0
*/ @HttpExchangeClient(name = "app-cisdigital-base-foundation", path = "/base/foundation/inner-api", mockUrl="tornar mock 接口地址") public interface FirstTestClient {
/**
- 获取xxx
*
- @param name 姓名
- @since 1.0.0
*/ @GetExchange("/xxx") ResVo<String> getXXX(@RequestParam("name") String name); }
### 3.12 Maven
- 使用 JDK21 驱动 Maven
- Maven 版本 3.9.10+
- 使用语义化版本 `主版本号.次版本号.修订版本号`,开发中的版本添加 `-SNAPSHOT` 后缀,正式版不带任何后缀
- 必须使用项目中的 maven wrapper
- 引入外部 jar 包,必须导入公司的外部依赖统一管理,不允许覆盖依赖 version
- 引入的业务模块,必须在根 pom 中的 dependencyManagement 管理依赖版本
- 正式版本必须排除所有 SNAPSHOT 依赖
- 使用 `flatten` 插件,配合 `revision` 变量, 统一多模块的版本
- 禁止私自处理依赖冲突,请联系技术委员会处理
### 3.13 泛型类型
严格按照以下规范使用泛型类型:
- T: Type(JAVA 类)通用泛型类型,通常作为第一个泛型类型
- S: 通用泛型类型,如果需要使用多个泛型类型,可以将 S 作为第二个泛型类型
- U: 通用泛型类型,如果需要使用多个泛型类型,可以将 U 作为第三个泛型类型
- V: 通用泛型类型,如果需要使用多个泛型类型,可以将 V 作为第四个泛型类型
- E: 集合元素泛型类型,主要用于定义集合泛型类型
- K: 映射-键泛型类型,主要用于定义映射泛型类型
- V: 映射-值泛型类型,主要用于定义映射泛型类型
- N: Number 数值泛型类型,主要用于定义数值类型的泛型类型
- ?: 表示不确定的 JAVA 类型
## 4 数据库共识
- 没有唯一编码的数据之间,关联关系通过主键 id 进行关联
- [建议] 有唯一编码的数据之间,关联关系通过唯一编码进行关联
> 如,血缘数据涉及重新采集,不存在固定 id,应使用 code 进行关联
- sql 脚本中的 DDL&DML 不可以携带 SCHEMA
- Entity 使用枚举时,数据库存储类型必须与枚举的字段类型一致
> 通过枚举最终存储的是整形,但数据库设置的字符串,mybaits 框架用人大金仓时就会报数据转换错误
- 业务相关的数据,非必要不使用数据库自增 id(在描述中说明使用自增 id 的原因)
- [建议] 推荐使用雪花 id、时间序列 id 等,可根据业务自行扩展
- 业务表必须有主键字段和以下几个审计字段,关联表可不做要求:
- `id`: 主键,64 位整数
- `create_time`:时间类型,创建时间
- `update_time`:时间类型,更新时间
- `create_by`: 字符串类型,长度 32,创建人 id
- `create_name`: 字符串类型,长度 64,创建人姓名
- `update_by`:字符串类型,长度 32,更新人 id
- `update_name`: 字符串类型,长度 64,更新人姓名
- 严禁把审计字段运用到业务逻辑中,业务逻辑涉及到人员的,都要新增字段存储
- 有租户的情况,需要考虑添加 `tenant_id` 字段
- 有删除的情况,需要考虑添加 `archived` 逻辑删除
- 有并发修改的情况,需要考虑增加 `version` 乐观锁版本,代码中执行更新时要添加 version 条件
- [建议] 若要充分享受代码生成器的便利,数据库存储枚举的字段要严格按照此模式
- 字段描述中添加:`[枚举]` 这个前缀,方便代码生成器定位
- 描述字段枚举的值使用 JSON 格式:`{"code":"中文描述","code":"中文描述",...}`
- Java 中的枚举名 = 转驼峰(数据库中枚举字段名)Enum
- 完整案例,如,表示任务状态的数据库字段 job_state,字段描述为:`[枚举]{'stop':'停止(0)','filish':'完成(1)','process':'执行中(2)'}`,对应的 Java 类是 modle 模块下 enums 包中的 JobStateEnum
- 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引
- 数据订正(特别是删除或修改记录操作)时,要先 select,确认无误才能执行更新语句,并准备好回滚措施
## 5 网关共识
- 前端的请求路径应该是: `/<业务网关前缀>/<产品名>/<服务名缩写>/api/<queryPath>`
- yaml 路由配置做以下约束:
- 每个服务都需要有唯一的路由前缀(predicates.path): `/<产品前缀>/<服务名缩写>/**`
- 路由 ID 必须使用服务全名作为前缀,如: app-datakits-resource, app-datakits-resource-2, app-datakits-resource-备份
门户中心-前端接口
- id: app-cisdigital-base-foundation
uri: http://app-cisdigital-base-foundation:8080 predicates:
- Path=/base/foundation/api/**
### 5.1 业务网关
功能如下:
- 生成 request-id
- 校验平台用户 token
- 设置请求 header
| Header 名 | 说明 | 来源 |
| :---------: | :--------------------------------------------: | :------------------------------: |
| Request-Id | 请求链路 ID | 链路追踪 |
| User-Id | 用户唯一标识,用于数据库查询、日志记录 | 当前登录用户 |
| User-Name | 用户的用户名,用于显示和日志记录 | 当前登录用户 |
| User-Code | 用户的用户编码,用于显示和日志记录 | 当前登录用户 |
| Org-Id | 用户当前登录的管理组织,唯一标识,用于业务隔离 | 当前登录用户 |
| Org-Code | 用户当前登录的管理组织编码,用于业务隔离 | 当前登录用户 |
| Tenant-Id | 租户 ID | 默认值 |
| Menu-Id | 菜单 ID,用于控制数据权限,门户前端 | 门户前端 |
| App-Id | 应用 ID,与后端 `spring.application.name` 一致 | 前端应用 |
| Super-Admin | 是否为超级管理员(boolean) | 当前登录用户 |
| Env | 运行环境(dev、prod 等) | 运行参数,用于大数据平台资源隔离 |
## 6 接口管理共识
### 6.1 管理模式
- 使用 smart-doc 配合 Torna 完成接口管理
- 通过 Torna 项目,分别管理某个产品的前端接口、内部接口、开放接口,方便给不同用户授予访问权限

- 在项目根目录有三个配置文件用于区分不同类型的接口上报
- `smart-doc.json` :前端接口上报
- `smart-doc-innerapi.json` :服务间内部接口上报
- `smart-doc-openapi.json` :开放接口上报
### 6.2 smartdoc 配置
参考 `smart-doc.json` 配置:
{ "projectName": "当前项目名", "openUrl": "torna地址", "appToken": "【注意】前端接口项目,对应的应用token", "pathPrefix": "指定接口的前缀,参考网关共识,如,/base/foundation", "packageFilters": "【注意】api的包路径",
"outPath": "target/api-docs", "isStrict": true, "showAuthor": false, "inlineEnum": true, "tornaDebug": true, "replace": true }
参考 `smart-doc-innerapi.json` 配置:
{ "projectName": "当前项目名", "openUrl": "torna地址", "appToken": "【注意】内部接口项目,对应的应用token", "pathPrefix": "指定接口的前缀,参考网关共识,如,/base/foundation", "packageFilters": "【注意】innerApi的包路径",
"outPath": "target/api-docs", "isStrict": true, "showAuthor": false, "inlineEnum": true, "tornaDebug": true, "replace": true }
参考 `smart-doc-openapi.json` 配置:
{ "projectName": "当前项目名", "openUrl": "torna地址", "appToken": "【注意】开放接口项目,对应的应用token", "pathPrefix": "指定接口的前缀,参考网关共识,如,/base/foundation", "packageFilters": "【注意】openApi的包路径",
"outPath": "target/api-docs", "isStrict": true, "showAuthor": false, "inlineEnum": true, "tornaDebug": true, "replace": true }
### 6.3 上报接口
上报前端接口:
make doc
上报内部调用接口:
make doc-innerapi
上报开放接口:
make doc-openapi
## 7 工程共识
> 对内:指针对内部平台、内部微服务
> 对外:指通过开放平台的形式,对外部环境提供能力
### 7.1 接口前缀
- controller 层每个接口路径需添加服务前缀,服务前缀通过常量管理,前缀遵循业务网关共识
@RequiredArgsConstructor @RestController @RequestMapping(ApiConstants.SERVER_PREFIX + "/api/alert") public class AlertController {
private final AlertManager alertManager;
@PostMapping("/checkDorisAvailable") public ResVo<Void> checkDorisAvailable() { alertManager.checkDorisAvailable(); return ResVo.ok(); }
}·
- 根据接口的使用方不同,controller 层每个接口路径需添加前:
- /api 前端交互接口,需要在 API 网关认证用户token
- /inner-api 服务间内部调用接口,无需认证
- /open-api 对外开放接口,需要在 `混合集成平台开放网关` 或 `能力管理中心` 认证应用信息
- [建议] 通过/v1,/v2 这种前缀来兼容新老接口
### 7.2 领域模型
> 需求和产品都不明确,所以现阶段无法采用 DDD,盲目 DDD 只会越来越难维护
- 项目用传统 MVC,贫血模式开发
- 项目里只使用四种 POJO 类:Entity、Dto、Param、Vo
> ORM 实体:Entity;数据传输对象(Controller 层接收):Param;数据展示对象(Controller 层返回):Vo;
- 除了 Entity 都必须实现 Serializable 接口
- POJO 类必须加上 jsr 303 validation 注解
- repository 入参可以是 Param 或 Dto,出参只能是 Entity 或者 Dto
- service 入参只能是 Param 和 Dto,出参只能是 Dto 或 Vo
> service 入参是 Param 的话,接口只能在 controller 使用,其他地方不能使用改方法。
> service 入参是 Dto 的话,只能在 controller 以下的层级使用
- 前端接口 controller 入参只能是 Param,出参只能是 Vo
- 内部接口 innerController 入参只能是 Param,出参只能是 Dto
- 开放接口 openController 入参只能是 Param,出参只能是 Dto
- 开放接口的Dto、Param、Service都需要使用单独的一套,不要复用前端接口和内部接口。
### 7.3 Maven 模块与代码分层
> maven 命名遵守 [命名规范](/elite-forge/code-specification/name/v1/)
生成器提供以下 maven 模块:
- <后端项目名>
- <service>-model: pojo模型与mapstruct转换器
- <service>-client: 需要发布的内部接口,使用Spring6 HttpExchange定义
- <service>-xxx-starter:需要发布的业务相关的starter
- <service>-xxx-sdk: 需要发布的业务相关的sdk
- <service>-biz: 高内聚的业务模块,里面再根据业务划分不同的java package
- <service>-boot: 启动模块,存放启动类和yaml配置
- test-aggregate: 输出聚合测试分析结果
代码分层如下:
- <service>-client:
- xxxClient
- yyyClient
- <service>-biz:
- common
- exception:该模块定义的异常类
- util:该模块定义的工具类
- 模块A
- controller
- innerapi
- xxxInnerController implements xxxClient 服务间内部调用的接口,不需要鉴权
- api
- xxxController 给前端使用的接口,需要在API网关进行统一鉴权
- openapi
- xxxOpenController 对外提供的开放能力,需要在开放平台网关进行统一鉴权,对业务代码进行二次封装后,保证向下兼容且稳定
- service
- xxxService: 业务服务类,注入repository实现类,直接写实现类不用定义接口
- repository
- mapper
- xxxMapper: mybatis mapper
- xxxRepository: orm服务类,直接写实现类不用定义接口
- <service>-model: POJO和Map-Struct转换
- common
- converter: map-struct的转换类
- dto: 内部传输类
- param: controller入参
- entity: orm实体映射POJO类
- vo:controller出参
- enums:枚举类
- 模块A
- converter: map-struct的转换类
- dto: 内部传输类
- param: controller入参
- entity: orm实体映射POJO类
- vo:controller出参
- enums:枚举类
### 7.4 项目文件说明
. ├── .editorconfig 编辑器配置 ├── .gitignore git忽略文件配置 ├── .gitlab gitlab仓库配置 ├──├── merge_request_templates MR默认模版 ├── .gitlab-ci.yml ci远程配置 ├── .mvn maven wrapper配置 ├── .pre-commit-config.yaml 本地校验配置 ├── xxx-biz 业务代码模块 ├── xxx-boot 服务启动模块 ├── xxx-model 领域模型模块 ├── xxx-client 服务间调用接口模块 ├── lombok.config lombok配置文件 ├── Makefile 封装了常用的项目构建命令 ├── mvnw maven wrapper命令 ├── mvnw.cmd maven wrapper命令 for windows └── readme.md 项目说明
### 7.5 用户&服务鉴权
- 内部服务间调用,采用 `零信任` 机制,所有服务间调用都需要简单的进行内部鉴权,此能力由服务网格提供
- 禁止微服务自己对用户 token 进行解析和鉴权,统一从网关层鉴权,鉴权信息由网关设置到 http header 中,微服务通过拦截器获取 header 中的鉴权信息
- 服务间调用透传网关的所有 header
### 7.6 国际化
> KEY 的命名遵守 [命名规范](/elite-forge/code-specification/name/v1/)
国际化文件存放在 `biz模块` 下的,`resources/i18n` 目录下:
- <项目名>.properties
- <项目名>_en_US.properties
- <项目名>_zh_CN.properties
开发时,需要同时写 `<项目名>.properties` 和 `<项目名>_zh_CN.properties` 两个文件
### 7.7 国产化
#### 7.7.1 数据库适配
根据项目需求,自行适配不同的数据库。
SQL 文件存放在 biz 模块下的,`resources/mapper/数据库` 目录下:
- mysql: 存放 mysql 数据库的 sql xml
- dm: 存放达梦数据库的 sql xml
- kingbase: 存放人大金仓的 sql xml
- oceanbase: 存放 OB 的 sql xml
#### 7.7.2 中间件适配
使用框架提供的防腐层,避免直接使用某个中间件的 Client,如 RedissonClient。
#### 7.7.3 硬件适配
需要在国产化指定的服务器环境中,构建成功,主流程回归测试完毕。
Java 编码规范 v1.0.0 - 条款索引
| 行号 | 章节 | 条款 |
|---|---|---|
| 5 | §2 风格共识 | 2.1 文件风格(.editorconfig 缩进/换行/编码) |
| 25 | §2 风格共识 | 2.2 代码风格(eclipse jdt + spotless) |
| 29 | §2 风格共识 | 2.3 Import 风格(VSCode/IDEA 配置) |
| 45 | §3 编码共识 | 3.1 代码注释(JSpecify/Javadoc/@since/@author禁止) |
| 80 | §3 编码共识 | 3.2 POJO 类(ResVo/Serializable/@Accessors/@Builder/UpperCamelCase) |
| 125 | §3 编码共识 | 3.3 枚举类(Enum后缀/BizEnum/@Getter/@RequiredArgsConstructor) |
| 167 | §3 编码共识 | 3.4 工具类(禁止hutool/ApacheCommons/统一框架工具) |
| 187 | §3 编码共识 | 3.5 控制语句(禁止循环调用接口/复杂嵌套) |
| 213 | §3 编码共识 | 3.6 日志打印(@Slf4j/占位符/禁止e.printStackTrace/核心日志关键字) |
| 254 | §3 编码共识 | 3.7 并发处理(@ThreadSafe/ThreadPoolExecutor/TTL/Micrometer) |
| 299 | §3 编码共识 | 3.8 MyBatis-Plus(禁止注解/复杂查询xml/keepGlobalFormat/ognl) |
| 313 | §3 编码共识 | 3.9 事务(public方法/seata TCC/最小范围/乐观锁) |
| 321 | §3 编码共识 | 3.10 Spring(禁止RestTemplate/@RequiredArgsConstructor/禁止controller写逻辑) |
| 384 | §3 编码共识 | 3.11 服务间调用(HttpExchangeClient/@GetExchange/@PostExchange/ResVo) |
| 419 | §3 编码共识 | 3.12 Maven(JDK21/3.9.10+/wrapper/flatten/revision) |
| 431 | §3 编码共识 | 3.13 泛型类型(T/S/U/V/E/K/N/?) |
| 445 | §4 数据库共识 | 主键id/审计字段/tenant_id/archived/version/唯一索引 |
| 467 | §4 数据库共识 | 数据库枚举存储规范([枚举]前缀/JSON格式/字段名转驼峰) |
| 475 | §5 网关共识 | 5 请求路径格式 /<业务网关前缀>/<产品名>/<服务名缩写>/api/ |
| 491 | §5 网关共识 | 5.1 业务网关 Header(Request-Id/User-Id/Tenant-Id/App-Id等) |
| 513 | §6 接口管理 | 6.1 smart-doc + Torna 模式 |
| 527 | §6 接口管理 | 6.2 smart-doc 三种配置文件 |
| 586 | §6 接口管理 | 6.3 上报命令(make doc / doc-innerapi / doc-openapi) |
| 606 | §7 工程共识 | 7.1 接口前缀(/api /inner-api /open-api) |
| 640 | §7 工程共识 | 7.2 领域模型(MVC贫血/四种POJO/分层入参出参约束) |
| 658 | §7 工程共识 | 7.3 Maven模块与代码分层(model/client/biz/boot) |
| 716 | §7 工程共识 | 7.4 项目文件说明 |
| 738 | §7 工程共识 | 7.5 用户&服务鉴权(零信任/网关鉴权/header透传) |
| 744 | §7 工程共识 | 7.6 国际化(i18n目录/中英文件同时写) |
| 756 | §7 工程共识 | 7.7 国产化(数据库适配/中间件防腐层/硬件适配) |