- 从0开始搭建jdk-21 springboot项目
从0开始搭建jdk-21 springboot项目
idea创建新项目
pom添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.glc.client</groupId>
<artifactId>glc-client</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
编写程序入口
package com.glc.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GlcClientApplication {
public static void main(String[] args) {
SpringApplication.run(GlcClientApplication.class, args);
}
}
然后直接启动程序就可以了,启动成功的日志如下
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.0)
2023-11-12T15:19:22.634+08:00 INFO 13420 --- [ main] com.glc.client.GlcClientApplication : Starting GlcClientApplication using Java 21.0.1 with PID 13420 (/Users/cgl/codes/glc-client/target/classes started by cgl in /Users/cgl/codes/glc-client)
2023-11-12T15:19:22.635+08:00 INFO 13420 --- [ main] com.glc.client.GlcClientApplication : No active profile set, falling back to 1 default profile: "default"
2023-11-12T15:19:22.948+08:00 INFO 13420 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-11-12T15:19:22.952+08:00 INFO 13420 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-11-12T15:19:22.952+08:00 INFO 13420 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.8]
2023-11-12T15:19:22.988+08:00 INFO 13420 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-11-12T15:19:22.989+08:00 INFO 13420 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 333 ms
2023-11-12T15:19:23.116+08:00 INFO 13420 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-11-12T15:19:23.120+08:00 INFO 13420 --- [ main] com.glc.client.GlcClientApplication : Started GlcClientApplication in 0.622 seconds (process running for 0.858)
添加yml配置文件
- 在resources目录下添加
application.yml
,application-dev.yml
,application-prod.yml
文件 - 指定一下程序的名称和端口, 在
application.yml
中添加如下配置:
spring:
application:
name: glc-client
profiles:
active: dev
server:
port: 8077
- 启动程序,可以看到启动日志里面配置生效了:
The following 1 profile is active: "dev"
Tomcat initialized with port(s): 8077 (http)
添加测试类
- 添加test依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
2.在test.java.com.glc.client
下面添加GlcClientApplicationTest
文件,输入以下测试代码:
package com.glc.client;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class GlcClientApplicationTest {
@Test
void main() {
System.out.println("test");
}
}
- 执行,可以看到输出了test字符串,test功能可以正常运行了
添加并测试home接口
- 创建controller文件夹
- 创建HomeController.java文件
- 编写接口,如下:
package com.glc.client.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
@GetMapping("/")
public String home(){
return "Hello World!";
}
}
- 浏览器访问:http://localhost:8077,可以看到返回了"Hello World!",说明接口生效了。
添加commons lang 依赖
后面会用到StringUtils,这里先导入 apache commons lang依赖
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
mysql数据库操作相关
添加musql-connect-java依赖
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
关于这个依赖的解释:
这段代码是一个Maven依赖配置,用于指定项目依赖的MySQL JDBC驱动程序 mysql-connector-java
。在这里,每个标签的含义如下:
<groupId>
: 指定依赖的组织或项目的标识符,在这个例子中是mysql
。这通常指定了开发或维护该依赖的组织。<artifactId>
: 指定具体的依赖名称,在这个例子中是mysql-connector-java
。这是该依赖在组中的唯一标识。<version>
: 指定依赖的版本,这里是8.0.33
。这告诉Maven要下载并使用该特定版本的依赖。<scope>
: 定义依赖的范围。在这个例子中,范围被设置为runtime
,意味着这个依赖只在运行时需要,并不在编译时需要。这是因为JDBC驱动通常在运行时用来建立数据库连接,而在编译代码时不是必须的。
简而言之,这段配置告诉Maven项目在运行时需要使用版本为8.0.33的MySQL JDBC驱动程序。
在yml文件中配置数据库信息
在application-dev.yml
添加数据库信息,同时本项目使用springboot默认的连接池hikari:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/glc?useUnicode=true&allowMultiQueries=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 32
auto-commit: true
pool-name: HikariCorePool
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: select TABLE_NAME from information_schema.tables limit 1
connection-init-sql: SET NAMES utf8mb4
在做完下面的mybatis plus相关的引入之后启动项目并第一次测试数据库查询相关接口之后,可以看到HikariCorePool - Start completed.
,说明数据库连接池启动成功。
引入mybatis plus
- 添加依赖。最新的版本在mvnrepository查看。
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4.1</version>
</dependency>
- 添加配置文件
创建config目录,在下面创建MybatisPlusConfig文件,写入以下内容:
package com.glc.client.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.glc.client.mapper*")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor();
innerInterceptor.setDbType(DbType.MYSQL);
innerInterceptor.setMaxLimit(100000L);
interceptor.addInnerInterceptor(innerInterceptor);
return interceptor;
}
}
引入mybatis plus generator插件
该插件主要用于一键生成表相关的实体类。
- 引入依赖mybatis-plus-generator和velocity-engine-core
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.4.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity-engine-core -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
- 创建MybatisPlusGenerator
在com.glc.client下面创建MybatisPlusGenerator文件,写入以下内容:
package com.glc.client;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import com.baomidou.mybatisplus.generator.fill.Property;
import org.apache.commons.lang3.StringUtils;
import java.sql.Types;
import java.util.Collections;
public class MybatisPlusGenerator {
public static void main(String[] args) {
String tableName = "user"; //需要生成的类对应的表名
IdType idType = IdType.ASSIGN_ID; //插入数据是默认的主键id的生成算法
String userDir = System.getProperty("user.dir");
String outputDir = StringUtils.join(userDir, "/src/main/java/");
String mapperXmlDir = StringUtils.join(userDir, "/src/main/resources/mapper");
//创建代码生成器对象
String url = "jdbc:mysql://127.0.0.1:3306/glc?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false";
String username = "root";
String password = "123456";
FastAutoGenerator.create(url, username, password)
.globalConfig(builder -> {
builder.author("gulong") // 设置作者
.fileOverride() // 覆盖已生成文件
.commentDate("yyyy-MM-dd") // 注释里面的日期的格式
.dateType(DateType.ONLY_DATE) //定义生成的实体类中日期类型 DateType.ONLY_DATE 默认值: DateType.TIME_PACK
.outputDir(outputDir); // 指定输出目录
})
.dataSourceConfig(builder -> {
builder.schema("glc")
.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
int typeCode = metaInfo.getJdbcType().TYPE_CODE;
if (typeCode == Types.SMALLINT) {
// 自定义类型转换
return DbColumnType.INTEGER;
}
return typeRegistry.getColumnType(metaInfo);
});
})
.packageConfig(builder -> {
builder.parent("com.glc.client") // 设置父包名
// .moduleName("") // 设置父包模块名
.controller("controller")
.entity("entity")
.service("service")
.service("service.impl")
.mapper("mapper")
.pathInfo(Collections.singletonMap(OutputFile.xml, mapperXmlDir)); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude(tableName) // 设置需要生成的表名
// .addTablePrefix("t_") // 设置过滤表前缀
.serviceBuilder()//service策略配置
.formatServiceFileName("%sService") //去掉类名中默认的I前缀
.formatServiceImplFileName("%sServiceImpl")
.entityBuilder()// 实体类策略配置
.idType(idType)//主键策略 这里AUTO是数据库自增ID,如果需要雪花算法生成的id可以改成ASSIGN_ID
.addTableFills(new Column("create_time", FieldFill.INSERT)) // 自动填充配置
.addTableFills(new Property("update_time", FieldFill.INSERT_UPDATE))
.enableLombok() //开启lombok
.logicDeleteColumnName("deleted")// 假删除字段
.enableTableFieldAnnotation()// 自动添加表字段的注解
.controllerBuilder() //controller 策略配置
.formatFileName("%sController")
.enableRestStyle() // 开启RestController注解
.mapperBuilder()// mapper策略配置
.formatMapperFileName("%sMapper")
.formatXmlFileName("%sMapper");
})
.templateEngine(new VelocityTemplateEngine())
.execute();
}
}
- 测试执行MybatisPlusGenerator程序
在上述配置中,我们指定了表为user表,点击执行,看看生成结果:
这样mybatis的代码生成器就配置完成了,之后需要添加新的表只需要改一下MybatisPlusGenerator里的表名就行了,idType看实际需求选择AUTO(自增id)还是ASSIGN_ID(雪花算法生成的id)
添加knife4j 4.0
为了方便调试接口,可以引入knife4j。在knife4j官方文档中,有如下说明:
由于springfox长久未更新,并且Swagger2规范在目前来看,一定程度上也并未升级,规范已经全部往OpenAPI3规范靠拢,因此,在Spring Boot 3.x版本中,开发者应该选择OpenAPI3规范来作为应用框架的开发首选方案。
- 引入依赖
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
- 配置文件
在application.yml
中,添加如下配置(点此查看官方关于权限控制的说明):
# springdoc-openapi项目配置
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
api-docs:
path: /v3/api-docs
group-configs:
- group: 'client'
paths-to-match: '/**'
packages-to-scan: com.glc.client
# knife4j的增强配置,不需要增强可以不配
knife4j:
enable: true
setting:
language: zh_cn
production: false
- 用knife4j官方的例子来测试一下是否成功
首先在controller包里添加BodyController文件:
package com.glc.client.controller;
import com.glc.client.model.FileResp;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("body")
@Tag(name = "body参数")
public class BodyController {
@Operation(summary = "普通body请求")
@PostMapping("/body")
public ResponseEntity<FileResp> body(@RequestBody FileResp fileResp) {
return ResponseEntity.ok(fileResp);
}
@Operation(summary = "普通body请求+Param+Header+Path")
@Parameters({
@Parameter(name = "id", description = "文件id", in = ParameterIn.PATH),
@Parameter(name = "token", description = "请求token", required = true, in = ParameterIn.HEADER),
@Parameter(name = "name", description = "文件名称", required = true, in = ParameterIn.QUERY)
})
@PostMapping("/bodyParamHeaderPath/{id}")
public ResponseEntity<FileResp> bodyParamHeaderPath(@PathVariable("id") String id, @RequestHeader("token") String token, @RequestParam("name") String name, @RequestBody FileResp fileResp) {
fileResp.setName(fileResp.getName() + ",receiveName:" + name + ",token:" + token + ",pathID:" + id);
return ResponseEntity.ok(fileResp);
}
}
其中FileResp如下:
package com.glc.client.model;
import lombok.Data;
@Data
public class FileResp {
private String name;
private Long fileSize;
private Boolean deleted;
}
然后启动项目,访问:http://127.0.0.1:8077/doc.html, 看到如下界面, 说明成功了:
测试接口结果如下:
![knife4j接口测试](https://i-blog.csdnimg.cn/blog_migrate/e906c84a91b90ee3be5e73f7ab90e111.png#pic_center)
至此knife4j就配置完成了,下面来看看knife的增强配置。
knife4j增强配置
首先看看官方说明:
在以前的版本中,开发者需要在配置文件中手动使用@EnableKnife4j来使用增强,自2.0.6版本后,只需要在配置文件中配置knife4j.enable=true即可不在使用注解
- 登录认证
knife4j:
# 开启增强配置
enable: true
# 开启Swagger的Basic认证功能,默认是false
basic:
enable: true
# Basic认证用户名
username: test
# Basic认证密码
password: 123
目前暂时没有其他增强配置的需求。官方文档已经很详细了,有需要的话直接查阅官方文档。
使用mybatis plus进行分页查询
下面我们将使用mybatis自带的分页功能进行分页查询
- 配置分页拦截器
之前其实已经配置过了,就是在MybatisPlusConfig文件里面,具体配置如下所示:
package com.glc.client.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.glc.client.mapper*")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor();
innerInterceptor.setDbType(DbType.MYSQL);
innerInterceptor.setMaxLimit(100000L);
interceptor.addInnerInterceptor(innerInterceptor);
return interceptor;
}
}
- 编写一个用户表的查询接口
在userController中添加如下接口代码:
package com.glc.client.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.glc.client.entity.User;
import com.glc.client.service.impl.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author gulong
* @since 2023-11-12
*/
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
/**
* 用户列表分页查询
*
* @param current 当前页数
* @param size 每页条数
* @return ResponseEntity<Page<User>>
*/
@GetMapping("/page")
public ResponseEntity<Page<User>> userPageQuery(@RequestParam("current") Integer current,
@RequestParam("size") Integer size) {
Page<User> pageParam = new Page<>(current, size);
LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.select(User::getId, User::getCreateTime, User::getCreateTime)
.ge(User::getCreateTime, "2023-11-12");
Page<User> pageResult = userService.page(pageParam, queryWrapper);
return ResponseEntity.ok(pageResult);
}
}
- 使用knife4j测试接口
接下来我们使用之前配置好的knife4j来测试user/page
接口,结果如下:
自定义分页查询
有时候我们可能对一些复杂的查询使用自定义的sql,我们可以用如下的方法进行分页查询。
UserController.java
/**
* 分页查询当天注册的用户信息
*
* @param current 当前页数
* @param size 每页条数
* @return user
*/
@GetMapping("/today")
public ResponseEntity<Page<User>> todayUserPageQuery(@RequestParam("current") Integer current,
@RequestParam("size") Integer size) {
Page<User> page = new Page<>(current, size);
String todayDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
userMapper.selectPageVo(page, todayDate);
return ResponseEntity.ok(page);
}
UserMapper.java
package com.glc.client.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.glc.client.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* <p>
* Mapper 接口
* </p>
*
* @author gulong
* @since 2023-11-12
*/
public interface UserMapper extends BaseMapper<User> {
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("todayDate") String todayDate);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.glc.client.mapper.UserMapper">
<select id="selectPageVo" resultType="com.glc.client.entity.User">
select id, create_time as createTime
from user
where create_time >= #{todayDate}
</select>
</mapper>
这样就实现了自定义的分页查询。
接口返回数据格式的相关配置
设置jackjson的默认时间格式
在上面的接口返回结果中,我们可以看到createTime的格式对前端不友好,我们统一个改成常用的yyyy-MM-dd HH:mm:ss
格式。本项目暂时未引入fastjson,在springboot中默认使用的是jackjson对接口返回的数据进行序列化,所以我们可以在yml文件中配置全局默认时间序列化格式。
- 在application.yml中添加如下配置:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
- 测试接口返回时间格式
如下图所示,可以看到已经按我们常用的格式返回了:
long类型返回前端的精度丢失问题
在上面的结果中,id的类型为long,这种类型的数据在返回给前端的时候如果数字过大,就会产生精度丢失的问题。
来看看AI智能助手对于这个问题的解释:
JavaScript中的Number类型仅能安全地表示-253到253之间的整数(即安全整数范围),而超出此范围的大整数在JavaScript中可能无法精确表示。
通常对此类问题的处理方法就是把这个long序列为string。
本项目默认使用jackjson序列化,所以可以使用如下注解来解决:
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
如果是使用fastjson序列化,则用如下注解:
@JSONField(serializeUsing = ToStringSerializer.class)
private Long id;
添加redis相关
web开发中,redis相关的操作是基本离不开的,下面将添加redis相关的一些依赖
引入spring-boot-starter-data-redis
经过对比,目前使用spring-boot-starter-data-redis是最合适的,下面开始引入。
拓展:点进依赖可以看到spring-boot-starter-data-redis是引入了spring-data-redis和lettuce-core,其默认使用lettuce-core客户端,线程安全
- 添加依赖,
其中commons-pool2是为了使用连接池。许多Redis客户端库,如Jedis、Lettuce等,在实现其连接池功能时依赖于commons-pool2。这些库使用commons-pool2来管理Redis连接的创建、借用、返回和销毁。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- 添加redis配置信息
在application-dev.yml
配置redis信息:
spring:
data:
redis:
host: localhost
port: 6379
password: 123456
database: 0
connect-timeout: 30000
timeout: 10000
lettuce:
pool:
min-idle: 16
max-idle: 32
max-active: 128
max-wait: 30000
- 关于redis的序列化
- 如果不进行配置,直接使用redisTemplate,则默认使用的jdk序列化,可读性很差,而且其他程序比如python就无法读取。
- 可以自定义序列化配置,但是注意最好不要使用
GenericJackson2JsonRedisSerializer
,这种会在redis里面存储类的信息,对于其他项目读取该数据非常不友好,
而且如果其他项目比如python项目设置了一个值,该项目将无法读取。 - 本项目选择采用stringRedisTemplate,这种就是key和value都存储为字符串,在序列化和反序列化的时候自己手动使用jackjson或者fastjson就行了
- 测试
在test.java.com.glc.client下创建RedisTest文件,输入如下测试代码并执行
package com.glc.client;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.glc.client.model.FileResp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@SpringBootTest
public class RedisTest {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
void testString() {
//存储/读取字符串
stringRedisTemplate.opsForValue().set("test-string", "redis-value");
String value = stringRedisTemplate.opsForValue().get("test-string");
System.out.printf(value);
stringRedisTemplate.delete("test-string");
}
@Test
void testJsonString() throws JsonProcessingException {
FileResp afile = new FileResp("afile", 123l, true);
FileResp bfile = new FileResp("bfile", 123l, false);
List<FileResp> list = Arrays.asList(afile, bfile);
stringRedisTemplate.opsForValue().set("test-json-string", new ObjectMapper().writeValueAsString(list));
String jsonValue = stringRedisTemplate.opsForValue().get("test-json-string");
System.out.printf(jsonValue);
List<FileResp> fromRedis = new ObjectMapper().readValue(jsonValue, new TypeReference<>() {
});
for (FileResp item : fromRedis) {
System.out.println(item);
}
stringRedisTemplate.delete("test-json-string");
}
@Test
void testHash() throws JsonProcessingException {
FileResp afile = new FileResp("afile", 123l, true);
FileResp bfile = new FileResp("bfile", 123l, false);
stringRedisTemplate.opsForHash().put("test-hash", "hash-1", new ObjectMapper().writeValueAsString(afile));
stringRedisTemplate.opsForHash().put("test-hash", "hash-2", new ObjectMapper().writeValueAsString(bfile));
String value = (String) stringRedisTemplate.opsForHash().get("test-hash", "hash-2");
System.out.println(value);
FileResp fileResp = new ObjectMapper().readValue(value, FileResp.class);
System.out.println(fileResp);
stringRedisTemplate.delete("test-hash");
}
@Test
void testList() {
FileResp afile = new FileResp("afile", 123l, true);
FileResp bfile = new FileResp("bfile", 123l, false);
List<FileResp> list = Arrays.asList(afile, bfile);
//数组里面只能是string
ObjectMapper objectMapper = new ObjectMapper();
List<String> jsonStrings = list.stream()
.map(fileResp -> {
try {
return objectMapper.writeValueAsString(fileResp);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
stringRedisTemplate.opsForList().rightPushAll("test-list", jsonStrings);
List<String> range = stringRedisTemplate.opsForList().range("test-list", 0, -1);
System.out.println(range);
stringRedisTemplate.delete("test-list");
}
}
测试结果发现序列化和反系列化都正常,至此,redis的普通操作都完成了。接下去引入redisson实现分布式锁
引入redisson
由于redisson提供了比较好的分布式锁解决方案,所以直接在项目中引入redisson
- 添加依赖
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.24.3</version>
</dependency>
- 配置文件
- 测试
引入webflux用于请求外部接口
有时候我们需要请求第三方接口,我们有多种选择,具体选择哪个比较好,我们来看看AI智能助手的回答:
在Spring Boot中发送HTTP请求可以使用多种框架,每个框架都有其特点和适用场景。以下是几个常用的选项:
- Spring’s
RestTemplate
: Spring Boot内置支持,易于集成和使用。RestTemplate
提供了丰富的方法来消费RESTful服务。它包括了多种便捷的方法来远程调用HTTP服务,并可以轻松地将响应绑定到POJOs上。但从Spring 5开始,RestTemplate
被标记为不推荐使用(deprecated),Spring团队推荐使用WebClient
。- Spring’s
WebClient
:WebClient
是Spring 5中引入的,它是一个更现代的、功能更全面的替代RestTemplate
的选项。WebClient
支持同步和异步请求,并且提供了更灵活的错误处理机制。WebClient
是在Spring WebFlux中引入的,但也可以在Spring MVC应用程序中使用。- OkHttp: OkHttp是一个高效的HTTP客户端,支持同步和异步请求,提供了快速的性能和广泛的功能,如连接池、GZIP压缩和响应缓存。OkHttp适用于需要精细控制HTTP通信的场景,但它不是Spring的一部分,需要单独添加依赖和配置。
- Apache HttpClient: 这是另一个流行的HTTP客户端库。它提供了丰富的功能,如连接管理、认证机制、缓存支持等。与OkHttp类似,Apache HttpClient不是Spring框架的一部分,需要额外配置。
选择哪个框架取决于您的具体需求:
- 如果您需要与Spring生态系统紧密集成,并且不介意使用反应式编程模型,可以选择
WebClient
。- 如果您需要更精细的控制和高性能,并且不介意添加额外的依赖和配置,可以选择OkHttp或Apache HttpClient。
- 如果您的项目已经使用了Spring 5以下的版本,
RestTemplate
仍然是一个可行的选项,尽管它已被标记为不推荐使用。总的来说,OkHttp是一个非常好的选择,特别是在需要高性能和定制HTTP请求的场景中。但如果您希望与Spring更紧密地集成,并利用Spring的反应式编程功能,那么
WebClient
可能是更好的选择。
基于上面的信息,本项目决定使用webflux,有以下几点考虑:
- 我们目前使用的是Spring最新版, spring webflux集成度更好
- 本项目想要接入gpt,获取流式响应,webflux最适合
下面开始具体步骤:
- 引入依赖:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webflux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
- controller
未完待续…