一、摘要
实战文章,主要分为两部分。 一是搭建SpringCloud微服务与集群;二是基于OAuth2.1(Spring Authorization Server)搭建前后分离安全框架。
后端主要采用的技术栈:SpringBoot3.0,Security6.0,OAuth2.1。
前端主要采用的技术栈:Vue3.0+Vite,ElementPlus。
二、环境
系统:MAC Apple M2 13.3
开发工具:IntelliJ IDEA 2022.3.3 (Ultimate Edition)
JDK版本:OpenJDK 17.0.8
三、文章内容
本章内容,搭建用户信息服(cloud-user)、测试服(cloud-test)、公共服(cloud-common),并实现负载均衡。
5、父工程依赖
本章中,父工程相对于上一章,需要添加的依赖如下:
- Mysql驱动
- MybatisPlus(Mybatis加强版)
<properties>
<java.version>17</java.version>
……
<!-- mysql驱动版本 -->
<mysql-j.version>8.0.33</mysql-j.version>
<!-- mybatisPlus版本 -->
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
</properties>
<dependencyManagement>
<dependencies>
……
<!-- mysql驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-j.version}</version>
</dependency>
<!-- MybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
……
</dependencies>
</dependencyManagement>
完整的依赖配置为:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>cloud</name>
<description>cloud</description>
<modules>
<module>cloud-test</module>
<module>cloud-user</module>
<module>cloud-common</module>
<module>cloud-eureka</module>
</modules>
<properties>
<java.version>17</java.version>
<!-- snakeyaml版本 -->
<snakeyaml.version>2.0</snakeyaml.version>
<!-- lombok注解版本 -->
<lombok.version>1.18.28</lombok.version>
<!-- SpringCloud版本 -->
<spring-cloud.version>2022.0.1</spring-cloud.version>
<!-- mysql驱动版本 -->
<mysql-j.version>8.0.33</mysql-j.version>
<!-- mybatisPlus版本 -->
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 指定snakeyaml,修复1.33版本漏洞 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<!-- lombok注解开发 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- spring-cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-j.version}</version>
</dependency>
<!-- MybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
6、搭建用户信息服
(1)添加依赖
添加用户信息服需要的依赖:
- SpringWeb依赖
- Eureka客户端
- JDBC数据库驱动
- mysql驱动
- MybatisPlus依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>cloud-user</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- SpringWeb依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- JDBC驱动 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- MybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
</dependency>
</dependencies>
</project>
(2)配置参数
在src.main.resources包中,创建application.yml配置文件(其中注释部分可根据需求自行调整)
主要设置:
- service.port,分配端口。
- spring.application.name,修改服务名称,后续方便在注册中心查看。
- spring.application.datasource,mysql参数配置,本服务使用的数据库(不懂可参考第7节)
- eureka.client.service-url.defaultZone,填写两个Eureka服务地址
- mybatis-plus,mybatis-plus的配置,具体注释上面说明。
server:
port: 8001
spring:
application:
name: CLOUD-USER
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cloud?useUnicode=true&characterEncoding=utf-8
username: cloud
password: c123456
eureka:
client:
service-url:
defaultZone: http://eureka01:7101/eureka, http://eureka02:7102/eureka
mybatis-plus:
global-config:
db-config:
# 表名前缀
table-prefix: sys_
# 自增ID策略
id-type: auto
# 逻辑删除
# 全局逻辑删除的实体字段名(3.3.0后,可以不配置后面的默认值)
logic-delete-field: delFlag
# 逻辑删除值(默认为1)
logic-delete-value: 1
# 逻辑未删除值(默认为0)
logic-not-delete-value: 0
#configuration:
# 配置日志输出
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 指定SQL语句xml的目录
mapper-locations: classpath:/mybatis-plus/mapper/**/*.xml
# 项目启动会检查xml配置存在
check-config-location: true
# 指定本地额外配置文件(不能与configuration共存)
#config-location: classpath:/mybatis-plus/mybatis-config.xml
# 实体类package包加载位置(可以直接用类名)
type-aliases-package: com.example.domain.entity
(3)启动类修改
在src.main.java.com.example包中,将Main启动类修改为
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
(4)实体表
在src.main.java.com.example.domain.entity包中,编写一个sys_user表的实体表User。
(其中@mock与@since,为SmartDoc的内容,不影响,可忽略或删除。)
package com.example.domain.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
/**
* 用户账号ID
* @mock 1000
* @since v1.0
*/
@TableId(type= IdType.AUTO)
private Long id;
/**
* 用户名称(用户账号)
* @mock user
* @since v1.0
*/
private String username;
/**
* 用户昵称
* @mock 赵四
* @since v1.0
*/
private String nickname;
/**
* 用户密码
* @mock 123456
* @since v1.0
*/
private String password;
/**
* 绑定邮箱
* @mock 123456@qq.com
* @since v1.0
*/
private String email;
/**
* 账号状态,0激活,1停用
* @mock 0
* @since v1.0
*/
private Byte isActive;
/**
* 性别(默认0未知,1男,2女)
* @mock 0
* @since v1.0
*/
private Byte sex;
/**
* 用户类型,0普通用户,1超级管理员,2管理员
* @mock 0
* @since v1.0
*/
private Byte userType;
/**
* 创建时间
* @mock 2023-05-05 17:00:11
* @since v1.0
*/
@TableField(fill = FieldFill.INSERT)
private String createTime;
/**
* 更新时间
* @mock 2023-05-05 17:00:11
* @since v1.0
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
/**
* 登录时间
* @mock 2023-05-05 17:00:11
* @since v1.0
*/
private String loginTime;
/**
* 登出时间
* @mock 2023-05-05 17:00:11
* @since v1.0
*/
private String logoutTime;
/**
* 上次登录时间
* @mock 2023-05-05 17:00:11
* @since v1.0
*/
private String lastLoginTime;
/**
* IP地址(登录)
* @mock 175.178.15.253
* @since v1.0
*/
private String loginIp;
/**
* 删除标志,0正常使用,1已删除
* @mock 0
* @since v1.0
*/
private Byte delFlag;
/**
* 乐观锁版本
* @mock 1
* @since v1.0
*/
@Version
private int version;
}
(5)Mapper类
在src.main.java.com.example.mapper包中编写一个Mapper接口类UserMapper,用于获取数据库内容。
其中,这里可以继承BaseMapper方法,可比较方便使用MybatisPlus已编写好的一些方法,也可自行编写xml,或者使用注解类如@Select来编写sql查询代码。
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.domain.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
(6)控制类
在src.main.java.com.example.controller包中编写一个控制类UserController,用于请求返回用户信息。
这里的接口地址,邀请附带一个username参数,方便后续测试用。
package com.example.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.domain.entity.User;
import com.example.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class UserController {
@Resource
UserMapper userMapper;
@GetMapping("/user/{username}")
public User getUser(@PathVariable String username) {
System.out.println("我被访问了");
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, username);
User user = userMapper.selectOne(wrapper);
return user;
}
}
(7)配置启动工具
与上一篇文章编写Eureka的方式同理,这里同样方式处理,启动两个User服务。
- UserApplication02启动时,分配8002的端口。(UserApplication01为8001)
7、搭建数据库
(1)安装数据库,可自行下载安装mysql8.0.33,Mac推荐使用brew下载,这里不展开细说。
(2)配置帐号及数据库:
- 打开终端,使用命令mysql -u root -p,然后输入密码登录mysql。
- 创建cloud数据库
create database cloud;
- 创建cloud用户,密码为c123456,专项专用。
create user 'cloud'@'%' identified by 'c123456';
- 分配数据库cloud的权限给用户cloud
grant all privileges on cloud.* to 'cloud'@'%' with grant option;
- 最后刷新一下权限,以防止没有生效
flush privileges;
(2)创建sys_user表
CREATE TABLE `sys_user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
`nickname` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '' COMMENT '用户昵称',
`password` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',
`email` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '邮箱',
`phone` varchar(16) DEFAULT NULL COMMENT '手机',
`is_active` tinyint NOT NULL DEFAULT '0' COMMENT '账号状态(0激活,1停用)',
`source_from` varchar(16) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '' COMMENT '用户来源',
`sex` tinyint DEFAULT '0' COMMENT '性别(默认0未知,1男,2女)',
`user_type` tinyint NOT NULL DEFAULT '0' COMMENT '用户类型(0普通用户,1管理员)',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`login_time` datetime DEFAULT NULL COMMENT '登录时间',
`logout_time` datetime DEFAULT NULL COMMENT '登出时间',
`last_login_time` datetime DEFAULT NULL COMMENT '上一次登录时间',
`login_ip` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT 'IP地址',
`del_flag` tinyint DEFAULT '0' COMMENT '删除标志(0未删除,1已删除)',
`version` int DEFAULT '0' COMMENT '乐观锁版本(默认0)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
(3)创建两条基础数据,如id=1,username=user;id=2,username=test。
8、启动用户信息服
到这里,可以启动UserApplication的两个服务了。
服务启动后,打开浏览器,访问http://127.0.0.1:8001/api/user/user可拿到user用户的数据
尝试一下访问另一个服务http://127.0.0.1:8002/api/user/test也是可以拿到test用户的数据
另外打开http://eureka01:7101可以发现,两个用户信息服也是已经被Eureka发现并注册。
9、创建测试服
现在我们来搭建测试服,实现通过集群内部访问用户信息服,并实现负载均衡。
(1)添加依赖
添加测试服需要的依赖:
- 导入内部用户信息服的模块
- SpringWeb依赖
- Eureka客户端
- openfeign依赖(用于内部调用,类似RestTemplate)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>cloud-test</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 导入公共模块 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>cloud-user</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringWeb依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- openfeign依赖(内部服调用) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- JDBC驱动 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
</dependencies>
</project>
(2)配置参数
在src.main.resources包中,创建application.yml配置文件(其中注释部分可根据需求自行调整)
主要设置:
- service.port,分配端口。
- spring.application.name,修改服务名称,后续方便在注册中心查看。
- spring.application.datasource,mysql参数配置。
- spring.cloud.openfeign,openfeign客户端配置。
- eureka.client.service-url.defaultZone,填写两个Eureka服务地址
server:
port: 8101
spring:
application:
name: CLOUD-TEST
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cloud?useUnicode=true&characterEncoding=utf-8
username: cloud
password: c123456
cloud:
openfeign:
client:
config:
default:
# 连接超时时间
connectTimeout: 5000
# 读取超时时间
readTimeout: 5000
eureka:
client:
service-url:
defaultZone: http://eureka01:7101/eureka, http://eureka02:7102/eureka
(3)启动类修改
在src.main.java.com.example包中,将Main启动类修改为
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
(注意,这里需要添加@EnableFeignClients注解,注册openfeign。)
(4)client类
在src.main.java.com.example.service.client包中编写一个openfeign接口类UserClient,用于内部调用接口。
- name,填写需要访问的服务名称spring.application.name(集群内)
- path,可填写统一接口,作用类似@RequestMapping。
- getUser方法,有点类似Mapper的用法,直接用接口实现访问。这里接口填写需要请求的内部接口,如第6节中用户信息服中提供的接口。
package com.example.service.client;
import com.example.domain.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "CLOUD-USER", path = "/api")
public interface UserClient {
@GetMapping("/user/{username}")
User getUser(@PathVariable String username);
}
(这里解释下,为什么这个接口要放在service包下,正常来说,从Controller接收的请求,一般都会转到service服务下处理,这里为了方便,会直接在Controller中实现逻辑。)
(5)控制类
在src.main.java.com.example.controller包中编写一个控制类TestController,主要实现通过测试服,访问用户信息服,并实现负载均衡。
package com.example.controller;
import com.example.domain.entity.User;
import com.example.service.client.UserClient;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
UserClient userClient;
@GetMapping("/info/{username}")
public String testOne(@PathVariable String username) {
User user = userClient.getUser(username);
return user.toString();
}
@GetMapping("/my")
public String testOne() {
return "访问成功";
}
}
最后同理,可以实现下启动配置,再分配一个端口出来,实现两个测试服。
写到这里,已经实现通过测试服,可以访问内部接口,并且实现负载均衡(默认为轮训策略)。
启动测试服后,请求测试服自己内容的接口http://127.0.0.1:8101/test/my,访问成功。
接着访问用户信息的接口http://127.0.0.1:8101/test/info/user,也是没有问题的。
另外,我们可以修改用户信息服的UserController,添加一些日志
package com.example.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.domain.entity.User;
import com.example.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class UserController {
@Resource
UserMapper userMapper;
@Value("${spring.application.name}")
String serviceName;
@Value("${server.port}")
String serverPort;
@GetMapping("/user/{username}")
public User getUser(@PathVariable String username) {
System.out.println(serviceName + ":" + serverPort + ",被访问了");
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, username);
User user = userMapper.selectOne(wrapper);
return user;
}
}
然后多次访问接口http://127.0.0.1:8101/test/info/user,发现是实现了负载均衡。
四、后续
下一章:编写通用服,并实现统一响应类。 基于OAuth2.1,实现安全框架。