第 1 章:Spring Cloud Alibaba 项目骨架搭建

本章目标:创建 Spring Cloud Alibaba 微服务学习项目的 Maven 多模块骨架,统一依赖版本,建立公共模块,为后续用户、商品、订单、网关、认证等服务开发打基础。


1. 本章目标

本章主要完成以下内容:

1. 创建 cloud-demo Maven 父工程
2. 创建 cloud-common、cloud-api、cloud-gateway、cloud-auth、cloud-user、cloud-product、cloud-order 七个子模块
3. 在父工程中统一管理 JDK、Spring Boot、Spring Cloud、Spring Cloud Alibaba、MyBatis-Plus 等版本
4. 在 cloud-common 中建立统一返回、错误码、业务异常、全局异常处理等公共基础能力
5. 执行 mvn clean package,确认整个多模块项目可以正常编译

本章暂时不开发用户、商品、订单业务,也暂时不接入 Nacos、MySQL、Gateway、Feign 等组件。


2. 为什么要先搭建父工程

微服务项目一般会拆分成多个独立服务,例如:

用户服务
商品服务
订单服务
认证服务
网关服务

如果每个服务都单独管理自己的依赖版本,后续很容易出现版本不一致、依赖冲突、运行时报错等问题。

因此,正式开发业务之前,需要先通过 Maven 父工程统一管理:

项目模块
依赖版本
插件版本
公共配置

这样可以保证后续每个服务都在同一套版本体系下开发,减少兼容性问题。

一句话总结:

父工程负责统一版本,公共模块负责统一规范。

3. 项目结构

本章创建的项目结构如下:

cloud-demo
├── pom.xml
├── cloud-common
│   ├── pom.xml
│   └── src/main/java/com/example/cloud/common
├── cloud-api
│   └── pom.xml
├── cloud-gateway
│   └── pom.xml
├── cloud-auth
│   └── pom.xml
├── cloud-user
│   └── pom.xml
├── cloud-product
│   └── pom.xml
└── cloud-order
    └── pom.xml

各模块职责如下:

模块 作用
cloud-common 公共返回、错误码、异常处理、工具类
cloud-api 后续存放 Feign 接口、DTO、服务间调用契约
cloud-gateway 后续作为统一网关
cloud-auth 后续实现登录认证和 JWT 签发
cloud-user 用户服务
cloud-product 商品服务
cloud-order 订单服务

4. 技术版本

本项目统一使用以下版本:

JDK:17
Spring Boot:2.7.18
Spring Cloud:2021.0.8
Spring Cloud Alibaba:2021.0.5.0
MyBatis-Plus:3.5.7
MySQL Driver:8.0.33
Lombok:1.18.30
JWT:0.11.5

其中:

Spring Boot 负责单个服务的快速开发与启动
Spring Cloud 负责 Gateway、OpenFeign、LoadBalancer 等官方微服务组件
Spring Cloud Alibaba 负责 Nacos、Sentinel 等 Alibaba 生态组件

IDEA截图:

创建项目
初始化完成
主目录删除src
点击新建目录创建子模块,也可以选择创建子模块,创建目录方式更干净
依然手动创建子模块,并手写pom.xml,可以使用这个模块,稍微改下就行:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example.cloud</groupId>
        <artifactId>cloud-demo</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>换成模块名</artifactId>
    <packaging>jar</packaging>

</project>

5. 父工程 pom.xml 关键配置

父工程 cloud-demo/pom.xml 的核心作用是:

1. 声明当前项目是 Maven 父工程
2. 管理所有子模块
3. 统一管理依赖版本
4. 统一管理插件版本

5.1 packaging 必须是 pom

<packaging>pom</packaging>

如果不写,Maven 默认会认为当前工程是 jar 项目。父工程本身不打业务 jar,它的作用是管理子模块,所以必须声明为 pom

5.2 modules 管理子模块

<modules>
    <module>cloud-common</module>
    <module>cloud-api</module>
    <module>cloud-gateway</module>
    <module>cloud-auth</module>
    <module>cloud-user</module>
    <module>cloud-product</module>
    <module>cloud-order</module>
</modules>

执行:

mvn clean package

时,Maven 会按照模块依赖关系依次构建这些子模块。

5.3 dependencyManagement 统一依赖版本

父工程中通过 dependencyManagement 管理依赖版本,例如:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

注意:

dependencyManagement 只负责版本约束,不会真正引入依赖。
dependencies 才会真正引入依赖。

例如父工程管理了 MyBatis-Plus 的版本,不代表所有模块都自动引入了 MyBatis-Plus。只有子模块自己写了依赖,才会真正引入。


父POM如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.cloud</groupId>
    <artifactId>cloud-demo</artifactId>
    <version>1.0.0</version>
<!--    父工程必须声明,否则默认为jar-->
    <packaging>pom</packaging>

    <name>cloud-demo</name>
    <description>Spring Cloud Alibaba microservice demo</description>

    <modules>
        <module>cloud-common</module>
        <module>cloud-api</module>
        <module>cloud-gateway</module>
        <module>cloud-auth</module>
        <module>cloud-user</module>
        <module>cloud-product</module>
        <module>cloud-order</module>
    </modules>

    <properties>
<!--        给 Spring Boot 生态看的统一 Java 版本属性-->
        <java.version>17</java.version>
<!--        告诉编译器源码按 Java 17 语法处理-->
        <maven.compiler.source>17</maven.compiler.source>
<!--        告诉编译器生成 Java 17 字节码-->
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <spring-boot.version>2.7.18</spring-boot.version>
        <spring-cloud.version>2021.0.8</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <mysql.version>8.0.33</mysql.version>
        <lombok.version>1.18.30</lombok.version>
        <jjwt.version>0.11.5</jjwt.version>
    </properties>
<!--    只管理依赖版本-->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot 版本管理 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud 版本管理 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud Alibaba 版本管理 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- MyBatis-Plus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>

            <!-- MySQL 驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>

            <!-- Lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>

            <!-- JWT,后续 cloud-auth 使用 -->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-api</artifactId>
                <version>${jjwt.version}</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-impl</artifactId>
                <version>${jjwt.version}</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-jackson</artifactId>
                <version>${jjwt.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
<!--        管理插件版本和默认配置-->
        <pluginManagement>
            <plugins>
                <!-- 编译插件,统一 JDK17 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.11.0</version>
                    <configuration>
                        <source>17</source>
                        <target>17</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>

                <!-- Spring Boot 打包插件,业务模块后续使用 -->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>

6. cloud-common 公共模块

cloud-common 是公共模块,用于存放所有服务都可能用到的基础能力。

本章创建了以下公共类:

Result<T>:统一返回结果
ErrorCode:统一错误码
BizException:业务异常
GlobalExceptionHandler:全局异常处理器

6.1 cloud-common 的 pom.xml

cloud-common 当前引入:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

原因:

spring-boot-starter-web:提供 @RestControllerAdvice、@ExceptionHandler 等 Web 异常处理能力
spring-boot-starter-validation:后续 DTO 参数校验会用到
lombok:减少 getter、setter、构造方法等样板代码

注意:cloud-common 是普通 jar 模块,不是启动服务,因此不需要启动类,也不需要 application.yml


完整pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example.cloud</groupId>
        <artifactId>cloud-demo</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>cloud-common</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <!-- Web 依赖:为了使用 @RestControllerAdvice、@ExceptionHandler -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 参数校验:后续 DTO 参数校验使用 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- Lombok:减少 getter/setter 等样板代码 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>

7. 统一返回 Result

统一返回的目标是让所有接口都保持相同结构:

{
  "code": 0,
  "message": "success",
  "data": {}
}

字段含义:

字段 说明
code 业务状态码
message 提示信息
data 返回数据

常用方法:

Result.success()
Result.success(data)
Result.fail(code, message)
Result.fail(errorCode)

统一返回的好处:

前端处理更统一
接口风格更规范
异常返回更清晰
后续网关、Feign、日志排查更方便

代码:

package com.example.cloud.common.result;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {

    private Integer code;

    private String message;

    private T data;

    public static <T> Result<T> success() {
        return new Result<>(0, "success", null);
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(0, "success", data);
    }

    public static <T> Result<T> fail(Integer code, String message) {
        return new Result<>(code, message, null);
    }

    public static <T> Result<T> fail(ErrorCode errorCode) {
        return new Result<>(errorCode.getCode(), errorCode.getMessage(), null);
    }

    public static <T> Result<T> fail(ErrorCode errorCode, String message) {
        return new Result<>(errorCode.getCode(), message, null);
    }
}

8. 统一错误码 ErrorCode

错误码用于规范系统中的常见错误:

SUCCESS:成功
PARAM_ERROR:请求参数错误
UNAUTHORIZED:未登录或登录已过期
FORBIDDEN:没有权限访问
NOT_FOUND:资源不存在
BIZ_ERROR:业务处理失败
SYSTEM_ERROR:系统异常
REMOTE_CALL_ERROR:远程服务调用失败

后续项目中不要随便返回字符串错误,而是尽量通过统一错误码表达错误类型。


完整代码:

package com.example.cloud.common.result;

import lombok.Getter;

@Getter
public enum ErrorCode {

    SUCCESS(0, "success"),

    PARAM_ERROR(40000, "请求参数错误"),

    UNAUTHORIZED(40100, "未登录或登录已过期"),

    FORBIDDEN(40300, "没有权限访问"),

    NOT_FOUND(40400, "资源不存在"),

    BIZ_ERROR(50000, "业务处理失败"),

    SYSTEM_ERROR(50001, "系统异常"),

    REMOTE_CALL_ERROR(50002, "远程服务调用失败");

    private final Integer code;

    private final String message;

    ErrorCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

9. 业务异常 BizException

业务异常用于主动抛出可预期的业务错误。

例如后续商品库存不足时,可以抛出:

throw new BizException(ErrorCode.BIZ_ERROR, "商品库存不足");

它和系统异常的区别是:

业务异常:用户操作或业务规则导致的可预期错误
系统异常:代码 bug、依赖故障、数据库异常等不可预期错误

完整代码:

package com.example.cloud.common.exception;

import com.example.cloud.common.result.ErrorCode;
import lombok.Getter;

@Getter
public class BizException extends RuntimeException {

    private final Integer code;

    public BizException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.code = errorCode.getCode();
    }

    public BizException(ErrorCode errorCode, String message) {
        super(message);
        this.code = errorCode.getCode();
    }

    public BizException(Integer code, String message) {
        super(message);
        this.code = code;
    }
}

10. 全局异常处理 GlobalExceptionHandler

全局异常处理器的作用是统一捕获异常,并转换成统一返回格式。

主要处理:

BizException
MethodArgumentNotValidException
BindException
Exception

处理逻辑:

业务异常:返回业务错误码和错误信息
参数校验异常:返回参数错误
未知异常:记录 error 日志,返回系统异常

这样 Controller 中不需要到处写 try-catch,代码更干净。


完整代码:

package com.example.cloud.common.exception;

import com.example.cloud.common.result.ErrorCode;
import com.example.cloud.common.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BizException.class)
    public Result<Void> handleBizException(BizException e) {
        log.warn("业务异常:{}", e.getMessage());
        return Result.fail(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getFieldErrors().isEmpty()
                ? ErrorCode.PARAM_ERROR.getMessage()
                : e.getBindingResult().getFieldErrors().get(0).getDefaultMessage();

        log.warn("参数校验异常:{}", message);
        return Result.fail(ErrorCode.PARAM_ERROR, message);
    }

    @ExceptionHandler(BindException.class)
    public Result<Void> handleBindException(BindException e) {
        String message = e.getBindingResult().getFieldErrors().isEmpty()
                ? ErrorCode.PARAM_ERROR.getMessage()
                : e.getBindingResult().getFieldErrors().get(0).getDefaultMessage();

        log.warn("参数绑定异常:{}", message);
        return Result.fail(ErrorCode.PARAM_ERROR, message);
    }

    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.fail(ErrorCode.SYSTEM_ERROR);
    }
}

11. 编译验证

在项目根目录执行:

mvn clean package

在这里插入图片描述

本次构建结果:

cloud-demo ......................................... SUCCESS
cloud-common ....................................... SUCCESS
cloud-api .......................................... SUCCESS
cloud-gateway ...................................... SUCCESS
cloud-auth ......................................... SUCCESS
cloud-user ......................................... SUCCESS
cloud-product ...................................... SUCCESS
cloud-order ........................................ SUCCESS
BUILD SUCCESS

这说明:

父工程结构正确
子模块识别正常
cloud-common 中的 4 个 Java 类编译成功
Maven 多模块构建成功

构建过程中部分模块出现:

JAR will be empty - no content was marked for inclusion!

这是正常现象。因为 cloud-apicloud-gatewaycloud-authcloud-usercloud-productcloud-order 当前只创建了 pom.xml,还没有 Java 代码,所以 Maven 提示 jar 为空。后续章节逐步添加代码后,这个警告会自然消失。


12. 本章常见问题

12.1 dependencyManagement 和 dependencies 有什么区别?

dependencyManagement 只管理版本,不会真正引入依赖。

dependencies 才会真正引入依赖。

父工程中通常使用 dependencyManagement 统一版本,子模块按需使用 dependencies 引入具体依赖。

12.2 为什么 Spring Cloud 和 Spring Cloud Alibaba 要同时引入?

因为它们负责的组件不同。

Spring Cloud:Gateway、OpenFeign、LoadBalancer 等官方组件
Spring Cloud Alibaba:Nacos、Sentinel 等 Alibaba 生态组件

所以父工程中需要同时导入两个 BOM,后续子模块再按需引入具体 starter。

12.3 空 jar 警告是不是错误?

不是。

当前部分模块还没有 Java 代码,所以 Maven 打包时提示 jar 为空。这不影响构建成功,也不影响后续开发。

12.4 cloud-common 为什么需要 spring-boot-starter-web?

因为 GlobalExceptionHandler 使用了:

@RestControllerAdvice
@ExceptionHandler

这些注解属于 Spring Web 异常处理体系,所以 cloud-common 需要引入 Web 相关依赖。


13. 本章结论

本章完成了 Spring Cloud Alibaba 微服务项目的基础骨架搭建。

当前项目已经具备:

Maven 多模块结构
统一版本管理
公共模块基础能力
全局异常处理基础
可正常编译的项目骨架

下一章将进入:

第 2 章:用户服务 cloud-user 开发

下一章会开始接入:

Spring Boot Web
MySQL
MyBatis-Plus
用户表
用户 CRUD 接口
统一返回
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐