前后端状态码定义和封装实践方案

问题背景

最近计划启动一个前后端分离的项目.创建spring boot工程.创建vue工程.不在这里细说.这里简单说一个状态码封装的问题.

web项目使用http协议.首先会想到http默认的状态码,例如200、301、401、500等.具体信息不在这里说明,网上很多白皮书.这里说一下状态码定义的一些优化实践.

实际开发过程中业务代码通常会自定义一套包括业务含义的状态码.这样就会出现两套状态码,一套是http默认的.一套是业务自定义的.这两套状态码在实际项目中如何使用呢?如果两套状态码语义上没有任何交集,那没问题,各用各的.根据业务背景取对应的状态码就可以了.所以理想状态是期望在定义业务状态码的时候避开http默认状态码的语义,http默认状态码可以满足的语义就使用http默认的. 但是实际操作中总会遇到一些特殊情况或者看起来难以划分的情况.

举一个例子:企业信息查询接口,当查询用户没有所要查询企业的查看权限.这个时候后端可能设置http默认状态码401.如果前端对于没有查询权限简单弹窗提示,感觉使用401没什么问题.但是如果业务需求复杂一些,用户没有查询权限分几种类型,不同的类型前端需要进行不同的触发动作.例如,用户不属于查询企业,前端提示用户不在查询企业内部,展示一个申请加入企业的跳转链接.如果用户在企业内部但是被公司法人限制访问,提示用户联系法人展示法人手机号.这个时候为了让前端有不一样的处理,仅仅使用401不够了.需要定义两个业务状态码401001标识不在企业内部,401002被法人禁止查询.有了业务状态码就会遇到一个问题,http默认的401还需要么?因为这个时候前端的判断逻辑可能是根据401+401001或者直接根据401001判断.我倾向于前端通过401001判断即可.这样401状态码存在与否就无所谓了.按照这个思路我们可以制定一种比较极端的方案:就是所有的后端业务处理的请求返回的http默认状态码都是200.所有的业务请求都增加一个业务状态码说明对应的业务语义. 基于上面的总结,说一个具体封装实现的方案.

后端服务实现方案(这里仅仅使用spring boot项目作为说明):

对于后端服务来说http默认的状态码只会用到200.所有业务语义都自定义一套状态码.这样很容易想到封装返回结构的时候两层结构,一层是http默认的status统一设置成200.返回的数据结构增加code变量用来标识业务语义的状态码.最简单的实现方案就是controller层封装ResponseEntity.status=HttpStatus.OK.数据对象使用调用业务方法的返回值判断.通常就是service层的返回对象封装之后写入.具体实现查看spring web的ResponseEntity.这里不细说.

这样方案为了controller层实现简单,可能要求service屏蔽所有异常,只能通过返回值来进行处理.当然要求service层屏蔽所有异常也简单,通过封装service的切面拦截所有异常.既然切面可以拦截所有异常,那放到service或者servcie的调用层(例如manage层).无论哪一层都可以.但是往往实际问题要更加复杂,例如根据异常的事务处理.如果我们所在各层增加切面拦截异常,可能由于异常被错误拦截导致事务无法触发.或者其他的异常处理被错误屏蔽.解决这个问题的简单方案就是异常拦截之后进行本层异常处理,处理之后再次抛出.所有不要异常处理的层都不做异常拦截.就是切面里面和业务代码里面的try-catche处理之后都将异常继续抛出.所有的抛出异常都根据当前的业务语义设置状态码.一个方案就是自定义异常抛出结构.所有抛出的异常都有状态码.这个可以根据实际情况来处理了.

有的时候根据异常类型判断没必要增加状态码.例如我们设计一个CanNotFindCompanyException(message).也可以设计一个通用的BusinessCompanyException(CanNotFindCompanyCode,message).我个人比较倾向于设计一个通用的.层与层之间的异常处理判断通过code.不是通过异常类型.当然了有些大的分类最好通过异常类型.例如需要事务回滚的异常和不需要事务回滚的异常.这个就看拦截异常的处理方案了.这个具体问题具体分析一下.但是我个人觉得异常类型尽量少,尽量使用code.这样我们做异常拦截处理的时候异常拦截分类比较简单.尤其是controller层统一异常拦截.既然这里提到了controller层统一异常拦截就简单说一下.controller层异常拦截一个简单实现方案是通过ControllerAdvice实现异常拦截.例如ExceptionHandler注解判断.异常类型少,这里处理的方法就少,而且大多数情况都是透传给前端,具体根据状态码的动作由前端决定.当然也可以通过过滤器和拦截器实现,直接封装返回对象.我不倾向于在过滤器或者拦截里面封装状态码.状态码通常和业务相关,拦截器和过滤器通常是和业务无关.过滤器和拦截里面处理异常的简单方案就是直接抛出.这里说一个比较特殊的例子.权限验证过滤器.验证用户是否登录系统通常在过滤器里面实现.如果请求没有权限标识(例如token)直接抛出异常.过滤器异常直接抛给servelet容器.相当于返回前端500.所以过滤器抛出异常的时候需要谨慎,抛出异常之后前端是否有合适的处理方案.大多数情况下过滤器抛出的异常前端都是直接展示message信息,不会有其他处理.本文目的是说状态的封装,这里说的很多异常处理的方式.因为通常情况异常处理会涉及到状态码的设计.有些异常不需要状态码,有些需要.所以说的有点多了.

总结一下服务端状态码的处理:

默认http的状态码(status)使用200.业务定义一套自己语义相关的状态码.状态码的传递方案是:正确的状态码通过返回值方式传递.错误的状态码通过异常抛出的方式.尽量减少异常的类型,尽量通过统一的异常携带状态码的方式抛出.各层切面尽量不要屏蔽或者修改异常类型或者状态码,最好直接抛出.controller层通过advice统一处理异常.过滤器和拦截器可以直接抛出servletException.没必要封装返回值异常.

前端服务实现方案(这里使用axios方式说明)

对于前端服务来说http请求通过axios调用请求.由于所有的业务异常返回的http status都是200,具体状态码在业务数据里面.我倾向于所有的http默认状态码的处理逻辑都放到axios拦截器里面.拦截器对于所有status是200的返回值直接将业务数据返回给调用接口.在具体封装各个业务调用接口里面解析出业务状态码code.进行具体业务处理.这里有一个问题,各个业务接口对于状态码的处理可能有重复的.也可能有业务接口特有的.这样我们可以封装统一业务状态码处理接口,统一接口里面无法处理的直接返回,再由各个业务接口独自处理.这样前端对于状态码的处理可能分为两层,三类型.两层是axios拦截器处理的http默认状态码和业务处理的业务状态码.三类型是http默认状态码类型、业务通用状态码类型和接口特有状态码类型.

相关推荐
心海资源13 分钟前
【心海资源】黄金首饰价格查询单页源码
前端·学习·开源软件
keyBird在成长14 分钟前
Java集合操作:如何避免并发修改异常
java·windows·python
2301_8187320617 分钟前
hadoop 无法存储数据到hbase里面 已经解决
java·大数据·数据库·hadoop·centos·hbase
悟能不能悟34 分钟前
Logback 在 Spring Boot 中的详细配置
java·spring boot·logback
打小就很皮...34 分钟前
《从虚拟 DOM 到 Diff 算法:深度解析前端高效更新的核心原理》-简版
前端·javascript·html
乌托邦的逃亡者35 分钟前
使用JProfiler进行Java应用性能分析
java·开发语言·性能优化
永日4567038 分钟前
java小结(一)
java·开发语言
神仙别闹39 分钟前
基于Java(SSM)+MySQL实现(Web)具有智能推荐功能的图书销售系统
java·前端·mysql
是席木木啊42 分钟前
Tomcat:部署前后端分离项目踩坑
前端·javascript·vue.js
奋斗的袍子0071 小时前
Spring Boot 拦截器:解锁5大实用场景
java·spring boot·后端·拦截器·interceptor