痛点:一个接口,N套VO
作为后端开发,你一定遇到过这样的场景:
同一个用户列表接口,不同角色看到的字段完全不一样。
管理员能看到用户的手机号、薪资等敏感信息,普通用户只能看到昵称和头像。
于是,代码变成了这样:
// 管理员看到的
{"id": 1, "name": "张三", "phone": "13800138000", "salary": 15000}
// 普通用户看到的
{"id": 1, "name": "张三"}
你开始写多套VO:AdminUserVO、NormalUserVO、PublicUserVO......
三个角色还好,五个角色就完全失控了。每个接口都要写if-else判断返回哪个VO,代码臃肿不堪,维护成本飙升。
解决方案:@JsonView 一劳永逸
其实Spring Boot的Jackson库早就为我们准备好了解决方案------@JsonView注解。
第一步:定义视图接口
public class Views {
// 最基础的公开信息
public interface Public {}
// 普通用户:包含公开信息 + 普通用户可见字段
public interface Normal extends Public {}
// 管理员:包含所有字段
public interface Admin extends Normal {}
}
通过继承关系,Admin自动拥有Normal和Public的所有字段,Normal自动拥有Public的所有字段。
第二步:实体类标注视图
public class UserVO {
@JsonView(Views.Public.class)
private Long id;
@JsonView(Views.Public.class)
private String name;
@JsonView(Views.Normal.class)
private String phone;
@JsonView(Views.Admin.class)
private BigDecimal salary;
// 省略getter/setter
}
每个字段属于哪个视图组一目了然。
第三步:动态切换视图
关键来了------如何根据当前登录用户角色动态切换视图?
@RestControllerAdvice
public class JsonViewAdvice extends AbstractMappingJacksonResponseBodyAdvice {
@Override
protected void beforeBodyWriteInternal(MappingJacksonValue body,
MediaType contentType, MethodParameter returnType,
ServerHttpRequest request, ServerHttpResponse response) {
// 根据当前用户角色动态设置视图
Class<?> view = resolveViewByRole();
if (view != null) {
body.setSerializationView(view);
}
}
private Class<?> resolveViewByRole() {
LoginUser user = SecurityUtils.getLoginUser();
if (user == null) return Views.Public.class;
if (user.isAdmin()) return Views.Admin.class;
return Views.Normal.class;
}
}
这样一来,Controller层完全不用改动:
@GetMapping("/list")
public R<List<UserVO>> list() {
return R.ok(userService.list());
}
同一个接口,管理员请求返回全部字段,普通用户请求只返回公开字段。
进阶技巧
1. 方法级别的视图覆盖
如果某个接口需要特殊处理,可以直接在方法上指定视图:
@GetMapping("/detail")
@JsonView(Views.Admin.class)
public R<UserVO> detail(@PathVariable Long id) {
return R.ok(userService.getById(id));
}
方法上的@JsonView会覆盖全局拦截器的设置。
2. 嵌套对象的视图控制
如果VO中有嵌套对象,同样可以标注视图:
public class OrderVO {
@JsonView(Views.Public.class)
private Long orderId;
@JsonView(Views.Normal.class)
private UserVO user; // UserVO中的字段也会受视图控制
}
优缺点分析
优点
-
代码复用:一套VO、一套Controller,告别多套VO的维护噩梦
-
扩展简单 :新增角色只需在
resolveViewByRole()中添加一个分支 -
声明式编程:字段归属清晰,一目了然
缺点
-
继承层次控制:角色层次太多时,视图接口的继承树会变得复杂,建议控制在4层以内
-
细粒度不足 :如果需求是"A角色看phone,B角色看email,C角色两个都看",
@JsonView就力不从心了
适合你的场景吗?
如果你的项目满足以下条件,@JsonView方案非常适合你:
-
角色之间有清晰的层级关系(Admin → Manager → Normal → Public)
-
每个角色的字段是上一层级的超集
-
角色数量在5个以内
如果你的场景是:
-
角色之间字段互有交叉,没有清晰的层级关系
-
需要精细化控制每个字段对每个角色的可见性
-
角色数量超过10个
那么建议考虑自定义注解 + 序列化器的方案,在序列化时逐字段判断权限。
总结
@JsonView是处理多角色字段返回的优雅方案,它让代码更简洁、更易维护。核心思路是:
-
定义视图接口:通过继承表达角色层级
-
标注字段归属 :用
@JsonView标记每个字段属于哪个视图 -
动态切换视图:通过ResponseBodyAdvice拦截响应,根据当前用户角色动态设置序列化视图
下次遇到多角色字段返回的需求,不妨试试这个方案,让你的代码更加优雅。