逗号的博客

这里有一句格言,但我还没想好

我们将要实现这样一个微服务系统,将 Spring Cloud Gateway 作为 OAuth2 的资源服务器,在 Gateway 实现集中的统一的鉴权功能,各个微服务之间的调用不再单独鉴权。系统的规划如下表所示

系统 端口 说明
example-auth 9090 认证与授权服务
example-eureka 8761 服务注册中心
example-gateway 8080 服务网关
example-user 8081 用户服务
example-product 8082 商品服务
example-order 8083 订单服务
example-common - 公共模块

example-auth 是一个独立的服务,它不会向服务注册中心注册自己,其他的服务,包括 example-gateway,均需要向服务注册中心注册自己。

阅读全文 »

Spring Security OAuth 的生命周期已经结束,官方已经删除它的文档,现在推荐使用 Spring Authorization Server,但是还是有很多老项目在继续使用 Spring Security OAuth,学习它仍然是必要的。虽然官方文档已经删除了,但是找到了一篇较早时间的翻译 Spring Security OAuth2 开发指南,可以作为实践的基础。网络上也有很多如何使用它的文章,这篇文章是自己实践的一点记录。

阅读全文 »

备注:Knife4j 通过 com.github.xiaoymin.knife4j.spring.extension.Knife4jOpenApiCustomizer 类的 addOrderExtension 方法给 Tag 类应用 @ApiSupport 注解的 order 属性。

问题描述

在我们的一个项目中使用了 Knife4j 来显示 Swagger 的文档,具体的依赖如下

1
2
3
4
5
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>

同时我们有两个 Controller,它们分别是

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理")
public class UserController {
// 省略了...
}

@RestController
@RequestMapping("/role")
@Tag(name = "角色管理")
public class RoleController {
// 省略了...
}

默认情况下,在 Knife4j 的页面中“角色管理”显示在了“用户管理”的前面。我们期望“用户管理”显示在“角色管理”的前面。为了达到这个目的我们使用了 Knife4j 提供的注解 @ApiSupport,修改后的代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理")
@ApiSupport(order = 1)
public class UserController {
// 省略了...
}

@RestController
@RequestMapping("/role")
@Tag(name = "角色管理")
@ApiSupport(order = 2)
public class RoleController {
// 省略了...
}

但是并没有什么效果,“用户管理”并没有显示在“角色管理”的前面,即 @ApiSupport 注解没有生效。

阅读全文 »

问题描述

我们有一个项目使用了 Knife4j,依赖的版本为

1
2
3
4
5
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>

这个版本的 Knife4j 使用了 SpringDoc,其获取 Swagger 配置的地址为 /v3/api-docs/swagger-config,获取文档的地址为 /v3/api-docs,如果项目有多个分组,获取单个分组,比如分组“1-动物”,的文档的地址为 /v3/api-docs/1-动物

在项目部署时在项目的前面放置了一台 Nginx 反向代理服务器,配置了当访问路径为 /example 时会将请求转发到我们自己的项目

1
2
3
location /example/ {
proxy_pass http://localhost:8080/;
}

当访问 http://localhost/example/doc.html 时,Knife4j 能正常的访问 Swagger 的配置,即 http://localhost/example/v3/api-docs/swagger-config,但是无法访问 http://localhost/v3/api-docs/1-动物,因此无法打开 Knife4j 文档页面。

阅读全文 »

问题描述

首先创建一个 Spring Boot 项目,然后创建一个实体类 User,它有两个字段姓名 name 和生日 birthday,其中 birthday 的类型是 OffsetDataTime

1
2
3
4
5
@Data
public class User {
private String name;
private OffsetDateTime birthday;
}

最后创建对应的控制器 UserController,GET 请求的第二种形式将参数封装成了对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/query1")
public OffsetDateTime query1(OffsetDateTime birthday) {
return birthday;
}

@GetMapping("/query2")
public User query2(User user) {
return user;
}

@PostMapping("/create")
public User create(@RequestBody User user) {
return user;
}
}

我们要实现接收前端传递过来的形如 yyyy-MM-dd HH:mm:ss 的字符串并自动将它转换为能够使用 OffsetDateTime 类型。

阅读全文 »

问题描述

在 SpringBoot 应用中可以使用以下几种方式来控制是否返回值为空的字段

  1. 在配置文件 application.yml 中配置全局自动忽略
    1
    2
    3
    spring:
    jackson:
    default-property-inclusion: NON_NULL
  2. 在类或字段上添加注解 @JsonInclude(JsonInclude.Include.NON_NULL)
  3. 实现一个 Jackson2ObjectMapperBuilderCustomizer 定制器
    1
    2
    3
    4
    5
    6
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    return jacksonObjectMapperBuilder -> {
    jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);
    };
    }
  4. 参考 JacksonObjectMapperConfiguration 自定义一个 ObjectMapper,覆盖默认的定义
    1
    2
    3
    4
    5
    6
    7
    8
    @Bean
    @Primary
    @ConditionalOnMissingBean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
    ObjectMapper mapper = builder.createXmlMapper(false).build();
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    return mapper;
    }

这 4 种方式要么是全局的要么是局部的,它们都是一次性的,设置后就不能改变,每次请求都会返回相同的结果。现在我们要实现根据请求参数中是否包含某个值动态的设置是否返回值为空的字段,具体的说我们要根据请求头中是否包含 X-Include-Non-Null 来控制是否返回值为空的字段。

阅读全文 »

问题描述

在上一篇文章中我们通过继承 GenericResponseService 类并重写 build 方法和实现 OperationCustomizer 接口的方式实现了 SpringDoc 的统一应答类型处理,但是实现的方式有点不够好,侵入 SpringDoc 的流程比较多,是否有更好的实现方式呢?

阅读全文 »

问题描述

在日常开发中我们会定义类似下面的 Result 类来统一接口返回结果的结构

1
2
3
4
5
6
7
8
9
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;

private Integer code;

private String message;

private T data;
}

此时 Controller 中的方法就会写成这个样子

1
2
3
4
5
@GetMapping("/detail1/{userId}")
public Result<User> detail1(@PathVariable(name = "userId") Long userId) {
User user = ....
return Result.<User>builder().data(user).build();
}

在每个方法上都写上类似 Result<User> 这样的代码还是比较繁琐的,我们期望写成 User 这样简单的形式

1
2
3
4
5
@GetMapping("/detail2/{userId}")
public User detail2(@PathVariable(name = "userId") Long userId) {
User user = ....
return user;
}

而由框架去处理返回结果的包装。此时就会使用到 Spring 的 ResponseBodyAdvice 接口

1
2
3
4
@RestControllerAdvice
public class ResultResponseBodyAdvice implements ResponseBodyAdvice<Object> {
// ...
}

同时我们也会使用 SpringDoc 对外提供可访问的文档。此时问题出现了,我们发现在 Swagger 的文档中方法 detail1 和方法 detail2 对应的 Schema 不一样

我们期望 detail2 展示的结果和 detail1 一样。

阅读全文 »
0%