1. 首页
  2. 我的总结
  3. yudao-cloud学习

yudao-cloud学习

  • 发布于 2024-09-06
  • 128 次阅读

快速启动(前端项目)

目标:将项目 yudao-cloud (opens new window)的前端项目运行起来 🛫

整个过程非常简单,预计 5 分钟就可以完成,取决于大家的网速。

↓↓↓ 技术交流群,一起苦练技术基本功,每日精进 30 公里!↓↓↓

1. 管理后台

项目的管理后台有 4 个版本:

  • yudao-ui-admin-vue3 (opens new window):基于 Vue3 + element-plus

  • yudao-ui-admin-vben (opens new window):基于 Vue3 + vben(ant-design-vue)

  • yudao-ui-admin-vue2 (opens new window):基于 Vue2 + element-ui

  • yudao-ui-admin-uniapp (opens new window):基于 Vue2 + uni-app

#1.1 Vue3 + element-plus 版本

yudao-ui-admin-vue3 (opens new window)是前端 Vue3 管理后台项目。

① 克隆 https://github.com/yudaocode/yudao-ui-admin-vue3.git (opens new window)项目,并 Star 关注下该项目。

② 在根目录执行如下命令,进行启动:

# 安装 pnpm,提升依赖的安装速度
npm config set registry https://registry.npmmirror.com
npm install -g pnpm
# 安装依赖
pnpm install

# 启动服务
npm run dev

③ 启动完成后,浏览器会自动打开 http://localhost:80 (opens new window)地址,可以看到前端界面。

友情提示:Vue3 使用 Vite 构建,所以它存在如下的情况,都是正常的:

  1. 项目启动很快,浏览器打开需要等待 1 分钟左右,请保持耐心。

  2. 点击菜单,感觉会有一点卡顿,因为 Vite 采用懒加载机制。不用担心,最终部署到生产环境,就不存在这个问题了。

详细说明,可见 《为什么有人说 Vite 快,有人却说 Vite 慢?》 (opens new window)文章。

疑问:我是前端工程师,不想启动后端项目,怎么办?

可以将上述的 npm run dev 命令,替代成 npm run dev-server 命令。

远程 演示环境的后端服务,只允许 GET 请求,不允许 POSTPUTDELETE 等请求。

#1.2 Vue3 + vben(ant-design-vue) 版本

yudao-ui-admin-vue3 (opens new window)是前端 Vue3 + vben(ant-design-vue) 管理后台项目。

① 克隆 https://github.com/yudaocode/yudao-ui-admin-vben.git (opens new window)项目,并 Star 关注下该项目。

② 在根目录执行如下命令,进行启动:

# 安装 pnpm,提升依赖的安装速度
npm config set registry https://registry.npmmirror.com
npm install -g pnpm
# 安装依赖
pnpm install

# 启动服务
npm run dev

③ 启动完成后,浏览器会自动打开 http://localhost:80 (opens new window)地址,可以看到前端界面。

疑问:我是前端工程师,不想启动后端项目,怎么办?

可以将上述的 npm run dev 命令,替代成 npm run front 命令。

远程 演示环境的后端服务,只允许 GET 请求,不允许 POSTPUTDELETE 等请求。

#1.3 Vue2 + element-ui 版本

yudao-ui-admin-vue2 (opens new window)是前端 Vue2 管理后台项目。

① 克隆 https://github.com/yudaocode/yudao-ui-admin-vue2.git (opens new window)项目,并 Star 关注下该项目。

② 在根目录执行如下命令,进行启动:

# 安装 Yarn,提升依赖的安装速度
npm install --global yarn
# 安装依赖
yarn install

# 启动服务
npm run local

② 启动完成后,浏览器会自动打开 http://localhost:1024 (opens new window)地址,可以看到前端界面。

疑问:我是前端工程师,不想启动后端项目,怎么办?

可以将上述的 npm run local 命令,替代成 npm run front 命令。

远程 演示环境的后端服务,只允许 GET 请求,不允许 POSTPUTDELETE 等请求。

#1.4 Vue2 + uni-app 版本

yudao-ui-admin-uniapp (opens new window)是前端 uni-app 管理后台项目。

① 克隆 https://github.com/yudaocode/yudao-ui-admin-uniapp.git (opens new window)项目,并 Star 关注下该项目。

② 下载 HBuilder (opens new window)工具,并进行安装。

③ 点击 HBuilder 的 [文件 -> 导入 -> 从本地项目导入...] 菜单,选择项目的 yudao-ui-admin-uniapp 目录。

④ 执行如下命令,安装 npm 依赖:

# 安装 npm 依赖
npm i

⑤ 点击 HBuilder 的 [运行 -> 运行到内置浏览器] 菜单,使用 H5 的方式运行。成功后,界面如下图所示:

友情提示:登录时,滑块验证码,在内存浏览器可能存在兼容性的问题,此时使用 Chrome 浏览器,并使用“开发者工具”,设置为 iPhone 12 Pro 模式!

疑问:我是前端工程师,不想启动后端项目,怎么办?

修改 config.js 配置文件的 baseUrl 后端服务的地址为 'http://api-dashboard.yudao.iocoder.cn。如下图所示:

2. uni-app 商城移动端

yudao-mall-uniapp (opens new window)是前端 uni-app 商城移动端项目。

前置任务:

需要参考 《商城手册 —— 功能开启》 文档,将商城的后端启动。

① 克隆 https://github.com/yudaocode/yudao-mall-uniapp (opens new window)项目,并 Star 关注下该项目。

② 下载 HBuilder (opens new window)工具,并进行安装。

③ 点击 HBuilder 的 [文件 -> 导入 -> 从本地项目导入...] 菜单,选择克隆的 yudao-mall-uniapp 目录

④ 执行如下命令,安装 npm 依赖:

# 安装 npm 依赖
npm i

⑤ 点击 HBuilder 的 [运行 -> 运行到浏览器 -> Chrome] 菜单,使用 H5 的方式运行。成功后,界面如下图所示:

疑问:我是前端工程师,不想启动后端项目,怎么办?

搜索 http://127.0.0.1:48080 关键字,修改后端服务的地址为 'http://api-dashboard.yudao.iocoder.cn/。如下图所示:

快速启动(后端项目)

目标:使用 IDEA 工具,将后端项目 yudao-cloud (opens new window)运行起来 🛫

整个过程非常简单,预计 15 分钟就可以完成,取决于大家的网速。

↓↓↓ 技术交流群,一起苦练技术基本功,每日精进 30 公里!↓↓↓

1. 克隆代码

使用 IDEA (opens new window)克隆 https://github.com/YunaiV/yudao-cloud (opens new window)仓库的最新代码,并给该仓库一个 Star (opens new window)

注意:不建议使用 Eclipse,因为它没有支持 Lombok 和 Mapstruct 插件。

克隆完成后,耐心等待 Maven 下载完相关的依赖。一定要注意:

① 默认情况下,使用 master 分支,它对应 JDK 8 + Spring Boot 2.7 版本。

② 如果你想体验 JDK 17/21 + Spring Boot 3.2 版本,需要切换到 master-jdk17 分支。3.1 初始化 MySQL

友情提示?

如果你是 PostgreSQL、Oracle、SQL Server、DM、大金 等其它数据库,也是可以的。

因为我主要使用 MySQL数据库为主,所以其它数据库的 SQL 文件可能存在滞后,可以加入 用户群 反馈。

补充说明?

由于工作较忙,暂时未拆分到多个数据库,可以按照前缀自行处理:

  • system_ 前缀,属于 yudao-module-system 服务

  • infra_ 前缀,属于 yudao-module-infra 服务

项目使用 MySQL 存储数据,所以需要启动一个 MySQL 服务。

① 创建一个名字为 ruoyi-vue-pro 数据库,【只要】 执行对应数据库类型的 sql (opens new window)目录下的 ruoyi-vue-pro.sql SQL 文件,进行初始化。

3. 基础设施(必选)

本小节的基础设施【必须】安装,否则项目无法启动。

#3.1 初始化 MySQL

友情提示?

如果你是 PostgreSQL、Oracle、SQL Server、DM、大金 等其它数据库,也是可以的。

因为我主要使用 MySQL数据库为主,所以其它数据库的 SQL 文件可能存在滞后,可以加入 用户群 反馈。

补充说明?

由于工作较忙,暂时未拆分到多个数据库,可以按照前缀自行处理:

  • system_ 前缀,属于 yudao-module-system 服务

  • infra_ 前缀,属于 yudao-module-infra 服务

项目使用 MySQL 存储数据,所以需要启动一个 MySQL 服务。

① 创建一个名字为 ruoyi-vue-pro 数据库,【只要】 执行对应数据库类型的 sql (opens new window)目录下的 ruoyi-vue-pro.sql SQL 文件,进行初始化。

② 默认配置下,MySQL 需要启动在 3306 端口,并且账号是 root,密码是 123456。如果不一致,需要修改 application-local.yaml 配置文件。

3.2 初始化 Redis

项目使用 Redis 缓存数据,所以需要启动一个 Redis 服务。

不会安装的胖友,可以选择阅读下文,良心的艿艿。

默认配置下,Redis 启动在 6379 端口,不设置账号密码。如果不一致,需要修改 application-local.yaml 配置文件

3.3 初始化 Nacos

项目使用 Nacos 作为注册中心配置中心,参考 《芋道 Nacos 极简入门》 (opens new window)文章,进行安装,只需要看该文的 「2. 单机部署(最简模式)」 即可。

安装完成之后,需要创建 dev 命名空间,如下图所示:

后端手册--新建服务

本章节,将介绍如何新建名字为 yudao-module-demo 的示例服务,并添加 RESTful API 接口。

虽然内容看起来比较长,是因为艿艿写的比较详细,大量截图,保姆级教程!其实只有 6 个步骤,保持耐心,跟着艿艿一点点来。🙂 完成之后,你会对整个 项目结构 有更充分的了解。

👍 相关视频教程

1. 新建 demo 模块

① 选择 File -> New -> Module 菜单,如下图所示:


② 选择 Maven 类型,选择父模块为 yudao,输入名字为 yudao-module-demo,并点击 Create 按钮,如下图所示:

③ 打开 yudao-module-demo 模块,删除 src 文件,如下图所示:


④ 打开 yudao-module-demo 模块的 pom.xml 文件,修改内容如下:

提示

<!-- --> 部分,只是注释,不需要写到 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">
    <parent>
        <artifactId>yudao</artifactId>
        <groupId>cn.iocoder.cloud</groupId>
        <version>${revision}</version> <!-- 1. 修改 version 为 ${revision} -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>yudao-module-demo</artifactId>
    <packaging>pom</packaging> <!-- 2. 新增 packaging 为 pom -->

    <name>${project.artifactId}</name> <!-- 3. 新增 name 为 ${project.artifactId} -->
    <description> <!-- 4. 新增 description 为该模块的描述 -->
        demo 模块,主要实现 XXX、YYY、ZZZ 等功能。
    </description>

</project>

#2. 新建 demo-api 子模块

① 新建 yudao-module-demo-api 子模块,整个过程和“新建 demo 模块”是一致的,如下图所示:

② 打开 yudao-module-demo-api 模块的 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">
    <parent>
        <artifactId>yudao-module-demo</artifactId>
        <groupId>cn.iocoder.cloud</groupId>
        <version>${revision}</version> <!-- 1. 修改 version 为 ${revision} -->
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>yudao-module-demo-api</artifactId>
    <packaging>jar</packaging> <!-- 2. 新增 packaging 为 jar -->

    <name>${project.artifactId}</name> <!-- 3. 新增 name 为 ${project.artifactId} -->
    <description> <!-- 4. 新增 description 为该模块的描述 -->
        demo 模块 API,暴露给其它模块调用
    </description>

    <dependencies>  <!-- 5. 新增 yudao-common 依赖 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-common</artifactId>
        </dependency>
    </dependencies>

</project>

③ 【可选】新建 cn.iocoder.yudao.module.demo 基础包,其中 demo 为模块名。之后,新建 apienums 包。如下图所示:

3. 新建 demo-biz 子模块

① 新建 yudao-module-demo-biz 子模块,整个过程和“新建 demo 模块”也是一致的,如下图所示:

② 打开 yudao-module-demo-biz 模块的 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">
    <parent>
        <artifactId>yudao-module-demo</artifactId>
        <groupId>cn.iocoder.cloud</groupId>
        <version>${revision}</version> <!-- 1. 修改 version 为 ${revision} -->
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging> <!-- 2. 新增 packaging 为 jar -->

    <artifactId>yudao-module-demo-biz</artifactId>

    <name>${project.artifactId}</name> <!-- 3. 新增 name 为 ${project.artifactId} -->
    <description> <!-- 4. 新增 description 为该模块的描述 -->
        demo 模块,主要实现 XXX、YYY、ZZZ 等功能。
    </description>

    <dependencies>  <!-- 5. 新增依赖,这里引入的都是比较常用的业务组件、技术组件 -->
        <!-- Spring Cloud 基础 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-env</artifactId>
        </dependency>

        <!-- 依赖服务 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-module-system-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-module-infra-api</artifactId>
            <version>${revision}</version>
        </dependency>

        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-module-demo-api</artifactId>
            <version>${revision}</version>
        </dependency>

        <!-- 业务组件 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
        </dependency>

        <!-- Web 相关 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-security</artifactId>
        </dependency>

        <!-- DB 相关 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-redis</artifactId>
        </dependency>

        <!-- RPC 远程调用相关 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-rpc</artifactId>
        </dependency>

        <!-- Registry 注册中心相关 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- Config 配置中心相关 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!-- Job 定时任务相关 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-job</artifactId>
        </dependency>

        <!-- 消息队列相关 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-mq</artifactId>
        </dependency>

        <!-- Test 测试相关 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-test</artifactId>
        </dependency>

        <!-- 工具类相关 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-excel</artifactId>
        </dependency>

        <!-- 监控相关 -->
        <dependency>
            <groupId>cn.iocoder.cloud</groupId>
            <artifactId>yudao-spring-boot-starter-monitor</artifactId>
        </dependency>
    </dependencies>

    <build>
        <!-- 设置构建的 jar 包名 -->
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <!-- 打包 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring.boot.version}</version>
                <configuration>
                    <fork>true</fork>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

③ 【必选】新建 cn.iocoder.yudao.module.demo 基础包,其中 demo 为模块名。之后,新建 controller.admincontroller.user 等包。如下图所示:

友情提示:

注意!【JDK 17 + Spring Boot 3.X 版本】和【JDK 8 + Spring Boot 2.X 版本】的代码略有不同,下面会分别展示。

【JDK 17 + Spring Boot 3.X 版本】其中 SecurityConfiguration 的 Java 代码如下:

package cn.iocoder.yudao.module.demo.framework.security.config;

import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
import cn.iocoder.yudao.module.infra.enums.ApiConstants;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;

/**
 * Demo 模块的 Security 配置
 */
@Configuration(proxyBeanMethods = false)
public class SecurityConfiguration {

    @Bean
    public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
        return new AuthorizeRequestsCustomizer() {

            @Override
            public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
                // Swagger 接口文档
                registry.requestMatchers("/v3/api-docs/**").permitAll()
                        .requestMatchers("/webjars/**").permitAll()
                        .requestMatchers("/swagger-ui").permitAll()
                        .requestMatchers("/swagger-ui/**").permitAll();
                // Druid 监控
                registry.requestMatchers("/druid/**").permitAll();
                // Spring Boot Actuator 的安全配置
                registry.requestMatchers("/actuator").permitAll()
                        .requestMatchers("/actuator/**").permitAll();
                // RPC 服务的安全配置
                registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll();
            }

        };
    }

}

【JDK 8 + Spring Boot 2.X 版本】其中 SecurityConfiguration 的 Java 代码如下:

package cn.iocoder.yudao.module.demo.framework.security.config;

import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
import cn.iocoder.yudao.module.system.enums.ApiConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;

/**
 * Demo 模块的 Security 配置
 */
@Configuration(proxyBeanMethods = false)
public class SecurityConfiguration {

    @Bean
    public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
        return new AuthorizeRequestsCustomizer() {

            @Override
            public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
                // Swagger 接口文档
                registry.antMatchers("/v3/api-docs/**").permitAll()
                        .antMatchers("/webjars/**").permitAll()
                        .antMatchers("/swagger-ui").permitAll()
                        .antMatchers("/swagger-ui/**").permitAll();
                // Druid 监控
                registry.antMatchers("/druid/**").anonymous();
                // Spring Boot Actuator 的安全配置
                registry.antMatchers("/actuator").anonymous()
                        .antMatchers("/actuator/**").anonymous();
                // RPC 服务的安全配置
                registry.antMatchers(ApiConstants.PREFIX + "/**").permitAll();
            }

        };
    }

}

其中 DemoServerApplication 的 Java 代码如下:

package cn.iocoder.yudao.module.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 项目的启动类
 *
 * @author 芋道源码
 */
@SpringBootApplication
public class DemoServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoServerApplication.class, args);
    }

}

④ 打开 Maven 菜单,点击刷新按钮,让引入的 Maven 依赖生效。如下图所示:

⑤ 在 resources 目录下,新建配置文件。如下图所示:


其中 application.yml 的配置如下:

spring:
  application:
  name: demo-server

  profiles:
    active: local
  
  main:
    allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
    allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务

  config:
    import:
      - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置
      - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置

  # Servlet 配置
  servlet:
    # 文件上传相关配置项
    multipart:
      max-file-size: 16MB # 单个文件大小
      max-request-size: 32MB # 设置总上传的文件大小

  # Jackson 配置项
  jackson:
    serialization:
      write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
      write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
      write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
      fail-on-empty-beans: false # 允许序列化无属性的 Bean

  # Cache 配置项
  cache:
    type: REDIS
    redis:
      time-to-live: 1h # 设置过期时间为 1 小时

server:
  port: 48099

logging:
  file:
    name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径

--- #################### 接口文档配置 ####################

springdoc:
  api-docs:
    enabled: true # 1. 是否开启 Swagger 接文档的元数据
    path: /v3/api-docs
  swagger-ui:
    enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面
    path: /swagger-ui.html
  default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档

knife4j:
  enable: true # 2.2 是否开启 Swagger 文档的 Knife4j UI 界面
  setting:
    language: zh_cn

# MyBatis Plus 的配置项
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
  global-config:
    db-config:
      id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
      #      id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库
      #      id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
      #      id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    banner: false # 关闭控制台的 Banner 打印
  type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject
  encryptor:
    password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成

mybatis-plus-join:
  banner: false # 关闭控制台的 Banner 打印

# VO 转换(数据翻译)相关
easy-trans:
  is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口

--- #################### RPC 远程调用相关配置 ####################

--- #################### MQ 消息队列相关配置 ####################

--- #################### 定时任务相关配置 ####################

xxl:
  job:
    executor:
      appname: ${spring.application.name} # 执行器 AppName
      logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
    accessToken: default_token # 执行器通讯TOKEN

--- #################### 芋道相关配置 ####################

yudao:
  info:
    version: 1.0.0
    base-package: cn.iocoder.yudao.module.demo
  web:
    admin-ui:
      url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址
  xss:
    enable: false
    exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
      - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  swagger:
    title: 管理后台
    description: 提供管理员管理的所有功能
    version: ${yudao.info.version}
  tenant: # 多租户相关配置项
    enable: true

debug: false
  • spring.application.name 配置项:可以改成你想要的服务名。

  • server.port 配置项:可以改成你想要的端口号。

  • yudao.info.version.base-package 配置项:可以改成你的项目的基准包名。

其中 application-local.yml 的配置如下:

--- #################### 数据库相关配置 ####################
spring:
  # 数据源配置项
  autoconfigure:
    exclude:
      - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
      - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
  datasource:
    druid: # Druid 【监控】相关的全局配置
      web-stat-filter:
        enabled: true
      stat-view-servlet:
        enabled: true
        allow: # 设置白名单,不填则允许所有访问
        url-pattern: /druid/*
        login-username: # 控制台管理用户名和密码
        login-password:
      filter:
        stat:
          enabled: true
          log-slow-sql: true # 慢 SQL 记录
          slow-sql-millis: 100
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
    dynamic: # 多数据源配置
      druid: # Druid 【连接池】相关的全局配置
        initial-size: 1 # 初始连接数
        min-idle: 1 # 最小连接池数量
        max-active: 20 # 最大连接池数量
        max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
        time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
        min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
        max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
        validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
      primary: master
      datasource:
        master:
          url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
          #          url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例
          #          url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例
          #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
          #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro # SQLServer 连接的示例
          #          url: jdbc:dm://10.211.55.4:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
          username: root
          password: 123456
        #          username: sa # SQL Server 连接的示例
        #          password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W # SQL Server 连接的示例
        #          username: SYSDBA # DM 连接的示例
        #          password: SYSDBA # DM 连接的示例
        slave: # 模拟从库,可根据自己需要修改
          lazy: true # 开启懒加载,保证启动速度
          url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
          username: root
          password: 123456

  # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
  redis:
    host: 127.0.0.1 # 地址
    port: 6379 # 端口
    database: 0 # 数据库索引
#    password: 123456 # 密码,建议生产环境开启

--- #################### MQ 消息队列相关配置 ####################

--- #################### 定时任务相关配置 ####################

xxl:
  job:
    admin:
      addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址

--- #################### 服务保障相关配置 ####################

# Lock4j 配置项
lock4j:
  acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
  expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒

--- #################### 监控相关配置 ####################

# Actuator 监控端点的配置项
management:
  endpoints:
    web:
      base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
      exposure:
        include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。

# Spring Boot Admin 配置项
spring:
  boot:
    admin:
      # Spring Boot Admin Client 客户端的相关配置
      client:
        instance:
          service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]

# 日志文件配置
logging:
  level:
    # 配置自己写的 MyBatis Mapper 打印日志
    cn.iocoder.yudao.module.demo.dal.mysql: debug
    org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示

--- #################### 芋道相关配置 ####################

# 芋道配置项,设置当前项目所有自定义的配置
yudao:
  env: # 多环境的配置项
    tag: ${HOSTNAME}
  security:
    mock-enable: true
  access-log: # 访问日志的配置项
    enable: false
  • logging.level.cn.iocoder.yudao.module.demo.dal.mysql 配置项:可以改成你的项目的基准包名。

其中 logback-spring.xml 的配置如下:

<configuration>
    <!-- 引用 Spring Boot 的 logback 基础配置 -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <!-- 变量 yudao.info.base-package,基础业务包 -->
    <springProperty scope="context" name="yudao.info.base-package" source="yudao.info.base-package"/>
    <!-- 格式化输出:%d 表示日期,%X{tid} SkWalking 链路追踪编号,%thread 表示线程名,%-5level:级别从左显示 5 个字符宽度,%msg:日志消息,%n是换行符 -->
    <property name="PATTERN_DEFAULT" value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} | %highlight(${LOG_LEVEL_PATTERN:-%5p} ${PID:- }) | %boldYellow(%thread [%tid]) %boldGreen(%-40.40logger{39}) | %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>

    <!-- 控制台 Appender -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
                <pattern>${PATTERN_DEFAULT}</pattern>
            </layout>
        </encoder>
    </appender>

    <!-- 文件 Appender -->
    <!-- 参考 Spring Boot 的 file-appender.xml 编写 -->
    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
                <pattern>${PATTERN_DEFAULT}</pattern>
            </layout>
        </encoder>
        <!-- 日志文件名 -->
        <file>${LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 滚动后的日志文件名 -->
            <fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
            <!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
            <cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
            <!-- 日志文件,到达多少容量,进行滚动 -->
            <maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
            <!-- 日志文件的总大小,0 表示不限制 -->
            <totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
            <!-- 日志文件的保留天数 -->
            <maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30}</maxHistory>
        </rollingPolicy>
    </appender>
    <!-- 异步写入日志,提升性能 -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志。默认的,如果队列的 80% 已满,则会丢弃 TRACT、DEBUG、INFO 级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能。默认值为 256 -->
        <queueSize>256</queueSize>
        <appender-ref ref="FILE"/>
    </appender>

    <!-- SkyWalking GRPC 日志收集,实现日志中心。注意:SkyWalking 8.4.0 版本开始支持 -->
    <appender name="GRPC" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
                <pattern>${PATTERN_DEFAULT}</pattern>
            </layout>
        </encoder>
    </appender>

    <!-- 本地环境 -->
    <springProfile name="local">
        <root level="INFO">
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
            <appender-ref ref="ASYNC"/>  <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
        </root>
    </springProfile>
    <!-- 其它环境 -->
    <springProfile name="dev,test,stage,prod,default">
        <root level="INFO">
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="ASYNC"/>
            <appender-ref ref="GRPC"/>
        </root>
    </springProfile>

</configuration>

#4. 新建 RESTful API 接口

① 在 controller.admin 包,新建一个 DemoTestController 类,并新建一个 /demo/test/get 接口。代码如下:

4. 新建 RESTful API 接口

① 在 controller.admin 包,新建一个 DemoTestController 类,并新建一个 /demo/test/get 接口。代码如下:

package cn.iocoder.yudao.module.demo.controller.admin;

import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;

@Tag(name = "管理后台 - Test")
@RestController
@RequestMapping("/demo/test")
@Validated
public class DemoTestController {

    @GetMapping("/get")
    @Operation(summary = "获取 test 信息")
    public CommonResult<String> get() {
        return success("true");
    }

}

注意/demo 是该模块所有 RESTful API 的基础路径,/test 是 Test 功能的基础路径。

① 在 controller.app 包,新建一个 AppDemoTestController 类,并新建一个 /demo/test/get 接口。代码如下:

package cn.iocoder.yudao.module.demo.controller.app;

import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;

@Tag(name = "用户 App - Test")
@RestController
@RequestMapping("/demo/test")
@Validated
public class AppDemoTestController {

    @GetMapping("/get")
    @Operation(summary = "获取 test 信息")
    public CommonResult<String> get() {
        return success("true");
    }

}

在 Controller 的命名上,额外增加 App 作为前缀,一方面区分是管理后台还是用户 App 的 Controller,另一方面避免 Spring Bean 的名字冲突。

可能你会奇怪,这里我们定义了两个 /demo/test/get 接口,会不会存在重复导致冲突呢?答案,当然是并不会。原因是:

  • controller.admin 包下的接口,默认会增加 /admin-api,即最终的访问地址是 /admin-api/demo/test/get

  • controller.app 包下的接口,默认会增加 /app-api,即最终的访问地址是 /app-api/demo/test/get

#5. 启动 demo 服务

① 运行 SystemServerApplication 类,将 system 服务启动。运行 InfraServerApplication 类,将 infra 服务启动。

② 运行 DemoServerApplication 类,将新建的 demo 服务进行启动。启动完成后,使用浏览器打开 http://127.0.0.1:48099/doc.html (opens new window)地址,进入该服务的 Swagger 接口文档。

③ 打开“管理后台 - Test”接口,进行 /admin-api/demo/test/get 接口的调试,如下图所示:

④ 打开“用户 App - Test”接口,进行 /app-api/demo/test/get 接口的调试,如下图所示:


6. 网关配置

① 打开 yudao-gateway 网关项目的 application.yml 配置文件,增加 demo 服务的路由配置。代码如下:

友情提示:图中的 /v2/ 都改成 /v3/,或者以下面的文字为准!!!

        - id: demo-admin-api # 路由的编号
          uri: grayLb://demo-server
          predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
            - Path=/admin-api/demo/**
          filters:
            - RewritePath=/admin-api/demo/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v2/api-docs
        - id: demo-app-api # 路由的编号
          uri: grayLb://demo-server
          predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
            - Path=/app-api/demo/**
          filters:
            - RewritePath=/app-api/demo/v3/api-docs, /v3/api-docs
      - name: demo-server
        service-name: demo-server
        url: /admin-api/demo/v3/api-docs

② 运行 GatewayServerApplication 类,将 gateway 网关服务启动。

③ 使用浏览器打开 http://127.0.0.1:48080/doc.html (opens new window)地址,进入网关的 Swagger 接口文档。然后,选择 demo-server 服务,即可进行 /admin-api/demo/test/get/app-api/demo/test/get 接口的调试,如下图所示:

代码生成【单表】(新增功能)

大部分项目里,其实有很多代码是重复的,几乎每个模块都有 CRUD 增删改查的功能,而这些功能的实现代码往往是大同小异的。如果这些功能都要自己去手写,非常无聊枯燥,浪费时间且效率很低,还可能会写错。

所以这种重复性的代码,项目提供了 codegen (opens new window)代码生成器,只需要在数据库中设计好表结构,就可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验。

针对不同的业务场景,项目提供了三种模式:单表、树表、主子表。

本文,我们将演示“单表”的使用,基于代码生成器,在 yudao-module-system 模块中,开发一个【用户组】的功能

1. 数据库表结构设计

设计用户组的数据库表名为 system_group,其建表语句如下:

CREATE TABLE `system_group` (
    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
    `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名字',
    `description` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
    `status` tinyint NOT NULL COMMENT '状态',
    `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户组';

① 表名的前缀,要和 Maven Module 的模块名保持一致。例如说,用户组在 yudao-module-system 模块,所以表名的前缀是 system_

疑问:为什么要保持一致?

代码生成器会自动解析表名的前缀,获得其所属的 Maven Module 模块,简化配置过程。

② 设置 ID 主键,一般推荐使用 bigint 长整形,并设置自增长。

③ 正确设置每个字段是否允许空,代码生成器会根据它生成参数是否允许空的校验规则。

④ 正确设置注释,代码生成器会根据它生成字段名与提示等信息。

⑤ 添加 creatorcreate_timeupdaterupdate_timedeleted 是必须设置的系统字段;如果开启多租户的功能,并且该表需要多租户的隔离,则需要添加 tenant_id 字段。

#2. 代码生成

#2.1 导入表

点击 [基础设施 -> 代码生成] 菜单,点击 [基于 DB 导入] 按钮,选择 system_group 表,后点击 [确认] 按钮。

代码实现?

可见 CodegenBuilder (opens new window)类,自动解析数据库的表结构,生成默认的配置。

#2.2 编辑配置

点击 system_group 所在行的 [编辑] 按钮,修改生成配置。后操作如下:

  • status 字段的显示类型为【下拉框】,字典类型为【系统状态】。

  • description 字段的【查询】取消。

  • idnamedescriptionstatus 字段的【示例】填写上。

字段信息

  • 插入:新增时,是否传递该字段。

  • 编辑:修改时,是否传递该字段。

  • 列表:Table 表格,是否展示该字段。

  • 查询:搜索框,是否支持该字段查询,查询的条件是什么。

  • 允许空:新增或修改时,是否必须传递该字段,用于 Validator 参数校验。

  • 字典类型:在显示类型是下拉框、单选框、复选框时,选择使用的字典。

  • 示例:参数示例,用于 Swagger 接口文档的 example 示例。

  • 将【上级菜单】设置为【系统管理】。

  • 将【前端类型】设置为“前端项目”对应的“前端类型”。例如说,我们这里演示的是 yudao-ui-admin-vue3 前端项目,则选择了【Vue3 Element Plus 标准模版】。

前端项目

前端类型

yudao-ui-admin-vue2

Vue2 Element UI 标准模版

yudao-ui-admin-vue3

Vue3 Element Plus 标准模版、Vue3 Element Plus Schema 模版

yudao-ui-admin-vben

Vue3 Vben 模版

生成信息

  • 生成场景:分成管理后台、用户 App 两种,用于生成 Controller 放在 admin 还是 app 包。

  • 上级菜单:生成场景是管理后台时,需要设置其所属的上级菜单。

  • 前端类型: 提供多种 UI 模版。

    • 【Vue3 Element Plus Schema 模版】,对应 《前端手册 Vue 3.X —— CRUD 组件》 说明。

    • 后端的 application.yaml 配置文件中的 yudao.codegen.front-type 配置项,设置默认的 UI 模版,避免每次都需要设置。

完成后,点击 [提交] 按钮,保存生成配置。

#2.3 预览代码

点击 system_group 所在行的 [预览] 按钮,在线预览生成的代码,检查是否符合预期。

2.4 生成代码

点击 system_group 所在行的 [生成代码] 按钮,下载生成代码的压缩包,双击进行解压。

代码实现?

可见 CodegenEngine (opens new window)类,基于 Velocity 模板引擎,生成具体代码。模板文件,可见 resources/codegen (opens new window)目录。

#3. 代码运行

本小节,我们将生成的代码,复制到项目中,并进行运行。

#3.1 后端运行

① 将生成的后端代码,复制到项目中。操作如下图所示:

② 将 ErrorCodeConstants.java_手动操作 文件的错误码,复制到该模块 ErrorCodeConstants 类中,并设置对应的错误码编号,之后进行删除。操作如下图所示:


③ 将 h2.sql 的 CREATE 语句复制到该模块的 create_tables.sql 文件,DELETE 语句复制到该模块的 clean.sql。操作如下图:

疑问:`create_tables.sql` 和 clean.sql 文件的作用是什么?

项目的单元测试,需要使用到 H2 内存数据库,create_tables.sql 用于创建所有的表结构,clean.sql 用于每个单元测试的方法跑完后清理数据。

然后,运行 GroupServiceImplTest 单元测试,执行通过。

④ 打开数据库工具,运行代码生成的 sql/sql.sql 文件,用于菜单的初始化

⑤ Debug 运行 YudaoServerApplication 类,启动后端项目。通过 IDEA 的 [Actuator -> Mappings] 菜单,可以看到代码生成的 GroupController 的 RESTful API 接口已经生效。


3.2 前端运行

① 将生成的前端代码,复制到项目中。操作如下图所示:

重新执行 npm run dev 命令,启动前端项目。点击 [系统管理 -> 菜单管理] 菜单,点击【刷新菜单缓存】,因为前端项目会缓存菜单在内存中的,所以需要刷新一下。

③ 点击 [系统管理 -> 用户组管理] 菜单,就可以看到用户组的 UI 界面。

至此,我们已经完成了【用户组】功能的代码生成,基本节省了你 80% 左右的开发任务,后续可以根据自己的需求,进行剩余的 20% 的开发!

#4. 后续变更

随着业务的发展,已经生成代码的功能需要变更。继续以【用户组】举例子,它的 system_group 表需要新增一个分类 category 字段,此时不建议使用代码生成器,而是直接修改已经生成的代码:

① 后端:修改 GroupDO 数据实体类、GroupSaveReqVO 保存 VO 类、GroupSaveRespVO 响应 VO 类,新增 category 字段。

② 前端:修改 Vue 的 index.vue 列表和 Form 表单组件,新增 category 字段。

③ 重新编译后后端,并进行启动。

over!非常简单方便,即保证了代码的整洁规范,又不增加过多的开发量。

#5. 常见问题

① 生成的代码结构,有没具体说明?

答:参见 《项目结构》 文档的说明,最好把对应视频好好看下。

② 为什么要分 XXXSaveReqVO、XXXRespVO、XXXPageReqVO 三个 VO 类?

答:星球里 https://t.zsxq.com/14Fc743WH (opens new window)进行了 VO 拆分的讨论,总体大家倾向拆分成三个 VO 类,因为这样更加清晰,而且也不会增加太多的维护工作量。

可能你会想,能不能把 XXXSaveReqVO 和 XXXRespVO 合并成一个呢?有两方面的考虑:

  1. 一般来说,新增/修改是不传递 createTimecreator 等字段,响应是需要返回 createTimecreator 等字段,两者的字段无法不一致。

  2. 一旦 VO 和 DO 拆分开后,调整字段时,例如说新增一个 xxx 字段,两个 VO 的修改成本,和一个 VO 实际是差不多的。

③ 为什么 UI 界面的数据字典,下拉没有选项,或者列表没有展示它的文本?

类似 https://t.zsxq.com/owJzU (opens new window)的问题。

原因是:需要在前端的代码里,枚举一下。例如说 Vue3 + Element-Plus 版本,需要在 yudao-ui-admin-vue3/src/utils/dict.tsDICT_TYPE 添加字典的 type 枚举值。

代码生成【主子表】

友情提示:

本文接 《代码生成【单表】》,请务必先阅读。因为重复的内容,本文会不再赘述!

主子表,指的是一个主表,被多个子表所关联,关联的关系是一对一或一对多。

例如说:主表是【学生】,子表可以是:

  • 子表是【成绩】,两者是“一对多”的关系,一个学生可以有多个成绩。

  • 子表是【班级】,两者是“一对一”的关系,一个学生只能有一个班级。

下面,我们将演示“主子表”的使用,基于代码生成器,在 yudao-module-system 模块中,开发一个【学生】的功能。

友情提示:

目前只有 yudao-ui-admin-vue3 支持主子表,yudao-ui-admin-vue2、yudao-ui-admin-vben 正在适配中!

#0. 主子表模式

针对不同的交互模式,项目提供了三种主子表模式:标准、ERP、内嵌。

#0.1 标准模式

对应 [基础设施 -> 代码生成案例 -> 主子表(标准)] 菜单。

在新增和修改时,主表和子表在一个弹窗表单中,一起提交。如下图所示:

0.2 内嵌模式

对应 [基础设施 -> 代码生成案例 -> 主子表(内嵌)] 菜单。

在「标准模式」的基础之上,列表 内嵌 子表的列表。如下图所示:


0.3 ERP 模式

对应 [基础设施 -> 代码生成案例 -> 主子表(ERP)] 菜单。

主表和子表,独立列表,也独立表单。如下图所示:

1. 数据库表结构设计

① 设计 主表 的数据库表名为 system_student 学生表,其建表语句如下:

CREATE TABLE `system_student` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
  `birthday` datetime NOT NULL COMMENT '出生日期',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '简介',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学生表';

② 设计 子表 的数据库表名为 system_student_course 学生课程表,其建表语句如下:

CREATE TABLE `system_student_course` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `student_id` bigint NOT NULL COMMENT '学生编号',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
  `score` tinyint NOT NULL COMMENT '分数',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学生课程表';

它和主表的关系是一对多,一个学生可以有多个课程,通过 student_id 字段进行关联。

③ 设计 子表 的数据表名为 system_student_grade 学生班级表,其建表语句如下:

CREATE TABLE `system_student_grade` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
  `student_id` bigint NOT NULL COMMENT '学生编号',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
  `teacher` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '班主任',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学生班级表';

它和主表的关系是一对一,一个学生只能有一个班级,通过 student_id 字段进行关联。

#2. 代码生成

2.1 导入表

点击 [基础设施 -> 代码生成] 菜单,点击 [基于 DB 导入] 按钮,选择 system_studentsystem_student_coursesystem_student_grade 表,后点击 [确认] 按钮。

#2.2 编辑配置(主表)

点击 system_student 所在行的 [编辑] 按钮,修改生成配置。后操作如下:

  • 将【生成模版】设置为【主表(标准模式)】。🔥最最关键的步骤!

  • 将【上级菜单】设置为【系统管理】。

  • 将【前端类型】设置为“前端项目”对应的“前端类型”。例如说,我们这里演示的是 yudao-ui-admin-vue3 前端项目,则选择了【Vue3 Element Plus 标准模版】。

#2.3 编辑配置(子表)

① 点击 system_student_course 所在行的 [编辑] 按钮,修改生成配置。后操作如下:

  • 将【生成模版】设置为【子表】。🔥最最关键的步骤!

  • 业务名:一般建议和【主表】保持一致,所以这里改成了 student

  • 主表信息:将【关联的主表】设置为 system_student 表,将【子表关联的字段】设置为 student_id 字段,将【关联关系】设置为“一对多”。

② 点击 system_student_grade 所在行的 [编辑] 按钮,修改生成配置。后操作如下:

  • (同上)将【生成模版】设置为【子表】。🔥最最关键的步骤!

  • (同上)业务名:一般建议和【主表】保持一致,所以这里改成了 student

  • (基本同上,关联关系不同)主表信息:将【关联的主表】设置为 system_student 表,将【子表关联的字段】设置为 student_id 字段,将【关联关系】设置为“一对一”。

#2.4 预览代码

点击 system_student 所在行的 [预览] 按钮,在线预览生成的代码,检查是否符合预期。

#2.5 生成代码

点击 system_student 所在行的 [生成] 按钮,生成代码。

#3. 代码运行

《代码生成【单表】》 的「3. 代码运行」一致,就不重复赘述。

支付手册--功能开启

项目提供统一的支付中心,提供微信、支付宝等支付渠道的支付、退款等能力,方便业务模块进行快速接入,无需关注各种繁琐的支付 API。


1. 概述

它由如下 3 部分组成:

yudao-spring-boot-starter-biz-pay (opens new window)组件:对接微信、支付宝等支付,提供统一的 PayClient 支付客户端 (opens new window)


yudao-module-pay (opens new window)后端模块:实现支付中心的后端功能,包括支付、退款等能力。

③ 支付中心的前端,提供支付中心的管理后台,可进行支付渠道的配置、支付订单、退款单的查看等操作。

  • Vue2 版本:@/views/pay (opens new window)目录

  • Vue3 版本:@/views/pay (opens new window)目录

#2. 功能开启

考虑到编译速度,默认 yudao-module-pay 模块是关闭的,需要手动开启。步骤如下:

  • 第一步,导入支付的 SQL 数据库脚本

  • 第二步,启动 yudao-module-pay 服务

  • 第三步,开启支付相关的 Job 任务

#2.1 第一步,导入 SQL

点击 pay-2024-01-05.sql.zip (opens new window)下载附件,解压出 SQL 文件,然后导入到数据库中。

友情提示:↑↑↑ pay.sql 是可以点击下载的! ↑↑↑

2.2 第二步,启动 pay 服务

① 运行该服务的 PayServerApplication (opens new window)启动类,看到 "Started PayServerApplication in 18.105 seconds" 说明开启成功。

② 然后,访问前端的支付菜单,确认功能是否生效。如下图所示:

至此,我们就成功开启了支付的功能 🙂

#2.3 第三步,开启支付 Job

① 参考 《定时任务》 文档,将 Job 定时任务开启。

② 在 XXL-Job 的 [执行器管理] 菜单,添加 pay-server 执行器。然后,需要重启 PayServerApplication 服务,成功注册到 XXL-Job 上。如下图所示:

③ 在 XXL-Job 的 [任务管理] 菜单,添加 payNotifyJobpayOrderSyncJobpayOrderExpireJobpayRefundSyncJob 任务,并进行开启。如下图所示:


③ 在 XXL-Job 的 [任务管理] 菜单,添加 payNotifyJobpayOrderSyncJobpayOrderExpireJobpayRefundSyncJob 任务,并进行开启。如下图所示:


3. 功能介绍

#3.1 应用信息

对应 [支付管理 -> 应用信息] 菜单,进行支付渠道、支付应用的管理。如下图所示:

3.1.1 支付应用

每个要接入支付中心的业务,对应一个支付应用。例如说:商城订单算一个应用,预约订单算一个应用。

点击【新增】按钮,可以进行支付应用的配置,保存在 pay_app 表。如下图所示:

为什么要有支付应用?直接配置支付渠道不行吗?

  1. 一个系统中,可能有多个业务需要,每个业务的支付、退款回调地址不同。

  2. 同时,每个业务的订单编号可能重复,需要使用支付应用进行隔离,只要求在每个支付应用下保持唯一即可。

  3. 另外,每个业务可能想要配置不同的支付渠道。

3.1.2 支付渠道

每个支付应用下,可以配置多个支付渠道。例如说:这里“示例应用”就配置了支付宝 PC 网站支付、支付宝 Wap 网站支付等等。

点击【√】或者【×】图标,可以进行支付应用的配置,保存在 pay_channel 表。如下图所示

支付费率?

参见 《第三方支付的费率、限额、通道分析》 (opens new window)文档。

#3.2 支付订单

对应 [支付管理 -> 支付订单] 菜单,进行支付订单的查看。如下图所示:

一般情况下,每个业务订单对应一条支付订单,保存在 pay_order 表,通过 merchant_order_id 字段关联。

#3.3 退款订单

对应 [支付管理 -> 退款订单] 菜单,进行退款订单的查看。如下图所示:

一般情况下,每个业务退款对应一条退款订单,保存在 pay_refund 表,通过 merchant_refund_no 字段关联。

3.4 回调通知

对应 [支付管理 -> 回调通知] 菜单,查看支付、退款的回调业务的结果。如下图所示:

3.5 支付回调【重要】

这里,我们要配置支付【中心】提供给支付【渠道】的回调地址,不同于上面支付【应用】的回调地址。整体的回调关系如下图所示:

① 由于支付回调需要外网,可参考 《内网穿透》 文档,将本地的 48080 端口,转发到外网中。这里,我的域名是 http://yunai.natapp1.cc

② 在 application-local.yaml (opens new window)配置文件中,修改 yudao.pay 配置项,设置为支付【中心】的回调地址。如下图所示:

  • yudao.pay.order-notify-url 配置项:对应 PayNotifyController 的 #notifyOrder(...) (opens new window)方法

  • yudao.pay.refund-notify-url 配置项:对应 PayNotifyController 的 #notifyRefund(...) (opens new window)方法

如果你想理解的更深入,可以后续 debug 断条调试。

#3.6 接入示例

对应 [支付管理 -> 接入示例 -> 支付&退款案例] 菜单,提供一个支付、退款的接入示例。如下图所示:

详细说明,可见如下文档:

微服务手册--微服务调试(必读)


1. 多环境 env 组件

在微服务架构下,多服务的调试是个非常大的痛点:在大家使用 同一个 注册中心时,如果多个人在本地启动 相同 服务,可能需要调试的一个请求会打到其他开发的本地服务,实际是期望达到自己本地的服务。

一般的解决方案是,使用 不同 注册中心,避免出现这个情况。但是,服务一多之后,就会产生新的痛点,同时本地启动所有服务很占用电脑内存。

因此,我们实现了 yudao-spring-boot-starter-env (opens new window)组件,通过 Tag 给服务打标,实现在使用 同一个 注册中心的情况下,本地只需要启动需要调试的服务,并且保证自己的请求,必须达到自己本地的服务。如下图所示:

  • 测试环境:启动了 gateway、system、infra 服务;本地环境:只启动了 system 服务

  • 请求时,带上 tag = yunai,优先请求本地环境 + tag = yunai 的服务:

    • ① 由于本地未启动 gateway 服务,所以请求打到测试环境的 gateway 服务

    • ② 由于请求 tag = yunai,所以转发到本地环境的 system 服务

    • ③ 由于本地未启动 infra 服务,所以转发回测试环境的 infra 服务

#2. 功能演示

在本地模拟,启动三个进程,如下图所示:

  • tag 为空的 gateway、system 服务

  • tag 为本机 hostname(例如说我本地是 Yunai.local)的 system 服务

注意!!!

hostname 是你的主机名:

  • Windows 在 cmd 里输入 hostname

  • MacOS 在 terminal 里输入 hostname

#第一步,启动 gateway 服务

直接运行 GatewayServerApplication 类,启动 gateway 服务。此时,我们可以在 Nacos 看到该实例,它是没 tag 属性。如下图所示:

第二步,启动 system 服务【有 tag】

运行 SystemServerApplication 类,启动 system 服务。此时,我们可以在 Nacos 看到该实例,它是有 tag 属性。如下图所示:

因为我们默认在 application-local.yaml 配置文件里,添加了 yudao.env.tag 配置项为 ${HOSTNAME}。如下图所示:


第三步,启动 system 服务 【无 tag】

① 修改 system 服务的端口为 28081,yudao.env.tag 配置项为空。如下图所示:

② 再一个 SystemServerApplication,额外启动 system 服务。此时,我们可以在 Nacos 看到该实例,它是没 tag 属性。如下图所示:

第四步,请求测试

① 打开 AuthController.http 文件,设置第一个请求的 tagYunai.local(要替换成你的 hostname),如下图所示:

② 点击前面的绿色小箭头,发起请求。从 IDEA 控制台的日志可以看到,只有有 tag 的 system 服务才会被调用。

你可以多点几次,依然是这样的情况噢!

#3. 实现原理

① 在服务注册时,会将 yudao.env.tag 配置项,写到 Nacos 服务实例的元数据,通过 EnvEnvironmentPostProcessor (opens new window)类实现。

② 在服务调用时,通过 EnvLoadBalancerClient (opens new window)类,筛选服务实例,通过服务实例的 tag 元数据,匹配请求的 tag 请求头。

③ 在网关转发时,通过 GrayLoadBalancer (opens new window)类,效果和 EnvLoadBalancerClient 一致。

#4. 未来的拓展

除了微服务调试比较麻烦外,MQ 消息队列的消费调试也是个麻烦的事儿,未来也会进行支持。实现的效果如下:

  • 本地发送的 MQ 消息,优先被本地启动的消费者进行处理,方便调试。

  • 如果本地没有启动消费者,则被测试环境的消费者进行处理,避免一致不被消费。

注册中心 Nacos

项目使用 Nacos 作为配置中心,实现服务的注册发现。


1. 搭建 Nacos Server

① 参考《芋道 Nacos 极简入门》 (opens new window)文章的「2. 单机部署(最简模式)」或「3. 单机部署(基于 MySQL 数据库)」小节。

② 点击 Nacos 控制台的 [命名空间] 菜单,创建一个 ID 和名字都为 dev 的命名空间,稍后会使用到。如下图所示:

2. 项目接入 Nacos

友情提示:以 yudao-module-system 服务为例子。

#2.1 引入依赖

yudao-module-system-biz 模块的 pom.xml (opens new window)中,引入 Nacos 对应的依赖。如下所示:

<!-- Registry 注册中心相关 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

#2.2 添加配置

application-local.yaml (opens new window)中,添加 nacos.config 配置。如下所示:

--- #################### 注册中心相关配置 ####################

spring:
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848 # Nacos 服务器地址
      username: # Nacos 账号
      password: # Nacos 密码
      discovery: # 【配置中心】配置项
        namespace: dev # 命名空间。这里使用 dev 开发环境
        group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
        metadata:
          version: 1.0.0 # 服务实例的版本号,可用于灰度发布
  • spring.cloud.nacos.discovery.namespace 配置项:设置为 dev,就是刚创建的命名空间

#2.3 启动项目

运行 SystemServerApplication 类,将 system-server 服务启动。

然后,在 Nacos 控制台的 [服务管理 -> 服务列表] 菜单,就可以看到该服务实例。如下图所示:

配置中心 Nacos

1. 配置中心 Nacos

项目使用 Nacos 作为配置中心,实现配置的动态管理。

#1.1 搭建 Nacos Server

① 参考《芋道 Nacos 极简入门》 (opens new window)文章的「2. 单机部署(最简模式)」或「3. 单机部署(基于 MySQL 数据库)」小节。

② 点击 Nacos 控制台的 [命名空间] 菜单,创建一个 ID 和名字都为 dev 的命名空间,稍后会使用到。如下图所示:

注意!新建命名空间时,它的“命名空间ID”、“命名空间名”都要是 dev 噢!!!

#1.2 项目接入 Nacos

友情提示:以 yudao-module-system 服务为例子。

#1.2.1 引入依赖

yudao-module-system-biz 模块的 pom.xml (opens new window)中,引入 Nacos 对应的依赖。如下所示:

<!-- Config 配置中心相关 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

#1.2.2 添加配置

① 在 application-local.yaml (opens new window)中,添加 nacos.config 配置。如下所示:

--- #################### 配置中心相关配置 ####################

spring:
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848 # Nacos 服务器地址
      username: # Nacos 账号
      password: # Nacos 密码
      config: # 【注册中心】配置项
        namespace: dev # 命名空间。这里使用 dev 开发环境
        group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
  • spring.cloud.nacos.config.namespace 配置项:设置为 dev,就是刚创建的命名空间

② 在 application.yaml (opens new window)中,添加 spring.cloud.config.import 配置项。

spring:
  cloud:
    config:
      - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置
      - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置
  • 其中 optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml 表示,从 Nacos 加载 dataIdsystem-server-local.yaml 的配置项。

#1.2.3 配置管理

友情提示:

如果你不会使用 Nacos 配置中心,可以参考《芋道 Spring Cloud Alibaba 配置中心 Nacos 入门 》 (opens new window)文档,学习 Nacos 配置中心的使用。

按照需要,将不同环境存在差异的 application-local.yaml (opens new window)和 application-dev.yaml (opens new window)中的配置,迁移到 Nacos 配置中心。以 application-local.yaml 为例子。

① 将 application-local.yaml 中,除了如下 Nacos 配置外的配置,迁移到 Nacos 配置中心中。如下图所示:

--- #################### 注册中心 + 配置中心相关配置 ####################

spring:
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848 # Nacos 服务器地址
      username: # Nacos 账号
      password: # Nacos 密码
      discovery: # 【配置中心】配置项
        namespace: dev # 命名空间。这里使用 dev 开发环境
        group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
        metadata:
          version: 1.0.0 # 服务实例的版本号,可用于灰度发布
      config: # 【注册中心】配置项
        namespace: dev # 命名空间。这里使用 dev 开发环境
        group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
  • = = 如果把这部分 Nacos 配置也放进去,都不知道 Nacos 地址信息了!!!

  • Data ID${spring.application.name}-${spring.profiles.active}.yaml,例如说 system-server-local.yaml

  • 配置格式:YAML 格式

  • 配置内容:将 application-local.yaml 中的配置,迁移到 Nacos 配置中心中。

提示

① 疑问:为什么项目中的 application-{env}.yaml 中的配置,没有放到 Nacos 配置中心中?

回答:主要考虑大家 《快速启动》 可以更简单。

实际项目中,是建议放到 Nacos 配置中心,进行配置的动态管理的。


② 疑问:是否建议将 application.yaml 中的配置,迁移到 Nacos 配置中心中?

回答:一般情况下,不建议将 application.yaml 中的配置,迁移到 Nacos 配置中心。因为 application.yaml 中的配置,是通用的配置,无需动态管理。

所以目前在大厂里的最佳实践:

  • 固定配置,放在 application.yaml 配置文件

  • 动态配置,按需放在 Nacos 等配置中心

  • 敏感配置,放在 KMS 密钥服务,类似阿里云的 密钥管理服务(opens new window)

#2. 配置管理

友情提示:该功能是从 Boot 项目延用到 Cloud 项目,一般情况下不会使用到,使用 Nacos 管理配置即可。

在 [基础设施 -> 配置管理] 菜单,可以查看和管理配置,适合业务上需要动态的管理某个配置。

例如说:创建用户时,需要配置用户的默认密码,这个密码是不会变的,但是有时候需要修改这个默认密码,这个时候就可以通过配置管理来修改。

对应的后端代码是 yudao-module-infraconfig (opens new window)业务模块。

#2.1 配置的表结构

infra_config 的表结构如下:

CREATE TABLE `infra_config` (
    `id` int NOT NULL AUTO_INCREMENT COMMENT '参数主键',
    `group` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '参数分组',
    `type` tinyint NOT NULL COMMENT '参数类型',
    `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '参数名称',
    `key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '参数键名',
    `value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '参数键值',
    `sensitive` bit(1) NOT NULL COMMENT '是否敏感',
    `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
    `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='参数配置表';
  • key 字段,对应到 Spring Boot 配置文件的配置项,例如说 yudao.captcha.enablesys.user.init-password 等等。

#2.2 后端案例

调用 ConfigApi 的 #getConfigValueByKey(String key) 方法,可以读取指定 key 的参数值。

具体案例,可见 AdminUserServiceImpl 的 #importUserList(...) 方法,在导入 Excel 用户时,它会读取 system.user.init-password 作为用户初始化密码。

#2.3 前端案例

后端提供了 /admin-api/infra/config/get-value-by-key (opens new window)RESTful API 接口,返回指定配置项的值。前端的使用示例如下图:

服务网关 Spring Cloud Gateway

yudao-gateway (opens new window)模块,基于 Spring Cloud Gateway 构建 API 服务网关,提供用户认证、服务路由、灰度发布、访问日志、异常处理等功能。

友情提示:如何学习 Spring Cloud Gateway?

阅读 《芋道 Spring Cloud 网关 Spring Cloud Gateway 入门 》 (opens new window)文章。

#1. 服务路由

新建服务后,在 application.yaml (opens new window)配置文件中,需要添加该服务的路由配置。示例如下图:

2. 用户认证

filter/security (opens new window)包实现,无需配置。

TokenAuthenticationFilter 会获得请求头中的 Authorization 字段,调用 system-server 服务,进行用户认证。

  • 如果认证成功,会将用户信息放到 login-user 请求头,转发到后续服务。后续服务可以从 login-user 请求头,解析 (opens new window)到用户信息。

  • 如果认证失败,依然会转发到后续服务,由该服务决定是否需要登录,是否需要校验权限。

考虑到性能,API 网关会本地缓存 (opens new window)Token 与用户信息,每次收到 HTTP 请求时,异步从 system-server 刷新本地缓存。

#3. 灰度发布

filter/grey (opens new window)包实现,实现原理如下:

所以在使用灰度时,如要如下配置:

① 第一步,【网关】配置服务的路由配置使用 grebLb:// 协议,指向灰度服务。例如说:② 第二步,【服务】配置服务的版本 version 配置。例如说:


③ 第三步,请求 API 网关时,请求头带上想要 version 版本。

可能想让用户的请求带上 version 请求头比较难,可以通过 Spring Cloud Gateway 修改请求头,通过 User Agent、Cookie、登录用户等信息,来判断用户想要的版本。详细的解析,可见 《Spring Cloud Gateway 实现灰度发布功能 》 (opens new window)文章。

#4. 访问日志

filter/logging (opens new window)包实现,无需配置。

每次收到 HTTP 请求时,会打印访问日志,包括 Request、Response、用户等信息。如下图所示:

5. 异常处理

GlobalExceptionHandler (opens new window)累实现,无需配置。

请求发生异常时,会翻译异常信息,返回给用户。例如说:

{
  "code": 500,
  "data": null,
  "msg": "系统异常"
}

#6. 动态路由

在 Nacos 配置发生变化时,Spring Cloud Alibaba Nacos Config 内置的监听器,会监听到配置刷新,最终触发 Gateway 的路由信息刷新。

参见 《芋道 Spring Cloud 网关 Spring Cloud Gateway 入门 》 (opens new window)博客的「6. 基于配置中心 Nacos 实现动态路由」小节。

使用方式:在 Nacos 新增 DataId 为 gateway-server.yaml 的配置,修改 spring.cloud.gateway.routes 配置项。

#7. Swagger 接口文档

基于 Knife4j 实现 Swagger 接口文档的 网关聚合 (opens new window)。需要路由配置如下:

友情提示:图中的 /v2/ 都改成 /v3/,或者以下面的文字为准!!!

  • 管理后台的接口:- RewritePath=/admin-api/{服务的基础路由}/v3/api-docs, /v3/api-docs

  • 用户 App 的接口:- RewritePath=/app-api/{服务的基础路由}/v3/api-docs, /v3/api-docs

  • Knife4j 配置:knife4j.gateway.routes 添加

浏览器访问 http://127.0.0.1:48080/doc.html (opens new window)地址,可以看到所有接口的信息。如下图所示:

7.1 如何调用

〇 点击左边「文档管理 - 全局参数设置」菜单,设置 header-idAuthorization 请求头。如下图所示:

tenant-id:1
Authorization: Bearer test1

添加完后,需要 F5 刷新下网页,否则全局参数不生效。

① 点击任意一个接口,进行接口的调用测试。这里,使用「管理后台 - 用户个中心」的“获得登录用户信息”举例子。

② 点击左侧「调试」按钮,并将请求头部的 header-idAuthorization 勾选上。

其中,header-id 为租户编号,Authorization"Bearer test" 后面为用户编号(模拟哪个用户操作)。

③ 点击「发送」按钮,即可发起一次 API 的调用。

7.2 如何关闭

如果想要禁用 Swagger 功能,可通过 knife4j.gateway.enabled 配置项为 false。一般情况下,建议 prod 生产环境进行禁用,避免发生安全问题。

8. Cors 跨域处理

filter/cors (opens new window)包实现,无需配置。

服务调用 Feign

yudao-spring-boot-starter-rpc (opens new window)技术组件,基于 Feign 实现服务之间的调用。

为什么不使用 Dubbo 呢?

Feign 通用性更强,学习成本更低,对于绝大多数场景,都能够很好的满足需求。虽然 Dubbo 提供的性能更强,特性更全,但都是非必须的。

目前国内 95% 左右都是采用 Feign,而 Dubbo 的使用率只有 5% 左右。所以,我们也选择了 Feign。

如果你对 Feign 了解较少,可以阅读 《芋道 Spring Cloud 声明式调用 Feign 入门》 (opens new window)系统学习。

#1. RPC 使用规约

本小节,我们来讲解下项目中 RPC 使用的规约。

#1.1 API 前缀

API 使用 HTTP 协议,所有的 API 前缀,都以 /rpc-api (opens new window)开头,方便做统一的全局处理。

#1.2 API 权限

服务之间的调用,不需要进行权限校验,所以需要在每个服务的 SecurityConfiguration 权限配置类中,添加如下配置:

// RPC 服务的安全配置
registry.antMatchers(ApiConstants.PREFIX + "/**").permitAll();

.3 API 全局返回

所有 API 接口返回使用 CommonResult (opens new window)返回,和前端 RESTful API 保持统一。例如说:

public interface DeptApi {

    @GetMapping(PREFIX + "/get")
    @Operation(summary = "获得部门信息")
    @Parameter(name = "id", description = "部门编号", required = true, example = "1024")
    CommonResult<DeptRespDTO> getDept(@RequestParam("id") Long id);
    
}

#1.4 用户传递

服务调用时,已经封装 Feign 将用户信息通过 HTTP 请求头 login-user 传递,通过 LoginUserRequestInterceptor (opens new window)类实现。

这样,被调用服务,可以通过 SecurityFrameworkUtils 获取到用户信息,例如说:

  • #getLoginUser() 方法,获取当前用户。

  • #getLoginUserId() 方法,获取当前用户编号。

#2. 如何定义一个 API 接口

本小节,我们来讲解下如何定义一个 API 接口。以 AdminUserApi 提供的 getUser 接口来举例子。

#2.1 服务提供者

AdminUserApi 由 system-server 服务所提供。

#2.1.1 ApiConstants

yudao-module-system-api 模块,创建 ApiConstants (opens new window)类,定义 API 相关的枚举。代码如下:

public class ApiConstants {

    /**
     * 服务名
     *
     * 注意,需要保证和 spring.application.name 保持一致
     */
    public static final String NAME = "system-server";

    public static final String PREFIX = RpcConstants.RPC_API_PREFIX +  "/system";

    public static final String VERSION = "1.0.0";

}

#2.1.2 AdminUserApi

yudao-module-system-api 模块,创建 AdminUserApi (opens new window)类,定义 API 接口。代码如下:

@FeignClient(name = ApiConstants.NAME) // ① @FeignClient 注解
@Tag(name = "RPC 服务 - 管理员用户") // ② Swagger 接口文档
public interface AdminUserApi {

    String PREFIX = ApiConstants.PREFIX + "/user";

    @GetMapping(PREFIX + "/get") // ③ Spring MVC 接口注解
    @Operation(summary = "通过用户 ID 查询用户")  // ② Swagger 接口文档
    @Parameter(name = "id", description = "部门编号", required = true, example = "1024") // ② Swagger 接口文档
    CommonResult<AdminUserRespDTO> getUser(@RequestParam("id") Long id);
    
}

另外,需要创建 AdminUserRespDTO (opens new window)类,定义用户 Response DTO。代码如下:

@Data
public class AdminUserRespDTO {

    /**
     * 用户ID
     */
    private Long id;
    /**
     * 用户昵称
     */
    private String nickname;
    /**
     * 帐号状态
     *
     * 枚举 {@link CommonStatusEnum}
     */
    private Integer status;

    /**
     * 部门ID
     */
    private Long deptId;
    /**
     * 岗位编号数组
     */
    private Set<Long> postIds;
    /**
     * 手机号码
     */
    private String mobile;

}

#2.1.3 AdminUserRpcImpl

yudao-module-system-biz 模块,创建 AdminUserRpcImpl (opens new window)类,实现 API 接口。代码如下:

@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
public class AdminUserApiImpl implements AdminUserApi {

    @Resource
    private AdminUserService userService;

    @Override
    public CommonResult<AdminUserRespDTO> getUser(Long id) {
        AdminUserDO user = userService.getUser(id);
        return success(UserConvert.INSTANCE.convert4(user));
    }
    
}

#2.2 服务消费者

bpm-server 服务,调用了 AdminUserApi 接口。

#2.2.1 引入依赖

yudao-module-bpm-biz 模块的 pom.xml (opens new window),引入 yudao-module-system-api 模块的依赖。代码如下:

<dependency>
    <groupId>cn.iocoder.cloud</groupId>
    <artifactId>yudao-module-system-api</artifactId>
    <version>${revision}</version>
</dependency>

#2.2.2 引用 API

yudao-module-bpm-biz 模块,创建 RpcConfiguration (opens new window)配置类,注入 AdminUserApi 接口。代码如下:

@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = {AdminUserApi.class.class})
public class RpcConfiguration {
}

#2.2.3 调用 API

例如说,BpmTaskServiceImpl (opens new window)调用了 AdminUserApi 接口,代码如下:

@Service
public class BpmTaskServiceImpl implements BpmTaskService {

    @Resource
    private AdminUserApi adminUserApi;

    @Override
    public void updateTaskExtAssign(Task task) {
        // ... 省略非关键代码
        AdminUserRespDTO startUser = adminUserApi.getUser(id).getCheckedData();
    }
}


定时任务 XXL Job

定时任务的使用场景主要如下:

  • 时间驱动处理场景:每分钟扫描超时支付的订单,活动状态刷新,整点发送优惠券。

  • 批量处理数据:按月批量统计报表数据,批量更新短信状态,实时性要求不高。

年度最佳定时任务:每个月初的工资单的推送!!!

项目基于 XXL Job 实现分布式定时任务,支持动态控制任务的添加、修改、开启、暂停、删除、执行一次等操作。

疑问:为什么使用 XXL-Job 呢?

目前国内开源的 Job 框架,经历过大规模的中大厂的考验,稳定性和功能性都是有保障的,目前可能只有 XXL-Job 和 Elastic-Job 两个选择。

相对来说,XXL-Job 更加轻量级,大家更容易上手。

#1. 如何搭建 XXL Job 调度中心

① 参见 《芋道 XXL-Job 极简入门》 (opens new window)文档的「4. 搭建调度中心 」部分。

② 搭建完成后,需要修改管理后台的 [基础设施 -> 定时任务] 菜单,指向你的 XXL-Job 地址。如下图所示:

2. 如何编写 XXL Job 定时任务

友情提示:以 yudao-module-system 服务为例子。

#2.1 引入依赖

yudao-module-system-biz 模块的 pom.xml (opens new window)中,引入 yudao-spring-boot-starter-job 技术组件。如下所示:

<dependency>
    <groupId>cn.iocoder.cloud</groupId>
    <artifactId>yudao-spring-boot-starter-job</artifactId>
</dependency>

该组件基于 XXL Job 框架的封装,实现它的 Spring Boot Starter 配置。

#2.2 添加配置

① 在 application.yaml (opens new window)中,添加 xxl.job 配置。如下所示:

--- #################### 定时任务相关配置 ####################

xxl:
  job:
    executor:
      appname: ${spring.application.name} # 执行器 AppName
      logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
    accessToken: default_token # 执行器通讯TOKEN
  • 注意,xxl.job.accessToken 配置,需要改成你的 XXL Job 调度中心的访问令牌。

② 在 application-local.yaml (opens new window)中,添加 xxl.job 配置。如下所示:

--- #################### 定时任务相关配置 ####################

xxl:
  job:
    enabled: true # 是否开启调度中心,默认为 true 开启
    admin:
      addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址

③ 在 XXL-Job 的 [执行器管理] 菜单中,添加该服务作为 XXL-Job 的执行器。如下图所示:

  • AppName:使用服务的 spring.application.name 即可,这个是我们项目所封装和约定的,例如说该服务是 system-server

  • 名称:没有强制要求,此处我们填写“系统服务”

  • 注册方式:使用“自动注册”即可,稍后我们启动该服务后,会看到注册上来

④ 启动该项目的 SystemServerApplication 类,看到如下日志,说明注册成功:

2024-07-27 13:21:52.724 |  INFO 2109 | main [TID: N/A] c.i.y.f.q.c.YudaoXxlJobAutoConfiguration | [xxlJobExecutor][初始化 XXL-Job 执行器的配置]

2024-07-27 13:21:53.432 |  INFO 2109 | main [TID: N/A] c.xxl.job.core.executor.XxlJobExecutor   | >>>>>>>>>>> xxl-job register jobhandler success, name:demoJob, jobHandler:com.xxl.job.core.handler.impl.MethodJobHandler@7827b580[class cn.iocoder.yudao.module.system.job.demo.DemoJob$$SpringCGLIB$$0#execute]

2024-07-27 13:21:53.496 |  INFO 2109 | Thread-13 [TID: N/A] com.xxl.job.core.server.EmbedServer      | >>>>>>>>>>> xxl-job remoting server start success, nettype = class com.xxl.job.core.server.EmbedServer, port = 10000
  • 其中,第 2 行的 demoJob 是我们在 yudao-module-system 提供的一个示例 Job。

另外,我们也可以在 XXL-Job 的 [任务管理] 菜单中,看到注册成功的信息。如下图所示:

2.3 创建 Job 定时任务

友情提示:继续以 yudao-module-system 服务为例子。

yudao-module-system-biz 模块的 job 包下,我们已经提供了一个 DemoJob 示例,代码如下:

@Component
public class DemoJob {

    @XxlJob("demoJob")
    @TenantJob
    public void execute() {
        System.out.println("美滋滋");
    }

}

疑问:为什么 Job 查询数据库时,报多租户的错误?

需要声明 @TenantJob (opens new window)注解在 Job 类上,实现并行遍历每个租户,执行定时任务的逻辑。

更多多租户的内容,可见 《开发指南 —— SaaS 多租户》 文档。

① 在 XXL-Job 的 [任务管理] 菜单中,点击 [新增] 按钮,填写定时任务的信息。如下图所示:

② 点击该任务后的 [操作] 按钮,选择 [执行一次] 按钮,执行一次该任务。然后,我们可以在 IDEA 看到 美滋滋 的输出。

如此,我们便完成了一个简单的定时任务的编写。撒花~~~

#4. 更多使用教程

可参见 《芋道 Spring Boot 定时任务入门》 (opens new window)文章的「5. 快速入门 XXL-JOB」部分。

常用的 Cron 表达式如下:

0 0 10,14,16 * * ? 每天上午 10 点,下午 2 点、4 点 
0 0/30 9-17 * * ? 朝九晚五工作时间内,每半小时 
0 0 12 ? * WED 表示每个星期三中午 12 点 
0 0 12 * * ? 每天中午 12 点触发 
0 15 10 ? * * 每天上午 10:15 触发 
0 15 10 * * ? 每天上午 10:15 触发 
0 15 10 * * ? * 每天上午 10:15 触发 
0 15 10 * * ? 2005 2005 年的每天上午 10:15 触发 
0 * 14 * * ? 在每天下午 2 点到下午 2:59 期间,每 1 分钟触发 
0 0/5 14 * * ? 在每天下午 2 点到下午 2:55 期间,每 5 分钟触发 
0 0/5 14,18 * * ? 在每天下午 2 点到 2:55 期间和下午 6 点到 6:55 期间,每 5 分钟触发 
0 0-5 14 * * ? 在每天下午 2 点到下午 2:05 期间,每 1 分钟触发 
0 10,44 14 ? 3 WED 每年三月的星期三的下午 2:10 和 2:44 触发 
0 15 10 ? * MON-FRI 周一至周五的上午 10:15 触发 
0 15 10 15 * ? 每月15日上午 10:15 触发 
0 15 10 L * ? 每月最后一日的上午 10:15 触发 
0 15 10 ? * 6L 每月的最后一个星期五上午 10:15 触发 
0 15 10 ? * 6L 2002-2005 2002 年至 2005 年,每月的最后一个星期五上午 10:15 触发 
0 15 10 ? * 6#3 每月的第三个星期五上午 10:15 触发

消息队列(内存)

#1. Spring Event

yudao-spring-boot-starter-mq (opens new window)技术组件,提供了 Redis、RocketMQ、RabbitMQ、Kafka 分布式消息队列的封装。

考虑到部分同学的项目对消息队列的要求不高,又不想引入额外部署的消息队列,所以默认使用 Spring Event 实现【内存】级别的消息队列。

疑问:为什么默认不使用 Redis 作为消息队列?

这确实是一种选择,但是想要使用 Redis 实现可靠的消息队列,必须使用 Redis 5.0 版本的 Stream 特性。

这样一方面对 Redis 要求的版本比较高,另一方面大多数同学对 Redis Stream 基本不了解,生产经验不足。

如果你对 Spring Event 不太了解,可以看看 《芋道 Spring Boot 事件机制 Event 入门》 (opens new window)文档。

#2. 使用示例

友情提示:下文操作的都是 yudao-module-system 服务

以【短信发送】举例子,我们来看看 Spring Event 的使用。如下图所示:

2.1 Message 消息

message 包下,新建 SmsSendMessage 类,短信发送消息。代码如下:

@Data
public class SmsSendMessage {

    /**
     * 短信日志编号
     */
    @NotNull(message = "短信日志编号不能为空")
    private Long logId;
    /**
     * 手机号
     */
    @NotNull(message = "手机号不能为空")
    private String mobile;
    /**
     * 短信渠道编号
     */
    @NotNull(message = "短信渠道编号不能为空")
    private Long channelId;
    /**
     * 短信 API 的模板编号
     */
    @NotNull(message = "短信 API 的模板编号不能为空")
    private String apiTemplateId;
    /**
     * 短信模板参数
     */
    private List<KeyValue<String, Object>> templateParams;

}

#2.2 SmsProducer 生产者

producer 包下,新建 SmsProducer 类,Sms 短信相关消息的生产者。代码如下:

@Slf4j
@Component
public class SmsProducer {

    @Resource
    private ApplicationContext applicationContext;

    /**
     * 发送 {@link SmsSendMessage} 消息
     *
     * @param logId 短信日志编号
     * @param mobile 手机号
     * @param channelId 渠道编号
     * @param apiTemplateId 短信模板编号
     * @param templateParams 短信模板参数
     */
    public void sendSmsSendMessage(Long logId, String mobile,
                                   Long channelId, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
        SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile);
        message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams);
        applicationContext.publishEvent(message);
    }

}

#2.3 SmsSendConsumer 消费者

consumer 包下,新建 SmsSendConsumer 类,SmsSendMessage 的消费者。代码如下:

@Component
@Slf4j
public class SmsSendConsumer {

    @Resource
    private SmsSendService smsSendService;

    @EventListener
    @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步
    public void onMessage(SmsSendMessage message) {
        log.info("[onMessage][消息内容({})]", message);
        smsSendService.doSendSms(message);
    }

}

#2.4 简单测试

〇 Run 启动 Gateway 网关服务,因为需要它来调用服务。

① Debug 启动 yudao-module-system 服务,可以在 SmsProducer 和 SmsSendConsumer 上面打上断点,稍微调试下。

② 打开 SmsTemplateController.http 文件,使用 IDEA httpclient 发起请求,发送短信。如下图所示:

消息队列(Redis)

yudao-spring-boot-starter-mq (opens new window)技术组件,基于 Redis 实现分布式消息队列:

疑问:什么是【广播】消费?什么是【集群】消费?

参见《阿里云 —— 集群消费和广播消费 》 (opens new window)文档

#1. 集群消费

集群消费,是指消息发送到 Redis 时,有且只会被一个消费者(应用 JVM 实例)收到,然后消费成功。如下图所示:

友情提示:

如果你需要使用到【集群】消费,必须使用 Redis 5.0.0 以上版本,因为 Stream 特性是在该版本之后才引入噢!

#1.1 使用场景

集群消费在项目中的使用场景,主要是提供可靠的、可堆积的异步任务的能力。例如说:

相比 《开发指南 —— 异步任务》 来说,Spring Async 在 JVM 实例重启时,会导致未执行完的任务丢失。而集群消费,因为消息是存储在 Redis 中,所以不会存在该问题。

#1.2 实现源码

集群消费基于 Redis Stream 实现:

  • 实现 AbstractRedisStreamMessage 抽象类,定义【集群】消息。

  • 使用 RedisMQTemplate 的 #send(message) 方法,发送消息。

  • 实现 AbstractRedisStreamMessageListener 接口,消费消息。

最终使用 YudaoRedisMQAutoConfiguration 配置类,扫描所有的 AbstractRedisStreamMessageListener 监听器,初始化对应的消费者。如下图所示:

1.3 实战案例

友情提示:下文操作的都是 yudao-module-system 服务

以【短信发送】举例子,改造使用 Redis 作为消息队列,同时也是讲解集群消费的使用。如下图所示:

1.3.0 引入依赖

yudao-module-system-biz 模块中,引入 yudao-spring-boot-starter-mq 技术组件。如下所示:

<dependency>
    <groupId>cn.iocoder.cloud</groupId>
    <artifactId>yudao-spring-boot-starter-mq</artifactId>
</dependency>

1.3.1 Message 消息

message 包下,修改 SmsSendMessage 类,短信发送消息。代码如下:

@Data
public class SmsSendMessage extends AbstractRedisStreamMessage { // 重点:需要继承 AbstractRedisStreamMessage 类

    /**
     * 短信日志编号
     */
    @NotNull(message = "短信日志编号不能为空")
    private Long logId;
    /**
     * 手机号
     */
    @NotNull(message = "手机号不能为空")
    private String mobile;
    /**
     * 短信渠道编号
     */
    @NotNull(message = "短信渠道编号不能为空")
    private Long channelId;
    /**
     * 短信 API 的模板编号
     */
    @NotNull(message = "短信 API 的模板编号不能为空")
    private String apiTemplateId;
    /**
     * 短信模板参数
     */
    private List<KeyValue<String, Object>> templateParams;

}

#1.3.2 SmsProducer 生产者

producer 包下,修改 SmsProducer 类,Sms 短信相关消息的生产者。代码如下:

@Slf4j
@Component
public class SmsProducer {

    @Resource
    private RedisMQTemplate redisMQTemplate; // 重点:注入 RedisMQTemplate 对象

    /**
     * 发送 {@link SmsSendMessage} 消息
     *
     * @param logId 短信日志编号
     * @param mobile 手机号
     * @param channelId 渠道编号
     * @param apiTemplateId 短信模板编号
     * @param templateParams 短信模板参数
     */
    public void sendSmsSendMessage(Long logId, String mobile,
                                   Long channelId, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
        SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile);
        message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams);
        redisMQTemplate.send(message); // 重点:使用 RedisMQTemplate 发送消息
    }

}

#1.3.3 SmsSendConsumer 消费者

consumer 包下,修改 SmsSendConsumer 类,SmsSendMessage 的消费者。代码如下:

@Component
@Slf4j
public class SmsSendConsumer extends AbstractRedisStreamMessageListener<SmsSendMessage> { // 重点:继承 AbstractRedisStreamMessageListener 类,并填写对应的 Message 类

    @Resource
    private SmsSendService smsSendService;

    @Override // 重点:实现 onMessage 方法
    public void onMessage(SmsSendMessage message) {
        log.info("[onMessage][消息内容({})]", message);
        smsSendService.doSendSms(message);
    }

}

#1.3.4 简单测试

〇 Run 启动 Gateway 网关服务,因为需要它来调用服务。

① Debug 启动 yudao-module-system 服务,可以在 SmsProducer 和 SmsSendConsumer 上面打上断点,稍微调试下。

② 打开 SmsTemplateController.http 文件,使用 IDEA httpclient 发起请求,发送短信。如下图所示:

如果 IDEA 控制台看到 [onMessage][消息内容 日志内容,说明消息的发送和消费成功。

#2. 广播消费

广播消费,是指消息发送到 Redis 时,所有消费者(应用 JVM 实例)收到,然后消费成功。如下图所示:

2.1 使用场景

例如说,在应用中,缓存了数据字典等配置表在内存中,可以通过 Redis 广播消费,实现每个应用节点都消费消息,刷新本地内存的缓存。

又例如说,我们基于 WebSocket 实现了 IM 聊天,在我们给用户主动发送消息时,因为我们不知道用户连接的是哪个提供 WebSocket 的应用,所以可以通过 Redis 广播消费。每个应用判断当前用户是否是和自己提供的 WebSocket 服务连接,如果是,则推送消息给用户。

#2.2 实现源码

广播消费基于 Redis Pub/Sub 实现:

  • 实现 AbstractChannelMessage 抽象类,定义【广播】消息。

  • 使用 RedisMQTemplate 的 #send(message) 方法,发送消息。

  • 实现 AbstractRedisChannelMessageListener 接口,消费消息。

最终使用 YudaoRedisMQAutoConfiguration 配置类,扫描所有的 AbstractRedisChannelMessageListener 监听器,初始化对应的消费者。如下图所示:

2.3 实战案例

参见 《开发指南 —— 本地缓存》