网站代码框架,wordpress的asp版,网站备案通过什么可以备案,网站怎么设置为可信任网站一、概述
1.1简介
R2DBC基于Reactive Streams反应流规范#xff0c;它是一个开放的规范#xff0c;为驱动程序供应商和使用方提供接口#xff08;r2dbc-spi#xff09;#xff0c;与JDBC的阻塞特性不同#xff0c;它提供了完全反应式的非阻塞API与关系型数据库交互。
…一、概述
1.1简介
R2DBC基于Reactive Streams反应流规范它是一个开放的规范为驱动程序供应商和使用方提供接口r2dbc-spi与JDBC的阻塞特性不同它提供了完全反应式的非阻塞API与关系型数据库交互。
简单说R2DBC项目是支持使用反应式编程API访问关系型数据库的桥梁定义统一接口规范不同数据库厂家通过实现该规范提供驱动程序包。
R2DBC定义了所有数据存储驱动程序必须实现的SPI目前实现R2DBC SPI的驱动程序包括r2dbc-h2为H2实现的驱动程序r2dbc mariadb为Mariadb实现的驱动程序r2dbc mssql为Microsoft SQL Server实现的本机驱动程序r2dbc mysql为Mysql实现的驱动程序r2dbc postgres为PostgreSQL实现的驱动程序
同时r2dbc还提供反应式连接池r2dbc-pool(https://github.com/r2dbc/r2dbc-pool)。
相关文档
https://doc.qzxdp.cn/spring/spring-data-r2dbc.html
1.2R2DBC历史
首先大家要知道我们最常使用的 JDBC 其实是同步的而我们使用 WebFlux 的目的是为了通过异步的方式来提高服务端的响应效率WebFlux 虽然实现了异步但是由于 JDBC 还是同步的而大部分应用都是离不开数据库的所以其实效率本质上还是没有提升。
那么怎么办呢有没有异步的 JDBC 呢有
目前市面上异步 JDBC 主要是两种 ADABADBA 是 Oracle 主导的 Java 异步数据库访问的标准 API它将会集成于未来的 Java 标准发行版中。但是目前发展比较慢只提供 OpenJDK 的沙盒特性供开发者研究之用。 R2DBCR2DBC 是 Spring 官方在 Spring5 发布了响应式 Web 框架 Spring WebFlux 之后急需能够满足异步响应的数据库交互 API不过由于缺乏标准和驱动Pivotal 团队开始自己研究响应式关系型数据库连接 Reactive Relational Database Connectivity并提出了 R2DBC 规范 API 用来评估可行性并讨论数据库厂商是否有兴趣支持响应式的异步非阻塞驱动程序。最早只有 PostgreSQL 、H2、MSSQL 三家数据库厂商不过现在 MySQL 也加入进来了这是一个极大的利好。目前 R2DBC 的最新版本是 0.9.0.RELEASE。
需要注意的是这两个都不是对原来 JDBC 的补充都是打算重新去设计数据库访问方案
二、快速入门
2.1原生API使用
https://r2dbc.io
导入依赖 dependencygroupIdio.asyncer/groupIdartifactIdr2dbc-mysql/artifactIdversion1.0.5/version/dependency
测试
//0、MySQL配置MySqlConnectionConfiguration configuration MySqlConnectionConfiguration.builder().host(localhost).port(3306).username(root).password(123456).database(test).build();//1、获取连接工厂MySqlConnectionFactory connectionFactory MySqlConnectionFactory.from(configuration);//2、获取到连接发送sql// JDBC Statement 封装sql的//3、数据发布者Mono.from(connectionFactory.create()).flatMapMany(connection -connection.createStatement(select * from t_author where id?id and name?name).bind(id,1L) //具名参数.bind(name,张三).execute()).flatMap(result - {return result.map(readable - {Long id readable.get(id, Long.class);String name readable.get(name, String.class);return new TAuthor(id, name);});}).subscribe(tAuthor - System.out.println(tAuthor tAuthor));
2.2Spring Data R2DBC整合
maven依赖 !-- https://mvnrepository.com/artifact/io.asyncer/r2dbc-mysql --dependencygroupIdio.asyncer/groupIdartifactIdr2dbc-mysql/artifactIdversion1.0.5/version/dependency!-- 响应式 Spring Data R2dbc--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-r2dbc/artifactId/dependency
yml配置
spring:r2dbc:url: r2dbcs:mysql://:3306/2046204601username: 2046204601password: 2046204601pool:enabled: trueinitial-size: 1validation-query: select 1sql:init:mode: alwaysjackson:default-property-inclusion: non_null # 序列化时忽略空属性值logging:level:sql: debugweb: debugcom:example: debugpattern:console: %-5level %C.%M[%line] - %msg%nmy:secretkey: 636eac2534bcfcc0
启动类 * SpringBoot 对r2dbc的自动配置* 1、R2dbcAutoConfiguration: 主要配置连接工厂、连接池** 2、R2dbcDataAutoConfiguration 主要给用户提供了 R2dbcEntityTemplate 可以进行CRUD操作* R2dbcEntityTemplate: 操作数据库的响应式客户端提供CruD api ; RedisTemplate XxxTemplate* 数据类型映射关系、转换器、自定义R2dbcCustomConversions 转换器组件* 数据类型转换intInteger varcharString datetimeInstant**** 3、R2dbcRepositoriesAutoConfiguration 开启Spring Data声明式接口方式的CRUD* mybatis-plus 提供了 BaseMapperIService自带了CRUD功能* Spring Data 提供了基础的CRUD接口不用写任何实现的情况下可以直接具有CRUD功能*** 4、R2dbcTransactionManagerAutoConfiguration 事务管理**/SpringBootApplication
public class R2DBCMainApplication {public static void main(String[] args) {SpringApplication.run(R2DBCMainApplication.class,args);}
}
测试
//1、Spring Data R2DBC基础的CRUD用 R2dbcRepository 提供好了//2、自定义复杂的SQL单表 Query//3、多表查询复杂结果集 DatabaseClient 自定义SQL及结果封装//Spring Data 提供的两个核心底层组件Autowired // join查询不好做 单表查询用R2dbcEntityTemplate r2dbcEntityTemplate; //CRUD API 更多API操作示例 https://docs.spring.io/spring-data/relational/reference/r2dbc/entity-persistence.htmlAutowired //贴近底层join操作好做 复杂查询好用DatabaseClient databaseClient; //数据库客户端Autowired// 导入R2dbcCustomConversions类用于自定义R2DBC的转换器R2dbcCustomConversions r2dbcCustomConversions;Testvoid r2dbcEntityTemplate() throws IOException {// Query By Criteria: QBC//1、Criteria构造查询条件 where id1 and name张三Criteria criteria Criteria.empty().and(id).is(1L).and(name).is(张三);//2、封装为 Query 对象Query query Query.query(criteria);r2dbcEntityTemplate.select(query, TAuthor.class).subscribe(tAuthor - System.out.println(tAuthor tAuthor.getName()));System.in.read();} Testvoid databaseClient() throws IOException {// 底层操作databaseClient.sql(select * from t_author)
// .bind(0,2L).fetch() //抓取数据.all()//返回所有.map(map - { //map bean 属性值System.out.println(map map);String id map.get(id).toString();String name map.get(name).toString();return new TAuthor(Long.parseLong(id), name, null);}).subscribe(tAuthor - System.out.println(tAuthor tAuthor));System.in.read();}
Repository
EnableR2dbcRepositories //开启 R2dbc 仓库功能jpa
Configuration
public class R2DbcConfiguration {}
Repository
public interface AuthorRepositories extends R2dbcRepositoryTAuthor,Long {//默认继承了一堆CRUD方法 像mybatis-plus//QBC Query By Criteria//QBE Query By Example//成为一个起名工程师 where id In () and name like ?//仅限单表复杂条件查询FluxTAuthor findAllByIdInAndNameLike(CollectionLong id, String name);//多表复杂查询Query(select * from t_author) //自定义query注解指定sql语句FluxTAuthor findHaha();// 1-1关联// 1-N关联//场景// 1、一个图书有唯一作者 1-1// 2、一个作者可以有很多图书 1-N}2.3一对一操作
转换器
ReadingConverter //读取数据库数据的时候,把row转成 TBook
public class BookConverter implements ConverterRow, TBookAuthor {//1、Query 指定了 sql如何发送//2、自定义 BookConverter 指定了 数据库返回的一 Row 数据怎么封装成 TBook//3、配置 R2dbcCustomConversions 组件让 BookConverter 加入其中生效Overridepublic TBookAuthor convert(Row source) {if(source null) return null;//自定义结果集的封装TBookAuthor tBook new TBookAuthor();tBook.setId(source.get(id, Long.class));tBook.setTitle(source.get(title, String.class));Long author_id source.get(author_id, Long.class);tBook.setAuthorId(author_id);tBook.setPublishTime(source.get(publish_time, Instant.class));//让 converter兼容更多的表结构处理if (source.getMetadata().contains(name)) {TAuthor tAuthor new TAuthor();tAuthor.setId(author_id);tAuthor.setName(source.get(name, String.class));tBook.setAuthor(tAuthor);}return tBook;}
配置生效
EnableR2dbcRepositories //开启 R2dbc 仓库功能jpa
Configuration
public class R2DbcConfiguration {Bean //替换容器中原来的ConditionalOnMissingBeanpublic R2dbcCustomConversions conversions(){//把我们的转换器加入进去 效果新增了我们的 Converterreturn R2dbcCustomConversions.of(MySqlDialect.INSTANCE,new BookConverter());}
}
自定义 ConverterRowBean 方式 BeanR2dbcCustomConversions r2dbcCustomConversions(){ListConverter?, ? converters new ArrayList();converters.add(new BookConverter());return R2dbcCustomConversions.of(MySqlDialect.INSTANCE, converters);}//1-1 结合自定义 Converter
bookRepostory.hahaBook(1L).subscribe(tBook - System.out.println(tBook tBook));
编程式封装方式: 使用DatabaseClient
//1-1第二种方式
databaseClient.sql(select b.*,t.name as name from t_book b LEFT JOIN t_author t on b.author_id t.id WHERE b.id ?).bind(0, 1L).fetch().all().map(row- {String id row.get(id).toString();String title row.get(title).toString();String author_id row.get(author_id).toString();String name row.get(name).toString();TBook tBook new TBook();tBook.setId(Long.parseLong(id));tBook.setTitle(title);TAuthor tAuthor new TAuthor();tAuthor.setName(name);tAuthor.setId(Long.parseLong(author_id));tBook.setAuthor(tAuthor);return tBook;}).subscribe(tBook - System.out.println(tBook tBook));
2.4一对多操作
bufferUntilChanged bufferUntilChanged 是一个操作符用于在数据流中缓存元素直到遇到一个与前一个元素不同的元素。 Flux.just(1,2,3,4,8,5,6,7,8,9,10).bufferUntilChanged(integer - integer%40 ).subscribe(list- System.out.println(list list));
1-N
Table(t_author)
NoArgsConstructor
AllArgsConstructor
Getter
Setter
Data
public class TAuthor {Idprivate Long id;private String name;// //1-N如何封装Transient //临时字段并不是数据库表中的一个字段
// Field(existfalse)private ListTBook books;} Testvoid oneToN() throws IOException {// databaseClient.sql(select a.id aid,a.name,b.* from t_author a
// left join t_book b on a.id b.author_id
// order by a.id)
// .fetch()
// .all(row - {
//
// })// 1~6// 1false 2false 3:false 4: true 8:true 5:false 6:false 7:false 8:true 9:false 10:false// [1,2,3]// [4,8]// [5,6,7]// [8]// [9,10]// bufferUntilChanged// 如果下一个判定值比起上一个发生了变化就开一个新buffer保存如果没有变化就保存到原buffer中// Flux.just(1,2,3,4,8,5,6,7,8,9,10)
// .bufferUntilChanged(integer - integer%40 )
// .subscribe(list- System.out.println(list list));; //自带分组FluxTAuthor flux databaseClient.sql(select a.id aid,a.name,b.* from t_author a left join t_book b on a.id b.author_id order by a.id).fetch().all().bufferUntilChanged(rowMap - Long.parseLong(rowMap.get(aid).toString())).map(list - {TAuthor tAuthor new TAuthor();MapString, Object map list.get(0);tAuthor.setId(Long.parseLong(map.get(aid).toString()));tAuthor.setName(map.get(name).toString());//查到的所有图书ListTBook tBooks list.stream().map(ele - {TBook tBook new TBook();tBook.setId(Long.parseLong(ele.get(id).toString()));tBook.setAuthorId(Long.parseLong(ele.get(author_id).toString()));tBook.setTitle(ele.get(title).toString());return tBook;}).collect(Collectors.toList());tAuthor.setBooks(tBooks);return tAuthor;});//Long 数字缓存 -127 - 127// 对象比较需要自己写好equals方法flux.subscribe(tAuthor - System.out.println(tAuthor tAuthor));System.in.read();}
2.5 route handler
此时就可以调用封装好的 CRUD 方法进行简单的增删改查操作了。在 Webflux 框架中我们可以使用 SpringMVC 中 Controller Service 的模式进行开发也可以使用 Webflux 中 route handler 的模式进行开发。
handler 就相当于定义很多处理器其中不同的方法负责处理不同路由的请求其对应的是传统的 Service 层
Component
public class UserHandler {Autowiredprivate UserRepository userRepository;public MonoServerResponse addUser(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userRepository.saveAll(request.bodyToMono(User.class)), User.class);}public MonoServerResponse delUser(ServerRequest request) {return userRepository.findById(Integer.parseInt(request.pathVariable(id))).flatMap(user - userRepository.delete(user).then(ServerResponse.ok().build())).switchIfEmpty(ServerResponse.notFound().build());}public MonoServerResponse updateUser(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userRepository.saveAll(request.bodyToMono(User.class)), User.class);}public MonoServerResponse getAllUser(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userRepository.findAll(), User.class);}public MonoServerResponse getAllUserStream(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(userRepository.findAll(), User.class);}}route 就是路由配置其规定路由的分发规则将不同的请求路由分发给相应的 handler 进行业务逻辑的处理其对应的就是传统的 Controller 层
Configuration
public class RouteConfig {BeanRouterFunctionServerResponse userRoute(UserHandler userHandler) {return RouterFunctions.nest(RequestPredicates.path(/userRoute),RouterFunctions.route(RequestPredicates.POST(), userHandler::addUser).andRoute(RequestPredicates.DELETE(/{id}), userHandler::delUser).andRoute(RequestPredicates.PUT(), userHandler::updateUser).andRoute(RequestPredicates.GET(), userHandler::getAllUser).andRoute(RequestPredicates.GET(/stream), userHandler::getAllUserStream));}}三、R2DBC 实战
3.1环境配置
maven依赖 dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-crypto/artifactId/dependency!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --dependencygroupIdcom.auth0/groupIdartifactIdjava-jwt/artifactIdversion4.4.0/version/dependency
yml依赖
spring:r2dbc:url: r2dbcs:mysql://:3306/2046204601username: password: pool:enabled: trueinitial-size: 1validation-query: select 1sql:init:mode: alwaysjackson:default-property-inclusion: non_null # 序列化时忽略空属性值logging:level:sql: debugweb: debugcom:example: debugpattern:console: %-5level %C.%M[%line] - %msg%nmy:secretkey: 636eac2534bcfcc0
schema.sql
create table if not exists user_react
(id char(19) not null primary key,name varchar(10) not null,account varchar(15) not null,password varchar(65) not null,role char(5) not null,insert_time datetime not null default current_timestamp,update_time datetime not null default current_timestamp on update current_timestamp,unique (account),index (role)
);
3.2CRUD
实体类
Data
NoArgsConstructor
AllArgsConstructor
Builder
public class UserReact {public static final String ROLE_USER hOl7U;public static final String ROLE_ADMIN yxp4r;IdCreatedByprivate String id;private String name;private String account;JsonProperty(access JsonProperty.Access.WRITE_ONLY)private String password;JsonIgnoreprivate String role;ReadOnlyPropertyprivate LocalDateTime insertTime;ReadOnlyPropertyprivate LocalDateTime updateTime;
}
Repository
Repository
public interface UserRepository extends ReactiveCrudRepositoryUserReact, String {MonoUserReact findByAccount(String account);Query(select * from user_react u where u.role:role;)FluxUserReact findByRole(String role);
}
工具类
Component // 标记为Spring组件使其可以被自动扫描并注入到其他类中
Slf4j // 使用Lombok库提供的日志功能简化日志记录操作
public class JWTComponent {// 私钥用于签名和验证JWT令牌Value(${my.secretkey})private String secretkey;/*** 对给定的负载数据进行编码生成一个JWT令牌。* param map 包含有效载荷数据的Map对象* return 返回编码后的JWT令牌字符串*/public String encode(MapString, Object map) {// 设置令牌过期时间为当前时间加一个月LocalDateTime time LocalDateTime.now().plusMonths(1);return JWT.create().withPayload(map) // 添加有效载荷数据.withIssuedAt(new Date()) // 设置令牌签发时间.withExpiresAt(Date.from(time.atZone(ZoneId.systemDefault()).toInstant())) // 设置令牌过期时间.sign(Algorithm.HMAC256(secretkey)); // 使用HMAC256算法和私钥进行签名}/*** 解码给定的JWT令牌验证其有效性并返回解码后的有效载荷。* param token 要解码的JWT令牌字符串* return 返回一个包含解码后有效载荷的Mono对象*/public MonoDecodedJWT decode(String token) {try {// 使用指定的算法和私钥验证并解码JWT令牌DecodedJWT decodedJWT JWT.require(Algorithm.HMAC256(secretkey)).build().verify(token);return Mono.just(decodedJWT); // 如果验证成功返回解码后的有效载荷} catch (TokenExpiredException | SignatureVerificationException | JWTDecodeException e) {Code code Code.FORBIDDEN; // 默认错误代码为禁止访问if (e instanceof TokenExpiredException) {code Code.TOKEN_EXPIRED; // 如果令牌已过期则设置相应的错误代码}return Mono.error(XException.builder().code(code).build()); // 返回一个包含错误信息的Mono对象}}
}Configuration
public class PasswordEncoderConfig {Beanpublic PasswordEncoder getPasswordEncoder() {return new BCryptPasswordEncoder();}
}
异常
Getter
public enum Code {LOGIN_ERROR(400, 用户名密码错误),BAD_REQUEST(400, 请求错误),UNAUTHORIZED(401, 未登录),TOKEN_EXPIRED(403, 过期请重新登录),FORBIDDEN(403, 无权限);public static final int ERROR 400;private final int code;private final String message;Code(int code, String message) {this.code code;this.message message;}}
EqualsAndHashCode(callSuper true)
Data
Builder
NoArgsConstructor
AllArgsConstructor
public class XException extends RuntimeException{private Code code;private int codeN;private String message;
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import reactor.core.publisher.Mono;Slf4j
RestControllerAdvice
public class ExceptionController {// 处理XException异常用于Mono中异常的处理// 注意此方法在filter内无效需要在单独处理。ExceptionHandler(XException.class)public MonoResultVO handleXException(XException exception) {// 如果异常中有错误码则返回带有错误码的错误信息if(exception.getCode() ! null) {return Mono.just(ResultVO.error(exception.getCode()));}// 否则返回带有错误码和错误信息的默认错误信息return Mono.just(ResultVO.error(exception.getCodeN(), exception.getMessage()));}// 处理通用的Exception异常ExceptionHandler(Exception.class)public MonoResultVO handleException(Exception exception) {// 返回带有BAD_REQUEST错误码和异常信息的错误信息return Mono.just(ResultVO.error(Code.BAD_REQUEST.getCode(), exception.getMessage()));}// 处理UncategorizedR2dbcException异常通常与数据库操作相关ExceptionHandler(UncategorizedR2dbcException.class)public MonoResultVO handelUncategorizedR2dbcException(UncategorizedR2dbcException exception) {// 返回带有BAD_REQUEST错误码和唯一约束冲突加上异常信息的错误信息return Mono.just(ResultVO.error(Code.BAD_REQUEST.getCode(), 唯一约束冲突 exception.getMessage()));}
}vo层
public interface RequestConstant {String TOKEN token;String UID uid;String ROLE role;
}
服务类
// 使用Service注解将该类标记为Spring框架的服务组件
Service
// 使用Slf4j注解自动为该类生成一个SLF4J日志记录器
Slf4j
// 使用RequiredArgsConstructor注解自动生成一个构造函数用于初始化final字段
RequiredArgsConstructor
public class InitService {// 注入UserService依赖private final UserService userService;// 使用Transactional注解确保方法内的操作在一个事务中执行Transactional// 使用EventListener注解监听ApplicationReadyEvent事件当事件发生时执行该方法EventListener(classes ApplicationReadyEvent.class)public MonoVoid onApplicationReadyEvent() {// 定义一个账户名String account admin;// 调用userService的getUser方法尝试获取指定账户的用户信息return userService.getUser(account)// 如果用户不存在返回的Mono为空则执行以下操作.switchIfEmpty(Mono.defer(() - {// 创建一个新的UserReact对象设置相关属性UserReact user UserReact.builder().name(account).account(account).role(UserReact.ROLE_ADMIN).build();// 调用userService的addUser方法添加新用户并返回其Monoreturn userService.addUser(user);})).then(); // 最后返回一个完成的MonoVoid}
}// 导入相关依赖和服务注解
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Mono;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;// 定义UserService类用于处理用户相关的业务逻辑
Service
Slf4j
RequiredArgsConstructor
public class UserService {private final UserRepository userRepository; // 注入UserRepository用于访问数据库中的用户数据private final PasswordEncoder passwordEncoder; // 注入PasswordEncoder用于密码加密// 根据账号获取用户信息的方法public MonoUserReact getUser(String account) {return userRepository.findByAccount(account); // 调用userRepository的findByAccount方法查询用户}// 根据用户ID获取用户信息的方法public MonoUserReact getUserById(String uid) {return userRepository.findById(uid); // 调用userRepository的findById方法查询用户}// 添加用户的方法使用事务注解确保操作的原子性Transactionalpublic MonoUserReact addUser(UserReact user) {return userRepository.findByAccount(user.getAccount()) // 先检查账号是否已存在.handle((u, sink) -sink.error(XException.builder() // 如果已存在则抛出异常.codeN(Code.ERROR).message(用户已存在).build())).cast(UserReact.class) // 将结果转换为UserReact类型.switchIfEmpty(Mono.defer(() - { // 如果不存在则创建新用户user.setPassword(passwordEncoder.encode(user.getAccount())); // 对密码进行加密return userRepository.save(user); // 保存用户到数据库}));}// 根据角色获取用户列表的方法public MonoListUserReact listUsers(String role) {return userRepository.findByRole(role).collectList(); // 调用userRepository的findByRole方法查询用户列表并收集为List}
}控制层
// 定义一个名为LoginController的类用于处理登录相关的请求
RestController
Slf4j // 使用Lombok库提供的日志功能
RequiredArgsConstructor // 使用Lombok库提供的构造器注入功能
RequestMapping(/api/) // 设置该控制器处理的请求的基本路径为/api/
public class LoginController {private final UserService userService; // 用户服务组件用于获取用户信息private final PasswordEncoder passwordEncoder; // 密码编码器用于验证密码是否正确private final JWTComponent jwtComponent; // JSON Web Token组件用于生成和解析JWT令牌// 处理POST请求映射到/login路径用于用户登录PostMapping(login)public MonoResultVO login(RequestBody UserReact user, ServerHttpResponse response) {// 从userService中获取用户信息根据用户的账号进行筛选return userService.getUser(user.getAccount())// 检查用户提供的密码是否与数据库中的密码匹配.filter(u - passwordEncoder.matches(user.getPassword(), u.getPassword()))// 如果密码匹配成功则执行以下操作.map(u - {// 创建一个包含用户ID和角色信息的Map对象MapString, Object tokenM Map.of(RequestConstant.UID, u.getId(),RequestConstant.ROLE, u.getRole());// 使用jwtComponent对tokenM进行编码生成JWT令牌String token jwtComponent.encode(tokenM);// 获取响应头对象HttpHeaders headers response.getHeaders();// 将生成的JWT令牌添加到响应头的token字段中headers.add(token, token);// 将用户的角色添加到响应头的role字段中headers.add(role, u.getRole());// 返回一个表示成功的ResultVO对象其中包含用户信息return ResultVO.success(Map.of(user, u));})// 如果密码不匹配或用户不存在则返回一个表示登录错误的ResultVO对象.defaultIfEmpty(ResultVO.error(Code.LOGIN_ERROR));}
}
// 定义一个名为AdminController的控制器类用于处理与管理员相关的API请求
RestController
// 使用Slf4j注解为该类提供日志记录功能
Slf4j
// 使用RequiredArgsConstructor注解自动生成构造函数要求所有final字段都必须被初始化
RequiredArgsConstructor
// 设置该控制器的基础URL路径为/api/admin/
RequestMapping(/api/admin/)
public class AdminController {// 注入UserService实例用于处理用户相关的业务逻辑private final UserService userService;// 处理POST请求创建新用户PostMapping(users)public MonoResultVO postUsers(RequestBody UserReact user) {// 调用userService的addUser方法添加用户并返回一个包含成功信息的ResultVO对象return userService.addUser(user).thenReturn(ResultVO.ok());}// 处理GET请求获取用户信息GetMapping(info)public MonoResultVO getInfo(RequestAttribute(RequestConstant.UID) String uid) {// 调用userService的getUserById方法根据用户ID获取用户信息并将其包装在ResultVO对象中返回return userService.getUserById(uid).map(user - ResultVO.success(Map.of(user, user)));}
}过滤器
// 定义一个名为ResponseHelper的类用于处理响应
Component
Slf4j
RequiredArgsConstructor
public class ResponseHelper {// 使用ObjectMapper对象进行JSON序列化private final ObjectMapper objectMapper;// 定义一个response方法接收Code枚举类型和一个ServerWebExchange对象作为参数SneakyThrowspublic MonoVoid response(Code code, ServerWebExchange exchange) {// 将错误信息转换为JSON字符串并编码为UTF-8字节数组byte[] bytes objectMapper.writeValueAsString(ResultVO.error(code)).getBytes(StandardCharsets.UTF_8);// 获取服务器响应对象ServerHttpResponse response exchange.getResponse();// 将字节数组包装成DataBuffer对象DataBuffer wrap response.bufferFactory().wrap(bytes);// 设置响应内容类型为JSONresponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);// 将DataBuffer写入响应并返回MonoVoid对象return response.writeWith(Flux.just(wrap));}
}
// 导入相关依赖
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;// 定义一个登录过滤器类实现WebFilter接口
Component
Slf4j
Order(1)
RequiredArgsConstructor
public class LoginFilter implements WebFilter {// 定义需要过滤的路径模式private final PathPattern path new PathPatternParser().parse(/api/**);// 定义不需要过滤的路径模式列表private final ListPathPattern excludesS List.of(new PathPatternParser().parse(/api/login));// 注入JWT组件private final JWTComponent jwtComponent;// 注入响应帮助类private final ResponseHelper responseHelper;// 重写filter方法Overridepublic MonoVoid filter(ServerWebExchange exchange, WebFilterChain chain) {// 获取请求对象ServerHttpRequest request exchange.getRequest();// 遍历排除列表如果请求路径匹配排除列表中的任何一个直接放行for (PathPattern p : excludesS) {if (p.matches(request.getPath().pathWithinApplication())) {return chain.filter(exchange);}}// 如果请求路径不在过滤范围内返回异常响应if (!path.matches(request.getPath().pathWithinApplication())) {return responseHelper.response(Code.BAD_REQUEST, exchange);}// 从请求头中获取tokenString token request.getHeaders().getFirst(RequestConstant.TOKEN);// 如果token为空返回未授权响应if (token null) {return responseHelper.response(Code.UNAUTHORIZED, exchange);}// 解码token并将解码结果放入请求属性中return jwtComponent.decode(token).flatMap(decode - {MapString, Object attributes exchange.getAttributes();attributes.put(RequestConstant.UID, decode.getClaim(RequestConstant.UID).asString());attributes.put(RequestConstant.ROLE, decode.getClaim(RequestConstant.ROLE).asString());// 继续执行后续过滤器链return chain.filter(exchange);});}
}