JsonView:一套VO实现多角色字段返回

痛点:一个接口,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中的字段也会受视图控制
}

优缺点分析

优点

  1. 代码复用:一套VO、一套Controller,告别多套VO的维护噩梦

  2. 扩展简单 :新增角色只需在resolveViewByRole()中添加一个分支

  3. 声明式编程:字段归属清晰,一目了然

缺点

  1. 继承层次控制:角色层次太多时,视图接口的继承树会变得复杂,建议控制在4层以内

  2. 细粒度不足 :如果需求是"A角色看phone,B角色看email,C角色两个都看",@JsonView就力不从心了

适合你的场景吗?

如果你的项目满足以下条件,@JsonView方案非常适合你:

  • 角色之间有清晰的层级关系(Admin → Manager → Normal → Public)

  • 每个角色的字段是上一层级的超集

  • 角色数量在5个以内

如果你的场景是:

  • 角色之间字段互有交叉,没有清晰的层级关系

  • 需要精细化控制每个字段对每个角色的可见性

  • 角色数量超过10个

那么建议考虑自定义注解 + 序列化器的方案,在序列化时逐字段判断权限。

总结

@JsonView是处理多角色字段返回的优雅方案,它让代码更简洁、更易维护。核心思路是:

  • 定义视图接口:通过继承表达角色层级

  • 标注字段归属 :用@JsonView标记每个字段属于哪个视图

  • 动态切换视图:通过ResponseBodyAdvice拦截响应,根据当前用户角色动态设置序列化视图

下次遇到多角色字段返回的需求,不妨试试这个方案,让你的代码更加优雅。

相关推荐
睡一觉就好了。1 小时前
TcpSocket
linux
小陈运维1 小时前
使用kubeadm初始化IPV4/IPV6集群
linux·docker·云原生·容器·kubernetes
行者-全栈开发1 小时前
SpringBoot 生产环境踩坑实录:10个真实故障案例与教训
java·spring boot·后端
李福春1 小时前
斗破Google ADK Java苍穹-第7境斗皇-御天地-Harness治理
java·人工智能·python·深圳同盟·腾讯云架构师技术同盟·adk java·harness 治理
Wang's Blog1 小时前
Java 项目部署之 Docker工具快速入门: 容器命令练习:创建、改文件、重启到删除的完整闭环
java·开发语言·docker
我是人✓1 小时前
Java面向对象核心特性深度解析
java·前端·算法
Wx-bishekaifayuan1 小时前
springboot线上中医药养生平台管理系统36753-计算机课程设计、毕业设计
java·spring boot·后端·spring·课程设计·express·旅游
东风微鸣1 小时前
精简 Ubuntu Linux 固件包:每个 N100 节点省下约 450 MB
linux·ubuntu·postgresql
Wx-bishekaifayuan1 小时前
springboot大学生就业平台47691-计算机课程设计、毕业设计
java·spring boot·后端·elasticsearch·课程设计·express·旅游