返回资源中心

微服务公共模块封装:common 公共 SDK

课件新手也能看懂的微服务公共模块封装最佳实践小红学堂更新于 2026年8月30日224 次阅读

你将学到:为什么微服务一定要做公共模块、common sdk 里装什么、它凭什么"一引入就能用"(自动配置 + 组件扫描)、为什么说它本身就是一个公共 SDK、以及依赖怎么分发和做版本管理。


一、先说痛点:为什么微服务一定要做公共模块

微服务把一个大系统拆成了一堆独立服务。比如一个仓储项目就拆成了 10 个服务:网关、系统、仓库、商品、承运商、入库、库存、出库、运输、计费报表。问题来了——这些服务里有大量"一模一样"的代码

  • 每个服务都要返回统一格式 {success, code, message, data} → 都要写一个 ApiResponse
  • 每个服务都要做全局异常处理 → 都要写一个 GlobalExceptionHandler
  • 每个服务的表都有 create_time / update_time / tenant_id → 都要写一个 BaseEntity
  • 每个服务都要用 JWT、分页对象、审计填充……

如果每个服务各写一套,会出现三个灾难:

问题后果
重复造轮子10 个服务写 10 遍 ApiResponse,浪费时间
风格不统一A 服务返回 code=0,B 服务返回 status=200,前端崩溃
改一处要改十处统一响应想加个字段,要去 10 个服务里改

💡 解决思路:把这些公共的类和配置抽出来,做成一个独立模块,打成 jar 包,让所有服务都依赖它。这就是「公共模块封装」——也是一个最基础的「公共 SDK」。


二、整体结构:单仓多模块,common 是公共底座

示例项目用的是 单仓多模块(Maven 聚合 / reactor) 结构:一个父工程 xiangmu-cloud,下面挂公共模块 + 10 个业务服务。

真实的父 POM(xiangmu-cloud/pom.xml):

<groupId>com.xiangmu</groupId>
<artifactId>xiangmu-cloud</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>          

<modules>
    <module>xiangmu-common</module>     
    <module>gateway-service</module>
    <module>system-service</module>
    <module>product-service</module>
    <module>inventory-service</module>
    
</modules>

关键点:xiangmu-common 和各服务在同一个父工程下,所以服务可以直接以模块依赖的方式引用它(reactor 内部依赖),这是教学项目最简单直接的方式。企业里如果 common 要被多个独立仓库共享,才需要发到 Maven 私服——这点第六节细讲。


三、xiangmu-common 里到底装了什么

按"职责"分门别类,主要有这几类(均为真实包路径 com.xiangmu.common.*):

分类代表类作用
统一响应apiApiResponseListResultPageRequest所有接口返回同一种格式
基础实体entityBaseEntity审计字段 + 租户字段 + 逻辑删除
统一异常exceptionBusinessException全局统一的业务异常
全局处理webGlobalExceptionHandlerTenantContextInterceptor异常兜底、请求拦截
自动配置configxiangmuJwtAutoConfiguration 等 5 个开箱即用的通用配置
JWTjwtJwtUtilJwtPropertiesJwtPayload令牌生成/校验
MyBatismybatisAuditInterceptorTenantLineInterceptorBaseMapper审计填充、SQL 拦截
基础设施cache/lock/mq/idempotentCacheServiceDistributedLockMqPublisher缓存/分布式锁/消息/幂等

🎥 真实代码 1:统一响应 ApiResponse

@Data
public class ApiResponse<T> implements Serializable {
    private boolean success;   // 是否成功
    private String  code;      // 业务码:0=成功,非0=失败
    private String  message;   // 提示信息
    private T       data;      // 业务数据

    public static <T> ApiResponse<T> success(T data) {
        return success(data, "操作成功");
    }
    public static <T> ApiResponse<T> failure(String code, String message) {
        ApiResponse<T> r = new ApiResponse<>();
        r.setSuccess(false);
        r.setCode(code);
        r.setMessage(message);
        return r;
    }
}

有了它,所有服务的 Controller 都 return ApiResponse.success(data),前端永远只对接一种格式。

🎥 真实代码 2:基础实体 BaseEntity

@Data
public abstract class BaseEntity implements Serializable {
    private Long    tenantId;     // 租户ID(多租户行级隔离,所有业务表必带)
    private Instant createTime;   // 创建时间
    private Instant updateTime;   // 更新时间
    private String  createBy;     // 创建人
    private String  updateBy;     // 更新人
    private Boolean deleted;      // 逻辑删除标记
}

各服务的实体只要 extends BaseEntity,这些公共字段就都有了。配合 AuditInterceptor,写入时还能自动填值(create_time/update_time 等),不用手动 set。

🎥 真实代码 3:全局异常处理 GlobalExceptionHandler

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)          // 业务异常
    public ApiResponse<Void> handleBusiness(BusinessException ex) {
        return ApiResponse.failure(ex.getErrorCode(), ex.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)   // 参数校验异常
    public ApiResponse<Void> handleValidation(MethodArgumentNotValidException ex) { ... }

    @ExceptionHandler(Exception.class)                  // 系统异常兜底
    public ApiResponse<Void> handleException(Exception ex) {
        return ApiResponse.failure("SYSTEM_ERROR", "系统异常,请稍后重试");
    }
}

业务代码里只管 throw new BusinessException("库存不足"),剩下"转成统一错误返回"的活由它兜底——每个服务都不用再写一遍。


四、为什么"引入依赖就能用"?

思考:JwtUtilAuditInterceptorGlobalExceptionHandler 这些都写在 xiangmu-common 里,为什么别的服务没写任何 @Bean,引入依赖后就自动生效了?

用了两种互补的装配机制

4.1 机制①:自动配置(Auto Configuration)

这就是大家常说的「starter」原理。在 xiangmu-common 里放一个文件: resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,内容是要加载的配置类清单:

com.xiangmu.common.config.xiangmuJwtAutoConfiguration
com.xiangmu.common.config.xiangmuWebMvcAutoConfiguration
com.xiangmu.common.config.xiangmuMyBatisAutoConfiguration
com.xiangmu.common.config.xiangmuCacheAutoConfiguration
com.xiangmu.common.config.xiangmuMqAutoConfiguration

这等于告诉 Spring Boot:「启动时请加载这几个配置类」。 (补充:Spring Boot 2.7 之前用的是 spring.factories新项目统一用 AutoConfiguration.imports

🎥 真实配置类(注意 @AutoConfiguration + 一堆条件注解):

@AutoConfiguration
@ConditionalOnClass(SqlSessionFactory.class)          // 类路径有 MyBatis 才生效
@ConditionalOnProperty(prefix = "xiangmu.tenant", name = "enabled",
                       havingValue = "true", matchIfMissing = true)  // 配置开关可关
@EnableConfigurationProperties(TenantProperties.class)
public class xiangmuMyBatisAutoConfiguration {

    @Bean
    public TenantLineInterceptor tenantLineInterceptor(TenantProperties p) { ... }

    @Bean
    public AuditInterceptor auditInterceptor() { return new AuditInterceptor(); }
}
@AutoConfiguration
@EnableConfigurationProperties(JwtProperties.class)
public class xiangmuJwtAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean                         // 服务自己没定义才用我的
    public JwtUtil jwtUtil(JwtProperties properties) {
        return new JwtUtil(properties);
    }
}

@ConditionalOnXxx 是自动配置的灵魂,让 SDK 既开箱即用又灵活:

注解含义好处
@ConditionalOnClass类路径有某个类才生效没引相关依赖就不瞎配
@ConditionalOnMissingBean服务自己没定义才用我的允许业务方覆盖默认实现
@ConditionalOnProperty配置开关打开才生效一行配置就能关掉某功能

4.2 机制②:组件扫描

GlobalExceptionHandler 这种 @RestControllerAdvice(本质是 Spring 组件),靠的是业务服务启动类开启包扫描:

@SpringBootApplication(scanBasePackages = "com.xiangmu")   // 扫描 com.xiangmu 装配公共组件
public class ProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }
}

因为公共组件都在 com.xiangmu.common 下,启动类把扫描范围设成 com.xiangmu,就能把 common 里的 @RestControllerAdvice@Component 一并扫进来。

✅ 一句话总结:自动配置负责"注册第三方/工具型 Bean",组件扫描负责"捡起公共注解组件",两者配合 → 服务只要依赖 common 就全部就位。


五、为什么说 xiangmu-common 本身就是一个「公共 SDK」

回头看 xiangmu-common 做的事:它把统一响应、统一异常、JWT、审计、多租户拦截这些通用能力封装成一个 jar,对外提供简单的类和注解,使用方加个依赖就能用——这正是 SDK 的本质:

SDK = 把通用/复杂的能力封装好,让使用方傻瓜式接入。

所以 xiangmu-common 就是这个项目里最基础的「公共 SDK」。

而且这种封装方式是可复制的:如果将来有别的通用能力需要沉淀,完全可以用同样的套路再建一个 SDK 模块。最典型的就是服务间调用的 client-sdk

示例项目每个服务都有一个 xxx-client-sdk(如 product-client-sdk)——别的服务想调商品服务,直接引它的 client-sdk 就行。它的封装思路和 common 一脉相承,是后面《Feign 服务间调用 SDK 封装》的内容,这里先建立"还能这样扩展"的概念即可。


六、依赖怎么分发 + 版本怎么管理

公共模块写好了,怎么让各个服务用上它? 分两种场景。

6.1 场景 A:单仓多模块(教学项目用的方式)

示例项目所有服务和 common 在同一个父工程里,所以服务直接模块依赖即可——连版本号都不用写(由父工程统一管理):

🎥 真实代码(product-service/product-common/pom.xml):

<dependency>
    <groupId>com.xiangmu</groupId>
    <artifactId>xiangmu-common</artifactId>   
</dependency>

优点:简单、改完 common 立即生效,不用发包。适合一个仓库装下所有服务的项目(教学、中小项目)。

6.2 场景 B:跨独立仓库共享 → 发 Maven 私服

企业里服务往往拆在不同的 git 仓库里,没法 reactor 直接依赖。这时就要把 common 发布到 Maven 私服(私有仓库),其他仓库像引普通依赖一样引入。

为什么用私服而不是 Maven 中央仓库?

原因说明
代码保密公司内部包不能传到公开的中央仓库
统一管理团队统一从私服拉依赖,版本可控可审计
加速缓存私服会缓存中央仓库的包,内网拉取更快

常见私服:NexusArtifactory阿里云效GitHub Packages。原理一样:一个放私有 jar 的远程仓库。

发布配置示例(在公共模块 / 父 pom 加 distributionManagement):


<distributionManagement>
    <repository>
        <id>company-nexus</id>
        <url>https://nexus.your-company.com/repository/maven-releases/</url>
    </repository>
    <snapshotRepository>
        <id>company-nexus</id>
        <url>https://nexus.your-company.com/repository/maven-snapshots/</url>
    </snapshotRepository>
</distributionManagement>

凭据写在 ~/.m2/settings.xmlid 要和上面对上):

<servers>
  <server>
    <id>company-nexus</id>
    <username>你的账号</username>
    <password>你的密码/Token</password>
  </server>
</servers>

然后执行发布:

mvn clean deploy    # 编译 → 打包 → 上传到私服

其他仓库的服务,在 pom 里加上 common 依赖(这次要写明 version)即可使用。

📌 示例项目是教学项目,采用的是 6.1 单仓模块依赖,没有发私服。6.2 是给你了解企业里跨仓库共享时的标准做法。

6.3 版本管理:集中管理 + SNAPSHOT/RELEASE

① 版本集中管理:示例项目在父 pom 用 <properties> 统一定义所有依赖版本,子模块只引坐标不写版本,避免"同一个库各服务版本不一致":

🎥 真实代码(xiangmu-cloud/pom.xml):

<properties>
    <java.version>21</java.version>
    <spring-cloud.version>2023.0.0</spring-cloud.version>
    <mybatis.version>3.0.3</mybatis.version>
    <redisson.version>3.27.2</redisson.version>
    <jjwt.version>0.12.5</jjwt.version>
    
</properties>

配合 <dependencyManagement>,子模块引依赖时不写版本,版本号只在父 pom 改一处——这是生产环境管理依赖版本的标准方式

② SNAPSHOT 与 RELEASE

版本类型例子含义特点
SNAPSHOT1.0.0-SNAPSHOT开发中的快照版同一版本号可反复覆盖,拉取取最新
RELEASE1.0.0正式发布版不可覆盖,发了就锁死,保证稳定

示例项目是教学项目,全程用 SNAPSHOT(方便边改边用)。 生产实践:联调阶段用 SNAPSHOT(改完重发,别人拉到最新);正式上线打 RELEASE 版本并锁定,保证线上依赖不会被人偷偷改动。


七、其他服务接入:两步搞定

以商品服务 product-service 为例:

第一步:pom 引入依赖(单仓不写 version):

<dependency>
    <groupId>com.xiangmu</groupId>
    <artifactId>xiangmu-common</artifactId>
</dependency>

第二步:启动类设好扫描范围(让组件扫描 + 自动配置都生效):

@SpringBootApplication(scanBasePackages = "com.xiangmu")
public class ProductApplication { ... }

之后业务代码直接用,公共能力已就位:

@GetMapping("/{id}")
public ApiResponse<ProductVO> get(@PathVariable Long id) {
    return ApiResponse.success(productService.get(id));   // 统一响应,开箱即用
}

八、小结

五句话带走:

  1. 微服务必做公共模块:否则重复造轮子、风格不统一、改一处要改十处。
  2. common sdk 是底座:统一响应、统一异常、基础实体、JWT、审计全在这。
  3. 装配是灵魂:自动配置(AutoConfiguration.imports + @ConditionalOnXxx)注册 Bean,组件扫描(scanBasePackages)捡注解组件,做到"引入即用、又可覆盖"。
  4. 它本身就是一个公共 SDK:把通用能力封装成 jar;需要别的能力(如 client-sdk)可用同样套路再建。
  5. 分发与版本:教学项目单仓模块直接依赖;企业跨仓库发 Maven 私服;版本集中管理,SNAPSHOT 联调、RELEASE 上线。