从0开始搭建jdk-21 springboot 3.1项目

从0开始搭建jdk-21 springboot 3.1项目

  • 从0开始搭建jdk-21 springboot项目
    • idea创建新项目
    • pom添加依赖
    • 编写程序入口
    • 添加yml配置文件
    • 添加测试类
    • 添加并测试home接口
    • 添加***mons lang 依赖
    • mysql数据库操作相关
      • 添加musql-connect-java依赖
      • 在yml文件中配置数据库信息
      • 引入mybatis plus
      • 引入mybatis plus generator插件
      • 添加knife4j 4.0
        • knife4j增强配置
      • 使用mybatis plus进行分页查询
        • 自定义分页查询
    • 接口返回数据格式的相关配置
      • 设置jackjson的默认时间格式
      • long类型返回前端的精度丢失问题
    • 添加redis相关
      • 引入spring-boot-starter-data-redis
      • 引入redisson
      • 引入webflux用于请求外部接口

从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>***.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.***piler.source>21</maven.***piler.source>
        <maven.***piler.target>21</maven.***piler.target>
        <maven.***piler.encoding>UTF-8</maven.***piler.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 ***.glc.client;

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

@SpringBootApplication
public class Gl***lientApplication {
    public static void main(String[] args) {
        SpringApplication.run(Gl***lientApplication.class, args);
    }
}

然后直接启动程序就可以了,启动成功的日志如下

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.0)

2023-11-12T15:19:22.634+08:00  INFO 13420 --- [           main] ***.glc.client.Gl***lientApplication      : Starting Gl***lientApplication 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] ***.glc.client.Gl***lientApplication      : 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 ***pleted 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] ***.glc.client.Gl***lientApplication      : Started Gl***lientApplication in 0.622 seconds (process running for 0.858)

添加yml配置文件

  1. 在resources目录下添加application.yml, application-dev.yml, application-prod.yml文件
  2. 指定一下程序的名称和端口, 在 application.yml中添加如下配置:
spring:
  application:
    name: glc-client
  profiles:
    active: dev

server:
  port: 8077
  1. 启动程序,可以看到启动日志里面配置生效了:
The following 1 profile is active: "dev"
Tomcat initialized with port(s): 8077 (http)

添加测试类

  1. 添加test依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

2.在test.java.***.glc.client下面添加Gl***lientApplicationTest文件,输入以下测试代码:

package ***.glc.client;


import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Gl***lientApplicationTest {

    @Test
    void main() {
        System.out.println("test");
    }
}
  1. 执行,可以看到输出了test字符串,test功能可以正常运行了

添加并测试home接口

  1. 创建controller文件夹
  2. 创建HomeController.java文件
  3. 编写接口,如下:
package ***.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!";
    }
}


  1. 浏览器访问:http://localhost:8077,可以看到返回了"Hello World!",说明接口生效了。

添加***mons lang 依赖

后面会用到StringUtils,这里先导入 apache ***mons lang依赖

<!-- https://mvnrepository.***/artifact/org.apache.***mons/***mons-lang3 -->
<dependency>
    <groupId>org.apache.***mons</groupId>
    <artifactId>***mons-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: ***.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-***mit: 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 ***pleted.,说明数据库连接池启动成功。

引入mybatis plus

  1. 添加依赖。最新的版本在mvnrepository查看。
<!-- https://mvnrepository.***/artifact/***.baomidou/mybatis-plus-boot-starter -->
<dependency>
    <groupId>***.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.4.1</version>
</dependency>
  1. 添加配置文件
    创建config目录,在下面创建MybatisPlusConfig文件,写入以下内容:
package ***.glc.client.config;

import ***.baomidou.mybatisplus.annotation.DbType;
import ***.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import ***.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("***.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插件

该插件主要用于一键生成表相关的实体类。

  1. 引入依赖mybatis-plus-generator和velocity-engine-core
<!-- https://mvnrepository.***/artifact/***.baomidou/mybatis-plus-generator -->
<dependency>
    <groupId>***.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.4.1</version>
</dependency>
<!-- https://mvnrepository.***/artifact/org.apache.velocity/velocity-engine-core -->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>
  1. 创建MybatisPlusGenerator
    在***.glc.client下面创建MybatisPlusGenerator文件,写入以下内容:
package ***.glc.client;

import ***.baomidou.mybatisplus.annotation.FieldFill;
import ***.baomidou.mybatisplus.annotation.IdType;
import ***.baomidou.mybatisplus.generator.FastAutoGenerator;
import ***.baomidou.mybatisplus.generator.config.OutputFile;
import ***.baomidou.mybatisplus.generator.config.rules.DateType;
import ***.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import ***.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import ***.baomidou.mybatisplus.generator.fill.Column;
import ***.baomidou.mybatisplus.generator.fill.Property;
import org.apache.***mons.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() // 覆盖已生成文件
                            .***mentDate("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("***.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();
    }

}

  1. 测试执行MybatisPlusGenerator程序
    在上述配置中,我们指定了表为user表,点击执行,看看生成结果:

这样mybatis的代码生成器就配置完成了,之后需要添加新的表只需要改一下MybatisPlusGenerator里的表名就行了,idType看实际需求选择AUTO(自增id)还是ASSIGN_ID(雪花算法生成的id)

添加knife4j 4.0

为了方便调试接口,可以引入knife4j。在knife4j官方文档中,有如下说明:

由于springfox长久未更新,并且Swagger2规范在目前来看,一定程度上也并未升级,规范已经全部往OpenAPI3规范靠拢,因此,在Spring Boot 3.x版本中,开发者应该选择OpenAPI3规范来作为应用框架的开发首选方案。

  1. 引入依赖
<!-- https://mvnrepository.***/artifact/***.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter -->
<dependency>
    <groupId>***.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>
  1. 配置文件
    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: ***.glc.client
# knife4j的增强配置,不需要增强可以不配
knife4j:
  enable: true
  setting:
    language: zh_***
  production: false
  1. 用knife4j官方的例子来测试一下是否成功
    首先在controller包里添加BodyController文件:
package ***.glc.client.controller;

import ***.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 ***.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就配置完成了,下面来看看knife的增强配置。

knife4j增强配置

首先看看官方说明:

在以前的版本中,开发者需要在配置文件中手动使用@EnableKnife4j来使用增强,自2.0.6版本后,只需要在配置文件中配置knife4j.enable=true即可不在使用注解

  1. 登录认证
knife4j:
  # 开启增强配置 
  enable: true
 # 开启Swagger的Basic认证功能,默认是false
  basic:
      enable: true
      # Basic认证用户名
      username: test
      # Basic认证密码
      password: 123

目前暂时没有其他增强配置的需求。官方文档已经很详细了,有需要的话直接查阅官方文档。

使用mybatis plus进行分页查询

下面我们将使用mybatis自带的分页功能进行分页查询

  1. 配置分页拦截器
    之前其实已经配置过了,就是在MybatisPlusConfig文件里面,具体配置如下所示:
package ***.glc.client.config;

import ***.baomidou.mybatisplus.annotation.DbType;
import ***.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import ***.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("***.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;
    }
}

  1. 编写一个用户表的查询接口
    在userController中添加如下接口代码:
package ***.glc.client.controller;

import ***.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import ***.baomidou.mybatisplus.core.toolkit.Wrappers;
import ***.baomidou.mybatisplus.extension.plugins.pagination.Page;
import ***.glc.client.entity.User;
import ***.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);
    }
}

  1. 使用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 ***.glc.client.mapper;

import ***.baomidou.mybatisplus.extension.plugins.pagination.Page;
import ***.glc.client.entity.User;
import ***.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="***.glc.client.mapper.UserMapper">

    <select id="selectPageVo" resultType="***.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文件中配置全局默认时间序列化格式。

  1. 在application.yml中添加如下配置:
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  1. 测试接口返回时间格式
    如下图所示,可以看到已经按我们常用的格式返回了:

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客户端,线程安全

  1. 添加依赖,

其中***mons-pool2是为了使用连接池。许多Redis客户端库,如Jedis、Lettuce等,在实现其连接池功能时依赖于***mons-pool2。这些库使用***mons-pool2来管理Redis连接的创建、借用、返回和销毁。

<!-- https://mvnrepository.***/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.***/artifact/org.apache.***mons/***mons-pool2 -->
<dependency>
    <groupId>org.apache.***mons</groupId>
    <artifactId>***mons-pool2</artifactId>
</dependency>
  1. 添加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
  1. 关于redis的序列化
  • 如果不进行配置,直接使用redisTemplate,则默认使用的jdk序列化,可读性很差,而且其他程序比如python就无法读取。
  • 可以自定义序列化配置,但是注意最好不要使用GenericJackson2JsonRedisSerializer,这种会在redis里面存储类的信息,对于其他项目读取该数据非常不友好,
    而且如果其他项目比如python项目设置了一个值,该项目将无法读取。
  • 本项目选择采用stringRedisTemplate,这种就是key和value都存储为字符串,在序列化和反序列化的时候自己手动使用jackjson或者fastjson就行了
  1. 测试
    在test.java.***.glc.client下创建RedisTest文件,输入如下测试代码并执行
package ***.glc.client;

import ***.fasterxml.jackson.core.JsonProcessingException;
import ***.fasterxml.jackson.core.type.TypeReference;
import ***.fasterxml.jackson.databind.ObjectMapper;
import ***.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

  1. 添加依赖
<!-- https://mvnrepository.***/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.24.3</version>
</dependency>
  1. 配置文件
  2. 测试

引入webflux用于请求外部接口

有时候我们需要请求第三方接口,我们有多种选择,具体选择哪个比较好,我们来看看AI智能助手的回答:

在Spring Boot中发送HTTP请求可以使用多种框架,每个框架都有其特点和适用场景。以下是几个常用的选项:

  1. Spring’s RestTemplate: Spring Boot内置支持,易于集成和使用。RestTemplate提供了丰富的方法来消费RESTful服务。它包括了多种便捷的方法来远程调用HTTP服务,并可以轻松地将响应绑定到POJOs上。但从Spring 5开始,RestTemplate被标记为不推荐使用(deprecated),Spring团队推荐使用WebClient
  2. Spring’s WebClient: WebClient是Spring 5中引入的,它是一个更现代的、功能更全面的替代RestTemplate的选项。WebClient支持同步和异步请求,并且提供了更灵活的错误处理机制。WebClient是在Spring WebFlux中引入的,但也可以在Spring MVC应用程序中使用。
  3. OkHttp: OkHttp是一个高效的HTTP客户端,支持同步和异步请求,提供了快速的性能和广泛的功能,如连接池、GZIP压缩和响应缓存。OkHttp适用于需要精细控制HTTP通信的场景,但它不是Spring的一部分,需要单独添加依赖和配置。
  4. 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最适合

下面开始具体步骤:

  1. 引入依赖:
<!-- https://mvnrepository.***/artifact/org.springframework.boot/spring-boot-starter-webflux -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
  1. controller

未完待续…

转载请说明出处内容投诉
CSS教程_站长资源网 » 从0开始搭建jdk-21 springboot 3.1项目

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买