梁华东
Published on 2025-12-11 / 39 Visits
0
0

IMOM 开发规范和指引

IMOM技术实现部

1. 前言

1.1. 目的

本规范旨在为后端开发团队提供统一的开发标准和指导,确保代码质量的一致性、可读性和可 维护性。 通过规范化的开发流程和编码标准,减少因代码风格不一致、架构设计混乱等问题 带来的沟通成本和开发成本,从而提升开发效率,减少潜在的安全漏洞和维护风险。

1.2. 范围

本规范适用于所有参与后端开发的开发人员、代码审查人员、项目管理者以及其他相关技术人 员,涵盖从编码风格、项目结构、接口设计、安全规范到性能优化等多个方面。在项目的整个 生命周期内,包括开发、测试、上线、维护等环节均应严格遵循本规范。

1.3. 益处

● 提高代码质量:统一的编码规范减少低质量代码的产生,提高代码的可读性和可维护性。

● 保障系统安全:通过规范化的安全处理和数据保护措施,避免常见的安全漏洞,确保系统 的安全性。

● 提升开发效率:标准化的项目结构和清晰的编码风格使团队成员能够快速上手,减少沟通 成本。

● 便于代码审查和协作:代码审查更加高效,通过一致的编码规范降低了审查难度,增强了 团队协作效果。

● 支持系统扩展与升级:良好的代码结构和模块化设计便于系统后续的扩展与升级,降低了 维护成本。

2. 工程架构

2.1. 目录结构

  1. 【强制】工程根目录mbm-mom-[服务名]

正例:

mbm-mom-mpm/
  1. 【推荐】子模块命名与结构
  1. [服务名]-api/

说明:API 子模块,负责暴露服务接口,进行外部系统的交互

  1. [服务名]-biz/

说明:业务逻辑层模块,处理具体的业务逻辑和规则

正例:

├── mbm-mom-mpm/
   ├── mpm-api //API接口
   ├── mpm-biz //业务逻辑实现
  1. 【强制】包名结构

正例:

├── mbm-mom-mpm/
   ├── mpm-api
       ├──com.sie.mbm.mom.mpm
           ├──com.sie.mbm.mom.mpm.product
               ├──com.sie.mbm.mom.mpm.product.dto //传输对象
               ...
   ├── mpm-biz
       ├──com.sie.mbm.mom.mpm
           ├──com.sie.mbm.mom.mpm.product //业务模块-产品
               ├──com.sie.mbm.mom.mpm.controller //控制器
               ├──com.sie.mbm.mom.mpm.convert //对象映射转换
               ├──com.sie.mbm.mom.mpm.constants //常量
               ├──com.sie.mbm.mom.mpm.entity //实体
               ├──com.sie.mbm.mom.mpm.enums//枚举
               ├──com.sie.mbm.mom.mpm.mapper//持久层
               ├──com.sie.mbm.mom.mpm.service//业务逻辑服务
           ├──com.sie.mbm.mom.mpm.process //业务模块-工艺
               ├──com.sie.mbm.mom.mpm.controller //控制器
               ├──com.sie.mbm.mom.mpm.convert //对象映射转换
               ....

2.2. 应用分层

  1. 【强制】图中默认上层依赖于下层,箭头数据流关系,如:开放接口层可以依赖于 Web 层(Controller),Controller依赖于 Service 层,依此类推。

• 开放接口层:暴露成 RPC 接口;通过 Web 封装成 http 接口;网关控制层等。

• 终端显示层:各个端的显示的层。当前PC端,移动端展示等。

• Controller层:主要是对访问控制进行转发,各类基本参数校验等,不做业务处理。

• Service 层:相对具体的业务逻辑服务层。

• SDK层:通用业务处理层,它有如下特征:

1) 对第三方平台封装的层,预处理返回结果及转化异常信息。

2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。

3) 与 DAO 层交互,对多个 DAO 的组合复用。

• DAO 层:数据访问层,与底层 iDME\PostgreSQL 等进行数据交互。

• 外部接口或第三方平台:包括MBM等产品 RPC 开放接口,基础平台,其它公司的接口。

  1. 【推荐】分层异常处理规约

    在 DAO、SDK层,产生的异常类型有很多,无法用细粒度的异常进 行 catch,使用 catch(Exception e)方式,并 DmeAssert.throwException(TokenExpireEnum.PARSE);不需要打印日志,因为日志在Service 层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志, 浪费性能和存储。在 Service 层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息, 相当于保护案发现场。

    Web 层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面, 尽量加上友好的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。

2.3. 模型分层

  1. DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。

说明:放到entity包中

正例:

@Table("SieEmployee")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class Employee extends BaseEntity<Employee> {
    @Schema(description = "主键ID")
    private Long id;
}
  1. DTO(Data Transfer Object):数据传输对象,Service 或 API 向外传输的对象。

说明:放到dto包中

2.4. 库依赖

  1. 【强制】二方库定义 GAV 遵从以下规则
  1. GroupID 格式:com.sie.业务线 [.子业务线],最多 4 级

正例:

com.sie.mbm.mom
  1. ArtifactID 格式:产品线名-模块名-子模块。语义不重复不遗漏

正例:

mbm-mom-mpm
  1. 【强制】二方库版本号命名方式:主版本号.次版本号.修订号
  1. 主版本号:产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级。
  2. 次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改。
  3. 修订号:保持完全兼容性,修复 BUG、新增次要功能特性等。

说明:注意起始版本号必须为:1.0.0,而不是 0.0.1。

反例:

版本号从 1.0.0.0 开始,升级成 1.0.0.64,// ❌完全失去版本的语义信息。
  1. 【强制】线上应用不要依赖 SNAPSHOT 版本(安全包除外)

说明:不依赖 SNAPSHOT 版本是保证应用发布的幂等性,使 RELEASE 版本号有延续性,且版本号不允许覆盖升级。

  1. 【强制】所有 pom 文件中的依赖声明放在语句块中,所有版本仲裁放在最顶级语句块中。

说明:里只是声明版本,并不实现引入,因此子项目需要显式的声明依赖,version 和 scope 都读取自父 pom。而所有声明在主 pom 的里的依赖都会自动引入,并默认被所有的子项目继承。

  1. 【强制】引入新的第三方库需进行评审,确保功能适配性、兼容性、安全性和可维护性。评审通过后,需遵循集中管理、版本控制及封装使用等。
  2. 【推荐】二方库不要有配置项,最低限度不要再增加配置项

3. 编程规范

3.1. 命名风格

  1. 【强制】所有命名(变量、方法、类、常量等)均不能以下划线 _ 或美元符号 $ 开头或结尾

反例:

private int _count;    // ❌ 不能以下划线开头
private int count_;    // ❌ 不能以下划线结尾
private String $name;  // ❌ 不能以美元符号开头
private String name$;  // ❌ 不能以美元符号结尾

正例:

private int count;    // ✅ 正确
private String name;  // ✅ 正确
  1. 【强制】所有(变量、方法、类、常量等)的命名必须使用标准的英文单词,严禁使用拼音与英文混合的方式,更不允许使用中文命名。

反例:

private String huoQuUserInfo();   // ❌ 拼音 + 英文混合
private int shuliang;             // ❌ 纯拼音
private String 用户名;             // ❌ 直接使用中文

正例:

private String getUserInfo();  // ✅ 正确,标准英文命名
private int quantity;          // ✅ 正确,标准英文命名
  1. 【强制】类名必须使用 UpperCamelCase(首字母大写,每个单词的首字母都大写)。但以下情况例外:DO、DTO、VO等通用术语可以全部大写。

反例:

public class userManager { }   // ❌ 错误:首字母应大写
public class user_manager { }  // ❌ 错误:不能使用下划线
public class User_manager { }  // ❌ 错误:单词首字母应大写
public class usermanager { }   // ❌ 错误:应使用 CamelCase

正例:

public class UserManager { }    // ✅ 正确,采用 UpperCamelCase
public class UserDTO { }   // ✅ 正确,DTO 全部大写
  1. 【强制】方法名、参数名、成员变量、局部变量必须统一使用 lowerCamelCase(小驼峰命名法),即首字母小写,后续单词首字母大写。

反例:

private String UserName;      // ❌ 错误:首字母不应大写
private String user_name;     // ❌ 错误:不能使用下划线
private String username;      // ❌ 不推荐:可读性差
public void GetUserInfo() { } // ❌ 错误:方法首字母不应大写
public void get_user_info() { } // ❌ 错误:不能使用下划线
  1. 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

反例:

public static final int maxCount = 100;     // ❌ 错误:未使用全大写
public static final String defaultEncoding = "UTF-8"; // ❌ 错误:未使用全大写
public static final double Pi = 3.14159;    // ❌ 错误:未使用全大写
public static final String DB = "MySQL";    // ❌ 错误:名称过短,缺乏语义信息

正例:

public static final int MAX_RETRY_COUNT = 5;   // ✅ 正确,清晰表达了最大重试次数
public static final String DEFAULT_ENCODING = "UTF-8"; // ✅ 正确,清晰表达了默认编码
public static final double PI = 3.14159;       // ✅ 正确,数学常量
public static final String DATABASE_TYPE = "MySQL";  // ✅ 正确,清晰表达数据库类型
  1. 【强制】DTO类中的任何布尔类型变量,不要加 is 前缀,否则可能导致某些框架(如 MyBatis、Jackson)解析和序列化错误。

反例:

public class User {
    private Boolean isActive;  // ❌ 错误:使用了 is 前缀
    private Boolean isDeleted; // ❌ 错误:使用了 is 前缀
    public Boolean isActive() {  // ❌ 错误:getter 方法会导致序列化问题
        return isActive;
    }
    public void setActive(Boolean isActive) {
        this.isActive = isActive;
    }
}

正例:

public class User {
    private Boolean active;  // ✅ 正确:布尔变量直接命名
    public Boolean isActive() {  // ✅ getter 方法符合规范
        return active;
    }
    public void setActive(boolean active) {
        this.active = active;
    }
}
  1. 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

反例:

package com.Sie.Project.Modules.Users; // ❌ 错误:大小写混用、复数形式
package com.sie.utils.helper_classes;  // ❌ 错误:下划线不允许
package com.sie.ordersManagement;      // ❌ 错误:大小写混用
package com.sie.orderprocess;          // ❌ 错误:单词无语义,应为 order.processing

正例:

package com.sie.mom.module.user;      // ✅ 规范:单数形式
  1. 【强制】杜绝完全不规范的缩写,确保代码可读性,避免让人“望文不知义”。

反例:

String usrNm = "John Doe";  // ❌ usrNm -> userName,缩写不清晰
String cfg = "config.xml";  // ❌ cfg -> config,避免过度缩写
int cstAmt = 100;           // ❌ cstAmt -> costAmount,容易混淆
List<Cst> cstList = new ArrayList<>(); // ❌ Cst -> Customer,无法直观理解
  1. 【参考】方法命名规约:
  1. 获取单个对象的方法用 get 做前缀。
  2. 获取多个对象的方法用 list 做前缀,复数结尾,如:listObjects。
  3. 获取统计值的方法用 count 做前缀。
  4. 插入的方法用 save/insert 做前缀。
  5. 删除的方法用 remove/delete 做前缀。
  6. 修改的方法用 update 做前缀。
  7. 批量操作 batch做前缀

正例:

User getUserById(Long userId);  // ✅ 获取
List<User> listUsers();  // ✅ 集合
Integer countUsers();  // ✅ 统计
Long saveUser(UserDTO userDTO);  // ✅ 保存
List<Long> saveUsers(List<UserDTO> userListDTO); //批量保存
Integer deleteUserById(Long userId);  // ✅ 删除
Boolean updateUser(UserDTO userDTO);  // ✅ 更新规范
Integer updateUsers(List<UserDTO> userListDTO);  //✅  批量更新用户信息,返回更新成功的数量
Integer deleteUsersByIds(List<Long> userIds);  // ✅ 批量删除用户,返回删除成功的数量
Integer saveOrUpdateUsers(List<UserDTO> userDTOList);  //✅  批量新增或更新,返回操作成功的数量
BatchOperationResult batchProcessUsers(BatchUserDTO batchUserDTO);  //✅ 统一处理新增、更新、删除,返回操作结果

3.2. 注解规约

  1. 【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用 // xxx 方式。

说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注释;在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。

参考Javadoc(文档注释)详解:链接

  1. 【强制】提供包级别的注释需要使用 package-info.java 文件

正例:

/**
 * 该包包含用户管理的相关类。
 * <p>
 * 主要功能包括:
 * - 用户注册、登录
 * - 权限管理
 * - 个人信息管理
 * </p>
 *
 * @author 中文名称带数字
 * @date 2024/08/08
 * @description: 简介说明
**/

package com.example.usermanagement;

import com.example.annotations.PackageAnnotation;

  1. 【强制】所有的类都必须添加创建者、说明和创建日期。

说明:在设置模板时,注意 IDEA 的@author 为`${USER}`,而 eclipse 的@author 为`${user}`,大小写有 区别,而日期的设置统一为 yyyy/MM/dd 的格式。

在 IDEA 中配置 文件模板 (File Templates) 参考:


#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")  
package ${PACKAGE_NAME};  
#end  

/**
 *  
 * @author ${USER}  
 * @date ${YEAR}/${MONTH}/${DAY}
 * @description
 */
public class ${NAME} {

}

正例:

/**
 * 包装规则
 *
 * @author 中文名称(有数字则带上)
 * @date 2024/08/08
 * @description: 包装规则描述
 **/
public class PackRule {

    /**
     * 产品编码
     */
    private String partNo;
}
  1. 【强制】方法注释尽量简洁,突出关键点; 参数、返回值、异常等信息必须补充完整,保持一致的格式,便于团队阅读和维护

正例:

/**
 * 简要描述方法的作用
 * 
 * @param param1 参数1的说明(无入参则不写)
 * @param param2 参数2的说明
 * @return 返回值说明(void则不写)
 * @throws ExceptionType 异常说明
 * @deprecated 如果方法已废弃,提供替代方案
 */
public ReturnType methodName(Type param1, Type param2) throws ExceptionType {
    // 方法实现
}
  1. 【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使 用/* */注释,注意与代码对齐。

正例:

public void processOrder() {
    // 计算折扣
    int discount = 10;
}

public void generateReport() {
    /*
     * 生成报表的逻辑,包含:
     * 1. 查询数据库
     * 2. 处理数据
     * 3. 格式化报表输出
     */
    reportService.create();
}

  1. 【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。

正例:

/**
 * 订单状态枚举
 * 
 * @author 中文名称带数字
 * @date 2024/08/08
 * @description: 订单状态枚举描述
 */
public enum OrderStatus {

    /** 订单已创建,等待支付 */
    PENDING_PAYMENT,  

    /** 订单已支付,等待发货 */
    PAID,  

    /** 订单已发货,等待收货 */
    SHIPPED,  

    /** 订单已完成,交易成功 */
    COMPLETED,  

    /** 订单已取消 */
    CANCELED;  
}

  1. 【强制】代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑 等的修改。

说明:代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后,就失去了导航的意义。

正例:

/**
 * 获取用户年龄
 *
 * @author 中文名称(有数字则带上)
 * @date 2024/08/08
 * @param userId 用户ID
 * @return String 用户年龄字符串
 */
public String getUserAge(String userId) { 
    return "25";  
}

反例:注释仍然写着返回 int,但方法已经改为 String,容易误导调用者。

/**
 * 获取用户年龄
 *
 * @author 中文名称(有数字则带上)
 * @date 2024/08/08
 * @param userId 用户ID
 * @return int 用户年龄
 */
public String getUserAge(String userId) { 
    return "25";  // 这里返回的是 String,但注释还写着 int!
}

  1. 【强制】复杂逻辑的注释

说明:对于复杂的算法或业务逻辑,注释应该解释清楚该逻辑的目的、思路以及具体步骤。

正例:

// 计算折扣时,使用了逐个商品的折扣率,再加上全局折扣率进行计算
// 折扣公式:商品价格 * 商品折扣 * 全局折扣
double finalPrice = item.getPrice() * item.getDiscount() * globalDiscount;
/**
 * 计算购物车中商品的总价。
 * 遍历每个商品,检查商品是否有折扣,应用折扣后累加总价。
 * 如果有优惠券,进一步减免总价。
 *
 * @param cartItems 购物车中的商品列表
 * @param coupon 优惠券信息(如果有)
 * @return 购物车的总价
 */
public double calculateTotalPrice(List<Item> cartItems, Coupon coupon) {
    //业务逻辑
}

反例:

// ❌没有注释解释为什么某些代码片段这么做,代码的目的和思路不明确,其他开发者阅读时不容易理解。

public double calculateTotalPrice(List<Item> cartItems, Coupon coupon) {
    double total = 0.0;
    for (Item item : cartItems) {
        // 计算商品价格
        total += item.getPrice();
    }
    // 应用优惠券
    if (coupon != null) {
        total -= coupon.getDiscount();
    }
    return total;
}
  1. 【参考】特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描, 经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。

1)待办事宜(TODO):(标记人,标记时间,[预计处理时间]) 表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc 还没有实现,但已经被广泛使用。只能应用于类,接口和方法(因为它是一个 Javadoc 标签)。

正例:

/**
 * 处理用户登录逻辑
 * TODO (张三, 2025-02-25, [2025-03-01]) 优化登录失败的错误提示信息
 */
public void login(String username, String password) {
    // 登录逻辑待优化
}

2) 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间]) 在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。

正例:

/**
 * 计算折扣价格
 * FIXME (张三, 2025-02-25, [2025-02-27]) 折扣计算逻辑错误,导致负值
 */
public double calculateDiscount(double price, double discount) {
    return price - price * discount; // 可能导致负值,需修正
}

3.3. 常量定义

  1. 【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。

正例:

public class CacheKeys {
    /**
     * 淘宝交易缓存的 Key 前缀
     */
    public static final String TAOBAO_TRADE_KEY_PREFIX = "Id#taobao_";
}

public class CacheService {
    public void saveToCache(String tradeId, Object value) {
        String key = CacheKeys.TAOBAO_TRADE_KEY_PREFIX + tradeId;
        cache.put(key, value);
    }
}

public class OrderStatus {
    /**
     * 订单待处理状态
     */
    public static final int STATUS_PENDING = 3;
}

public class OrderService {
    public void processOrder(int status) {
        if (status == OrderStatus.STATUS_PENDING) {
            System.out.println("订单待处理...");
            // 处理逻辑
        }
    }
}

反例:

public void saveToCache(String tradeId, Object value) {
    String key = "Id#taobao_" + tradeId; // ❌ 硬编码,后续修改不便
    cache.put(key, value);
}
public void processOrder(int status) {
    if (status == 3) {  // ❌ 魔法值,阅读代码时无法直接理解 3 代表什么
        System.out.println("订单待处理...");
    }
}
  1. 【强制】常量必须使用 static final 关键字定义,命名应使用大写字母+下划线分隔,并加上注解

正例:

/**
     * 最大重试次数,表示操作失败时最多可以重试的次数。
     * 例如:网络请求失败时,最多允许重试 5 次。
     */
    public static final int MAX_RETRY_COUNT = 5;

    /**
     * 默认编码格式,用于处理字符串编码,例如文件读取、网络传输等。
     * 常见值:UTF-8、GBK、ISO-8859-1。
     */
    public static final String DEFAULT_ENCODING = "UTF-8";

反例:

//❌ 没有注解
public final int maxRetryCount = 5;  // ❌ 没有 static,无法共享
public static String defaultEncoding = "UTF-8"; // ❌ 没有 final,容易被修改
  1. 【强制】在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字 混淆,造成误解。

正例:

  Long value = 2L; // ✅ 大写 L,清晰易读
  long maxLimit = 10000000000L; // ✅ 正确,避免误读

反例:

Long value = 2l; // ❌ 小写 l,容易误认为 21
long maxLimit = 10000000000l; // ❌ 看起来像 100000000001

3.4. 代码格式

  1. 【强制】缩进与代码块,采用 4 个空格缩进(不要使用 Tab);大括号 {} 的写法采用 "行尾风格",即左大括号 { 与语句在同一行,右大括号 } 另起一行。

正例:

public class Example {
    public void method() {
        if (true) {
            System.out.println("遵循代码缩进规范");
        } else {
            System.out.println("保持一致");
        }
    }
}
  1. 【强制】代码换行(分屏换行),单行字符数限制不超过 120 个(一屏能显示),超过应进行合理换行。
  2. 【强制】IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式。
  3. 【推荐】方法参数换行,当参数较多时,每个参数换行并对齐

正例:

public void doSomething(
        String name, int age, 
        boolean isActive, double score) {
    // 方法体
}

反例:

public void doSomething(String name, int age, 
    boolean isActive, double score) {
    // ❌ 第二行缩进不对齐
}

4. 接口规范

4.1. 基本要求

为了实现高质量、可读性、可维护性和可扩展性的RESTful API,应遵循以下设计规范:

  1. 协议:使用HTTP/HTTPS协议进行通信。
  2. 请求方法:常用的请求方法有GET、POST、PUT、DELETE等。
  3. 无状态:应该是无状态的,即它不应该依赖于上一个请求的状态。
  4. 缓存:应该支持缓存机制,以提高性能和可用性。
  5. 幂等性:的设计应具有幂等性,即对于同一个请求,无论请求多少次,结果都是一样的。
  6. 可扩展性:应该具有良好的可扩展性,以便支持未来的新功能和业务场景。

4.2. 类级别路径或全局路径

  1. 【强制】为该控制器类中的所有方法提供一个公共的前缀路径

说明:@RequestMapping("[版本]/[模块]/[类]"),模块为可选项,模块并非微服务名,如:process 代表工艺模块。

正例:

@RequestMapping("/v1/process/masters") // ✅ 正确
public class MasterController {
   @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteMaster(@PathVariable Long id) {
          //业务逻辑
      }
}

反例:

@RequestMapping("/process/masters") // ❌ 错误,没有版本号
@RequestMapping("v1/mes/process/masters") // ❌ 错误,不需要加应用服务名
public class UserController {
   @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteMaster(@PathVariable Long id) {
          //业务逻辑
      }
}
public class UserController {
   @DeleteMapping("v1/smt/process/masters") // ❌ 错误,没有全局声明
    public ResponseEntity<Void> deleteMaster(@PathVariable Long id) {
          //业务逻辑
      }
}

4.3. 方法与操作

  1. 【强制】API 的 HTTP 方法对应以下操作
HTTP 方法 操作 JAVA 注解 说明
GET 获取资源 GetMapping 获取某个资源或者资源列表
POST 创建资源 PostMapping 创建一个新资源,或入参是DTO对象
PUT 更新资源 PutMapping 完全更新某个资源
PATCH 部分更新资源 PatchMapping 更新资源的部分内容(可选)
DELETE 删除资源 DeleteMapping 删除某个资源

正例:

@RestController
@RequestMapping("/v1/users")
public class UserController {

    // 获取所有用户
    @GetMapping
    public R<List<User>> getUsers() {
        return userService.getAllUsers();
    }

    // 获取特定用户
    @GetMapping("/by-user-id")
    public R<User> getUser(@RequestParam("id") Long userId) {
        return userService.getUserById(userId);
    }

    // 创建用户
    @PostMapping
    public User createUser(@Valid @RequestBody UserDTO user) {
        return userService.createUser(user);
    }

    // 更新用户
    @PutMapping()
    public User updateUser(@Valid @RequestBody UserDTO user) {
        return userService.updateUser(user);
    }

    // 删除用户
    @DeleteMapping()
    public void deleteUser(@RequestParam("id") Long userId) {
        userService.deleteUser(userId);
    }

    //批量删除用户
    @DeleteMapping("/delete-batch-by-ids")
    public void deleteBatch(@RequestParam("ids") Collection<Long> ids) {
        userService.deleteBatch(ids);
    }
}
  1. 【推荐】传递复杂的 JSON 数据或对象,应使用 POST 或 PUT 方法,并在方法中使用 @RequestBody。

说明:使用 @GetMapping 配合 @RequestBody 作为参数时,存在生成文档参数看不到明细的问题

    // 查询用户
@PostMapping("/search")
@Operation( summary = "查询用户", description = "通过请求体传递复杂的查询条件" )
public List<User> searchUsers(@Valid @RequestBody @Parameter(description = "查询条件") SearchCriteria searchCriteria) {
        // 查询逻辑   
}

4.4. 方法路径规则

  1. 【强制】路径命名规则,使用分隔单名:中划线 (kebab-case) 全小写URL 路径

正例:

/userProfile, /OrderHistory  /order_history ❌ 错误
/user-profile, /order-history  ✅ 正确
  1. 【推荐】URL(宾语)是名词

正例:

// 根据ID获取用户
@GetMapping("/user-by-id")    // ✅ 正确,路径中不包含动词,HTTP 方法(GET)表示查询
public ResponseEntity<UserDTO> getUserById(@RequestParam Long id) { 
    // 处理逻辑
}

反例:

// 错误示例:路径包含动词
@GetMapping("/get-user-by-id")    // ❌ 错误,路径中包含了动词 "get"
public ResponseEntity<UserDTO> getUserById(@RequestParam Long id) { 
    // 处理逻辑
}

4.5. 版本控制

  1. 【推荐】版号原则说明

主版本号:产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级。

次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改。

修订号:保持完全兼容性,修复 BUG、新增次要功能特性等。

  1. 【强制】将版本号放入URL中(方便直观)

版本号:v{n}n 代表版本号,分为整形和浮点型

整型:大功能版本发布形式:具有当前版本状态下的所有 API接口,例如:v1,v2

    浮点型:为小版本号,只具备补充 api 的功能,其他 api 都默认调用对应大版本号的 api 例如:v1.1 v2.2。

正例:

@RestController
@RequestMapping("/v1/users")  // ✅ 正确 在类上定义版本号
public class UserController {
}

4.6. HTTP 状态码

  1. 【强制】根据不同的情况返回相应的 HTTP 状态码

正例:

状态码 说明 适用场景
200 OK 请求成功,且返回数据(GET、POST、PUT、DELETE) 获取数据、更新数据成功
201 Created 请求成功,且资源已被创建 POST 请求成功后返回资源
204 No Content 请求成功,但无返回内容 DELETE 请求成功
400 Bad Request 请求无效,客户端错误 请求缺少参数、格式错误
401 Unauthorized 未授权,认证失败 需要认证才能访问
403 Forbidden 禁止访问,权限不足 无权限访问该资源
404 Not Found 请求资源不存在 资源未找到
405 Method Not Allowed 方法不允许 请求方法不支持
500 Internal Server Error 服务器错误 服务器异常

4.7. 接口文档注解

参考:Swagger 3 注解使用指南 链接

5. 异常规范

  1. 【强制】Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。

说明:避免 catch 预检查可规避的异常,优先进行 null 判断、索引检查、参数校验。真正不可预见的异常(如 IO 异常、数据库异常),才使用 try-catch 处理。

正例

if (user != null) {  
    String name = user.getName();
}

if (CollectionUtils.isNotEmpty(list)) {
    int value = list.get(5);
}

反例

try {
    String name = user.getName(); // 可能抛 NPE
} catch (NullPointerException e) {
    logger.error("空指针异常", e); // ❌ 不推荐
}

try {
    int length = str.length(); // ❌ 可能抛 NPE
} catch (NullPointerException e) {
    logger.error("字符串为空", e);
}

try {
    int value = list.get(5); // ❌ 可能越界
} catch (IndexOutOfBoundsException e) {
    logger.error("索引越界", e);
}
  1. 【强制】catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。 对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。

说明

  1. 稳定代码 不放 try-catch 里,避免影响异常捕获逻辑。
  2. 针对非稳定代码,精确 catch 具体异常,不同异常不同处理方式。
  3. 禁止 catch Exception e 直接打印,会丢失异常类型,影响排查。

反例

try {
    log.info("开始执行任务");
    String content = new String(Files.readAllBytes(Paths.get("config.txt"))); // 可能抛 IO 异常
    int result = 10 / num; // 可能抛 ArithmeticException
    processData(content); // 可能抛 NullPointerException
} catch (Exception e) { 
    log.error("发生异常", e); // ❌ 粗暴捕获,无法区分异常类型
}

正例:

log.info("开始执行任务"); // ✅ 稳定代码,放 try-catch 外
try {
    String content = new String(Files.readAllBytes(Paths.get("config.txt"))); // 可能抛 IO 异常
    int result = 10 / num; // 可能抛 ArithmeticException
    processData(content); // 可能抛 NullPointerException
} catch (IOException e) {
    log.error("文件读取失败", e); // ✅ 处理 IO 相关问题
} catch (ArithmeticException e) {
    log.error("数学运算错误", e); // ✅ 处理数学异常
} catch (NullPointerException e) {
    log.error("空指针异常", e); // ✅ 处理空指针异常
}

  1. 【强制】捕捕获的异常类型 必须与 抛出的异常类型 完全匹配,或者 捕获异常是抛出异常的父类

说明:如果代码 捕获了错误的异常类型,可能会导致异常无法正确处理,甚至隐藏潜在的 Bug

反例

public void process() {
    try {
        throw new IOException("IO 发生异常");
    } catch (NullPointerException e) { // ❌ 代码无法捕获 IOException
        System.out.println("捕获到空指针异常");
    }
}

正例

public void process() {
    try {
        riskyOperation();
    } catch (IOException e) { // ✅ 精确匹配,处理 IO 异常
        System.out.println("文件读取失败:" + e.getMessage());
    } catch (SQLException e) { // ✅ 精确匹配,处理数据库异常
        System.out.println("数据库操作失败:" + e.getMessage());
    } catch (Exception e) { // ✅ 兜底处理其他异常
        System.out.println("发生未知异常:" + e.getMessage());
    }
}

public void riskyOperation() throws IOException, SQLException {
    // 可能抛出 IOException 或 SQLException
}

  1. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容。

反例

try {
    // 数据库操作
    String result = databaseService.queryData("someQuery");
} catch (SQLException e) {
    // 捕获了异常,但什么都没做,异常被吞掉,最少要写个日志。
}

正例

try {
    // 数据库操作
    String result = databaseService.queryData("someQuery");
} catch (SQLException e) {
    log.error("查询数据时发生错误", e);
    // 处理异常:可能是数据库连接问题、查询错误等
    throw new BusinessException("数据查询失败,请稍后重试");
}
  1. 【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说 明什么情况下会返回 null 值。

说明:明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。

正例

/**
 * 获取用户的详细信息
 * 
 * @param userId 用户 ID
 * @return 用户的详细信息,如果用户不存在,则返回 null  //✅ 正例:返回 null 并做充分说明
 */
public User getUserDetails(String userId) {
    if (userId == null || !userExists(userId)) {
        return null; // 如果用户不存在,返回 null
    }
    return findUserById(userId);
}

反例

//❌ 错误示例:返回 null 而不说明
public User getUserDetails(String userId) {
    if (userId == null || !userExists(userId)) {
        return null;
    }
    return findUserById(userId);
}
  1. 【推荐】防止 NPE,注意 NPE 产生的场景
  1. 自动拆箱可能导致 NPE

反例

public int getAge() {
    Integer age = null;
    return age;  // ❌ 自动拆箱时抛出 NullPointerException
}

正例:

public int getAge() {
    Integer age = null;
    return Optional.ofNullable(age).orElse(0);  // ✅ 推荐使用 Optional 处理
}
  1. 查询结果可能为 null

反例

User user = userRepository.findById(userId);  
String username = user.getName();  // ❌ 如果 user 为空,则调用 getName() 触发 NPE

正例

String username = Optional.ofNullable(userRepository.findById(userId))
                          .map(User::getName)
                          .orElse("默认名称");  // ✅ 推荐使用 Optional
  1. 级联调用可能导致 NPE

反例

String city = user.getAddress().getCity();  // ❌ 如果 user 或 address 为 null,则 NPE

正例

String city = (user != null && user.getAddress() != null) ? user.getAddress().getCity() : "未知";

String city = Optional.ofNullable(user)
                      .map(User::getAddress)
                      .map(Address::getCity)
                      .orElse("未知");  // ✅ 推荐

  1. 集合中元素可能为 null

反例

List<String> names = new ArrayList<>();
names.add(null);
System.out.println(names.get(0).length());  // ❌ 取值时 NPE

正例

for (String name : names) {
    System.out.println(Objects.requireNonNullElse(name, "默认值").length());  // ✅ 确保不为 null
}

6. 日志规范

6.1. 基本要求

  1. 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架(SLF4J)中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

正例:

//✅ 正确使用@Slf4j注解
@Slf4j
public class HttpInvokerServiceExporterAspect {
   public void mypointcut() {
        log.info("test");
    }
}
  1. 【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。

说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅

是替换动作,可以有效提升性能。

正例:

 logger.info("Processing order: orderId={}, userName={}", orderId, userName);

反例:

// ❌错误示例 1:直接使用 + 进行拼接
logger.info("Processing order: orderId=" + orderId + ", userName=" + userName);

// ❌错误示例 2:字符串拼接会导致性能损耗
logger.info("Processing order: " + String.format("orderId=%s, userName=%s", orderId, userName));

  1. 【强制】对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。

说明:尽管 debug(参数) 方法内第一行代码 isDisabled(Level.DEBUG_INT) 为真时(如 Log4j、Logback),会直接 return,但参数仍可能发生字符串拼接。此外,debug(getName()) 形式的调用会导致不必要的方法开销。

// 如果判断为真,那么可以输出 trace 和 debug 级别的日志
if (logger.isDebugEnabled()) {
 logger.debug("Current ID is: {} and name is: {}", id, getName());
}

  1. 【强制】生产环境禁止直接使用 System.out 或 System.err 输出日志或使用 e.printStackTrace()打印异常堆栈。

正例:

logger.error("发生异常: {}", e.getMessage(), e);

反例:

//❌日志不可控,生产环境可能无法记录。
System.out.println("系统启动成功");
  
 try {
     int result = 10 / 0;
} catch (Exception e) {
   e.printStackTrace(); //❌仅在控制台打印异常,不会被日志系统捕获,排查问题困难。
 }
  1. 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过 throws 关键字往上抛出。

说明:在异常处理时,必须包含完整的错误上下文,以便于日志排查和问题分析。

格式:【模块名称】 业务关键字段 , 事件描述 详细异常信息

关键日志字段

字段 说明 示例
【模块名称】 业务功能模块 【订单处理】, 【支付处理】
业务ID 关键业务 ID 订单ID: 123456, 用户ID: 98765
状态 业务状态 订单状态: PENDING, 支付状态: FAILED
关键参数 影响逻辑的参数 支付方式: Alipay, 库存: 10
时间 记录时间点 2025-03-04T10:00:00
异常信息 详细异常栈 NullPointerException: xxx

正例:

  try {
         int result = 10 / 0;
} catch (ArithmeticException e) {
       logger.info("【支付处理】 订单ID: {} , 支付方式: {} ,交易状态: {} ,处理时间: {}", orderId, paymentMethod, status, LocalDateTime.now());   
         throw new BusinessException("订单处理异常,ID: " + orderId, e);
}

反例:

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    logger.error("计算错误", e);
    // ❌ 没有抛出异常,导致业务流程异常中断
}
  1. 【强制】日志打印时禁止直接用 JSON 工具将对象转换成 String。

正例:

logger.error("【订单处理失败】订单ID: {}, 用户ID: {}, 错误信息: {}",  order.getOrderId(), order.getUserId(), e.getMessage(), e);

反例:

logger.info("订单信息: {}", JSON.toJSONString(order));  // ❌ 禁止使用
logger.info("订单详情: {}", order);  // ❌ 不推荐
  1. 【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑 爆,并记得及时删除这些观察日志。

说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?

  1. 【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。

说明:注意日志输出的级别,error级别只记录系统逻辑出错、异常或者重要的错误信息。

6.2. 日志配置

  1. 【强制】日志格式
时间 | 级别 | 线程 | 类名 | 方法 | 行号 | 追踪ID | 消息

2025-03-03 12:00:00.123 | INFO  | main | com.example.service.UserService | getUser | 45 | traceId=abc123 | 查询用户信息成功

  1. 【推荐】统一 JSON 格式(方便日志分析)
{
    "timestamp": "2025-03-03T12:00:00.123",
    "level": "INFO",
    "thread": "main",
    "logger": "com.example.service.UserService",
    "method": "getUser",
    "line": 45,
    "traceId": "abc123",
    "message": "查询用户信息成功"
}
  1. 【推荐】链路追踪ID

使用 MDC 统一追踪 ID(分布式链路追踪)

  1. 【推荐】日志分割
  1. 文件大小:单个日志文件最大 10MB
  2. 日志保留时间:7 天
  3. 日志格式:每日日志 logs/app-2025-03-03.1.log,超出 10MB 会自动切割
  1. 【推荐】Logback 配置
%d{yyyy-MM-dd HH:mm:ss.SSS} → 时间戳格式
%level → 日志级别
%thread → 线程名
%logger{36} → 限制类名长度
%method → 方法名
%line → 代码行号
%X{traceId} → MDC 变量(分布式追踪)
%msg%n → 日志消息
  1. 【推荐】日志存储策略
  1. Elasticsearch (ES):日志存储 & 查询引擎
  2. Logstash / Filebeat:日志收集 & 传输
  3. Kibana:日志分析 & 可视化展示
  4. Logback + JSON:日志格式化,支持 ELK 解析

7. 代码管理

7.1. 提交规范

  1. 【强制】提交信息格式

格式:type [标识]+‘:’+【业务模块】+具体页面/具体功能开发的描述,若是修复善治 bug,带上善治 bug id

类型(Type)

新增:【业务模块】新增feature,新功能开发

修复:【业务模块】修复bug,如修复禅道bug,带上禅道bug编码

文档:【业务模块】仅仅修改了注释/文档,如readme.md

样式:【业务模块】仅仅是对格式进行修改,如逗号、缩进、空格等。不改变代码逻辑。

重构:【业务模块】代码重构,没有新增功能或修复bug

移除:【业务模块】删除部分无用文件。

测试:【业务模块】测试用例,包括单元测试、集成测试。

更新:【业务模块】改变构建流程、或者增加依赖库、工具等。

回滚:【业务模块】版本回滚

type 含义 使用场景举例
新增 ✨ 新功能 添加新页面、新接口、新配置
修复 🐛 修复问题 修复 Bug、逻辑错误、异常处理
重构 🔨 重构 优化已有代码结构但不改变功能
优化 ⚡ 性能优化 提升查询效率、减少响应时间
样式 🎨 样式调整 格式化代码、统一命名、去除空格
文档 📝 文档变更 更新注释、接口文档、README 等
测试 ✅ 测试相关 新增或修改单元测试、集成测试
更新 ✅依赖更新 变更构建流程、增加依赖、更新工具
回滚 ⏪ 回滚提交 回退之前的某次提交
合并 🔀 合并分支 合并 feature/dev/main 分支
  1. 【强制】git账号提人的名字需为中文,邮箱要用公司邮箱。

说明:Git 提交记录 需要清晰可读,避免混淆不同人员的提交记录,便于追踪责任人。

git config --global user.name "张三"
git config --global user.email "linchang@chinasie.com"

8. 数据库

8.1. 建表规范

  1. 【强制】表名规范格式:sie_ + 产品线 + _ + 功能

正例:

sie_mdm_customer    //主数据产品线的客户表
  1. 【强制】iDME 模型命名规范(模型名)格式:Sie + 产品线 + 功能(大驼峰命名)

正例:

SieMdmCustomer
  1. 【强制】iDME 字段模型命名规范(模型字段)命名方式:大驼峰命名

正例:

CustomerName、ContactPhone
  1. 【强制】数据库字段命名规范(字段名)命名方式:全小写 + 下划线分隔

正例

customer_name、contact_phone

9. 跨服务调用

核心价值:防止级联故障,保障系统稳定性

9.1. 问题背景

在微服务架构中,服务间的依赖关系复杂,一个服务的故障可能引发连锁反应:

// ❌ 不检查直接调用的风险示例
public OrderDTO createOrder(OrderRequest request) {
    // 直接调用库存服务
    inventoryClient.deductStock(request.getSkuId(), request.getQuantity());  // 如果库存服务宕机,这里会抛出异常
  
    // 直接调用用户服务
    UserDTO user = userClient.getUser(request.getUserId());  // 如果用户服务不可用,订单创建失败
  
  
    return orderRepository.save(request);
}

9.2. 使用规范

// ✅ 正确用法
boolean canAccessMpm = applicationBizHandle.isAvailableApplication(ServiceNameConstants.MPM_BIZ);
boolean canAccessUaa = applicationBizHandle.isAvailableApplication(ServiceNameConstants.UAA_BIZ);

// ❌ 错误用法
boolean result1 = applicationBizHandle.isAvailableApplication("mpm-biz");  // 禁止使用字符串
boolean result2 = applicationBizHandle.isAvailableApplication("MPM_BIZ");  // 禁止直接使用常量名

正例:

  @Resource
    private ApplicationBizHandle applicationBizHandle;
  
    /**
     * 安全调用MPM服务
     */
    public <T> T safeInvokeMpm(Supplier<T> mpmInvoker, Supplier<T> fallback) {
        if (applicationBizHandle.isAvailableApplication(ServiceNameConstants.MPM_BIZ)) {
            return mpmInvoker.get();
        }
        log.warn("MPM服务不可用,使用降级方案");
        return fallback.get();
    }

10. 单元测试

11. 版本发版


Comment