快速启动(前端项目)
目标:将项目 yudao-cloud (opens new window)的前端项目运行起来 🛫
整个过程非常简单,预计 5 分钟就可以完成,取决于大家的网速。
↓↓↓ 技术交流群,一起苦练技术基本功,每日精进 30 公里!↓↓↓
1. 管理后台
项目的管理后台有 4 个版本:
yudao-ui-admin-vue3
(opens new window):基于 Vue3 + element-plusyudao-ui-admin-vben
(opens new window):基于 Vue3 + vben(ant-design-vue)yudao-ui-admin-vue2
(opens new window):基于 Vue2 + element-uiyudao-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 分钟左右,请保持耐心。
点击菜单,感觉会有一点卡顿,因为 Vite 采用懒加载机制。不用担心,最终部署到生产环境,就不存在这个问题了。
详细说明,可见 《为什么有人说 Vite 快,有人却说 Vite 慢?》 (opens new window)文章。
疑问:我是前端工程师,不想启动后端项目,怎么办?
可以将上述的 npm run dev
命令,替代成 npm run dev-server
命令。
远程 演示环境的后端服务,只允许 GET
请求,不允许 POST
、PUT
、DELETE
等请求。
#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
请求,不允许 POST
、PUT
、DELETE
等请求。
#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
请求,不允许 POST
、PUT
、DELETE
等请求。
#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 服务。
不会安装的胖友,可以选择阅读下文,良心的艿艿。
Windows 安装 Redis 指南:http://www.iocoder.cn/Redis/windows-install(opens new window)
Mac 安装 Redis 指南:http://www.iocoder.cn/Redis/mac-install(opens new window)
默认配置下,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
为模块名。之后,新建 api
和 enums
包。如下图所示:
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.admin
和 controller.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
长整形,并设置自增长。
③ 正确设置每个字段是否允许空,代码生成器会根据它生成参数是否允许空的校验规则。
④ 正确设置注释,代码生成器会根据它生成字段名与提示等信息。
⑤ 添加 creator
、create_time
、updater
、update_time
、deleted
是必须设置的系统字段;如果开启多租户的功能,并且该表需要多租户的隔离,则需要添加 tenant_id
字段。
#2. 代码生成
#2.1 导入表
点击 [基础设施 -> 代码生成] 菜单,点击 [基于 DB 导入] 按钮,选择 system_group
表,后点击 [确认] 按钮。
代码实现?
可见 CodegenBuilder (opens new window)类,自动解析数据库的表结构,生成默认的配置。
#2.2 编辑配置
点击 system_group
所在行的 [编辑] 按钮,修改生成配置。后操作如下:
将
status
字段的显示类型为【下拉框】,字典类型为【系统状态】。将
description
字段的【查询】取消。将
id
、name
、description
、status
字段的【示例】填写上。
字段信息
插入:新增时,是否传递该字段。
编辑:修改时,是否传递该字段。
列表:Table 表格,是否展示该字段。
查询:搜索框,是否支持该字段查询,查询的条件是什么。
允许空:新增或修改时,是否必须传递该字段,用于 Validator 参数校验。
字典类型:在显示类型是下拉框、单选框、复选框时,选择使用的字典。
示例:参数示例,用于 Swagger 接口文档的
example
示例。
将【上级菜单】设置为【系统管理】。
将【前端类型】设置为“前端项目”对应的“前端类型”。例如说,我们这里演示的是
yudao-ui-admin-vue3
前端项目,则选择了【Vue3 Element Plus 标准模版】。
生成信息
生成场景:分成管理后台、用户 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 合并成一个呢?有两方面的考虑:
一般来说,新增/修改是不传递
createTime
、creator
等字段,响应是需要返回createTime
、creator
等字段,两者的字段无法不一致。一旦 VO 和 DO 拆分开后,调整字段时,例如说新增一个 xxx 字段,两个 VO 的修改成本,和一个 VO 实际是差不多的。
③ 为什么 UI 界面的数据字典,下拉没有选项,或者列表没有展示它的文本?
类似 https://t.zsxq.com/owJzU (opens new window)的问题。
原因是:需要在前端的代码里,枚举一下。例如说 Vue3 + Element-Plus 版本,需要在 yudao-ui-admin-vue3/src/utils/dict.ts
的 DICT_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_student
、system_student_course
、system_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)后端模块:实现支付中心的后端功能,包括支付、退款等能力。
基于 PayClient 支付客户端,对接微信、支付宝等支付渠道。
对内提供 PayOrderApi (opens new window)统一支付 API 能力、PayRefundApi (opens new window)统一退款 API 能力。
③ 支付中心的前端,提供支付中心的管理后台,可进行支付渠道的配置、支付订单、退款单的查看等操作。
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 的 [任务管理] 菜单,添加 payNotifyJob
、payOrderSyncJob
、payOrderExpireJob
、payRefundSyncJob
任务,并进行开启。如下图所示:
③ 在 XXL-Job 的 [任务管理] 菜单,添加 payNotifyJob
、payOrderSyncJob
、payOrderExpireJob
、payRefundSyncJob
任务,并进行开启。如下图所示:
3. 功能介绍
#3.1 应用信息
对应 [支付管理 -> 应用信息] 菜单,进行支付渠道、支付应用的管理。如下图所示:
3.1.1 支付应用
每个要接入支付中心的业务,对应一个支付应用。例如说:商城订单算一个应用,预约订单算一个应用。
点击【新增】按钮,可以进行支付应用的配置,保存在 pay_app
表。如下图所示:
应用标识:每个接入支付中心的应用(业务)标识,例如说现有项目中的商城订单使用
mall
、会员钱包使用wallet
支付结果的回调地址:每个业务需要实现一个支付回调接口,在用户支付成功时,支付中心会进行回调。示例订单的支付回调 (opens new window)、商城订单的支付回调(opens new window)
退款结果的回调地址:每个业务需要实现一个退款回调接口,在用户退款成功时,支付中心会进行回调。示例订单的退款回调 (opens new window)、商城订单的退款回调
为什么要有支付应用?直接配置支付渠道不行吗?
一个系统中,可能有多个业务需要,每个业务的支付、退款回调地址不同。
同时,每个业务的订单编号可能重复,需要使用支付应用进行隔离,只要求在每个支付应用下保持唯一即可。
另外,每个业务可能想要配置不同的支付渠道。
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
文件,设置第一个请求的 tag
为 Yunai.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 加载dataId
为system-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-infra
的 config
(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.enable
、sys.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-id
和 Authorization
请求头。如下图所示:
tenant-id:1
Authorization: Bearer test1
添加完后,需要 F5 刷新下网页,否则全局参数不生效。
① 点击任意一个接口,进行接口的调用测试。这里,使用「管理后台 - 用户个中心」的“获得登录用户信息”举例子。
② 点击左侧「调试」按钮,并将请求头部的 header-id
和 Authorization
勾选上。
其中,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 实现分布式消息队列:
使用 Stream (opens new window)特性,提供【集群】消费的能力。
使用 Pub/Sub (opens new window)特性,提供【广播】消费的能力。
疑问:什么是【广播】消费?什么是【集群】消费?
参见《阿里云 —— 集群消费和广播消费 》 (opens new window)文档
#1. 集群消费
集群消费,是指消息发送到 Redis 时,有且只会被一个消费者(应用 JVM 实例)收到,然后消费成功。如下图所示:
友情提示:
如果你需要使用到【集群】消费,必须使用 Redis 5.0.0 以上版本,因为 Stream 特性是在该版本之后才引入噢!
#1.1 使用场景
集群消费在项目中的使用场景,主要是提供可靠的、可堆积的异步任务的能力。例如说:
短信模块,使用它异步 (opens new window)发送短信。
邮件模块,使用它异步 (opens new window)发送邮件。
相比 《开发指南 —— 异步任务》 来说,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 监听器,初始化对应的消费者。如下图所示: