软工后端API复盘

返回格式

我这次软工作业选用的API格式是这样的

{
    # 返回状态码
    code: integer
    # 返回值
    data: object
}

但其实更好的话还是要写完整来:

{
    # 返回状态码
    code: integer
    # 返回信息描述
    message: string
    # 返回值
    data: object
}

先说code

code

code顾名思义,就是状态码,我在这边是这么设计的

参数名 说明
SUCCESS 0 操作成功
SYSTEM_BUSY -1 系统繁忙,此时请开发者稍候再试
MISSING_PARAMETERS 1001 缺少相关参数
FAIL_TO_UPDATE_DISH_STAR 2001 更新菜品星级失败
FAIL_TO_UPDATE_USER_INFO 2002 更新用户信息失败
FAIL_TO_UPDATE_MARKED_WINDOW 2003 更新收藏窗口失败
FAIL_TO_SAVE_DISH_STAR 3001 增添菜品星级失败
FAIL_TO_SAVE_FEEDBACK 3002 增添反馈信息失败
INVALID_CODE 40029 code 无效

你看到之后什么感觉,我反正日后看到是看都不想看,太糟糕了!

这样虽然能够照常满足业务,但状态码太凌乱了!

我们应该参考HTTP的状态码来设计,比如设计成这样:(四位数)

1000-1999 表示参数错误
2000-2999 表示用户错误
3000-3999 表示接口异常
......

这样前端开发人员在得到返回值后,根据状态码就可以知道,大概什么错误,再根据message相关的信息描述,可以快速定位
而我的设计则是按CRUD分类,是不太好的(40029是为了匹配微信小程序的code)

message

这个字段是我没有的,大概作用就是在发生错误时,友好的进行提示。

一般的设计是和code状态码一起设计,如:

//返回状态码
public enum ResultCode {
    private Integer code;
	private String message;
    
	ResultCode(Integer code, String message){
        this.code = code;
		this. message = message;
    }
	public Integer code(){
        return this code;
	}
    public String message() {
        return this.message;
    }
    /*成功状态码*/
    SUCCESS(1, "成功"),
    /*参数错误: 1001-1999 */
    PARAM_IS_INVALID(1001, "参数无效"),
    PARAM_IS_BLANK(1002, "参数为空"),
    PARAM_TYPE_BIND_ERROR(1003, "参数类型错误"),
    PARAM_NOT_COMPLETE(1004, "参数缺失"),
    /*用户错误: 2001-2999*/
    USER_NOT_LOGGED_IN(2001, "用户未登录,访问的路径需要验证,请登录"),
    USER_LOGIN_ERROR(2002, "账号不存在或密码错误"),
    USER_ACCOUNT_FORBIDDEN (2003, "账号已被禁用"),
    USER_NOT_EXIST(2004, "用户不存在"),
    USER_HAS_EXISTED (2005, "用户已存在")
}

这样状态码和信息就会一一对应,比较好维护

data

按照上面来说的话,我们整个Result的格式应该是这样:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result implements Serializable {
    private Integer code;
    private String message;
    private Object data;
}

但我是这么设计的,巧妙地用了个泛型,但是没有序列化:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class JsonObject<T> {
    private Integer code;
    private T data;
}

控制层controller

假设我们在controller层处理业务请求,并返回给前端,以order订单为例

@RestController
@RequestMapping ("/orders")
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
	@GetMapping ("{id}")
	public Result get0rder(@PathVariable("id") Integer id) {
        Order order = orderService.get0rderById(id);
        Result result = new Result(ResultCode.SUCCESS, order);
        return result;
    }
}

在获得order对象之后,我们是用的Result构造方法进行包装赋值,然后进行返回。但是构造方法这样的包装很麻烦,或许我们可以优化一下。

美观优化

我们可以在Result类中,加入静态方法

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result implements Serializable {
    // 。。。
    
    //返回成功
    public static Result success() {
        Result result = new Result();
        result.setResultCode(ResultCode.SUCCESS):
        return result;
    }
    //返回成功
    public static Result success(Obiect data) {
        Result result = new Result();
        result.setResultCode(ResultCode.SUCCESS):
        result.setData(data);
        return result;
    }
    //返回失败
    public static Result failure(ResultCode resultCode) {
        Result result = new Result():
        result.setResultCode(resultCode);
        return result;
    }
    //返回失败
    public static Result failure (ResultCode resultCode, object data) {
        Result result = new Result();
        result.setResultCode(resultCode);
        result.setData(data);
        return result;
    }
}

这时候controller就可以这么优化

@RestController
@RequestMapping ("/orders")
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
	@GetMapping ("{id}")
	public Result get0rder(@PathVariable("id") Integer id) {
        if(id == null){
            return Result.failure(ResultCode.PARAM_IS_VALID);
        }
        Order order = orderService.get0rderById(id);
        return Result.success(order);
    }
}

是不是美多了!!!虽然代码更优雅了,但是,还有问题!

  • 每个方法的返回对象都是Result封装对象,没有实际业务含义,多余
  • 在业务代码中,成功时调用Result.success(),异常错误调用Result.failure(),多余
  • 上面判断id是否为null,其实我们可以使用hibernate validate做校验,没有必要在方法体中做判断,多余

持续优化

根据奥卡姆剃刀原则,咱们该切就切!

@RestController
@RequestMapping ("/orders")
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
	@GetMapping ("{id}")
	public Order get0rder(@PathVariable("id") Integer id) {
        return orderService.get0rderById(id);
    }
}

这样很直观,而且更贴近业务了,但状态和异常要怎么做呢?

我们需要做几个事情:

  • 定义一个注解@ResponseResult,表示这个接口返回的值需要包装一下
@Retention(RUNTIME)
@Target({TYPE,METHOD})
@Documented
public @interface ResponseResult{
    
}
  • 做一个拦截器,拦截请求,判断此请求是否此请求返回的值需要包装,其实就是运行的时候,解析@ResponseResult注解
// 请求拦截器
@S1f4j
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
    // 标记名称
    public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
    
    @Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
        // 请求的方法
        if(handler instanceof HandlerMethod) {
            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            final Class<?> clazz = handlerMethod.getBeanType();
            final Method method = handlerMethod.getMethod();
            // 判断是否在类对象上面加了注解
            if(clazz.isAnnotationPresent(ResponseResult,class)){
                //设置此请求返回体,需要包装,住下传递,在ResponseBodyAdvice接口进行判断
                request.setAttribute(RESPONSE_RESULT_ANN, 						
                                      clazz.getAnnotation(ResponseResult.class));
            }else if(method.isAnnotationPresent(ResponseResult.class)){//方法体上是否有注解
                //设置此请求返回体,需要包装,住下传递,在ResponseBodyAdvice接口进行判断
                request.setAttribute(RESPONSE_RESULT_ANN, 
                                      method.getAnnotation(ResponseResult.class))
            }
        }
    	return true;
    }
}

这边就是获取此请求,是否需要返回值包装,设置一个属性标记

  • 核心就是要实现接口ResponseBodyAdvice@ControllerAdvice,判断是否需要包装返回值,如果需要,就把Controller接口的返回值进行重写。

我们接着重写返回体:

@S1f4j
@Component
public class ResponseResultHandler implements ResponseBodyAdvice<Object>{

    // 标记名称
    public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
    
	//是否请求 包含了 包装注解 标记 没有就直接返回 不再要重写返回体
    @Override
	public boolean supports(MethodParameter returnType,Class<? extends 
                                  HttpMessageConverter<?>> converterType) {
        ServletRequestAttributes sra = ((ServietRequestAttributes) 					
                                     RequestContextholder.getRequestAttributes());
        HttpServletRequest request = sra.getRequest();
        // 判断请求是否有包装标记
        ResponseResult responseResultAnn = (ResponseResult)request.getAttribute(RESPONSE_RESULT_ANN) ;
        return responseResultAnn == null ? false: true;
    }
                                
	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType 
                                  selectedContentType,Class<? extends 
                                  HttpMessageConverter<?>> selectedConverterType, 
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
		log.info("进入返回体,重写格式处理中.....");
        
        // 异常处理
        if(body instanceof ErrorResult){
            log.info("返回值 异常 做包装 处理中.....");
            ErrorResult errorResult = (ErrorResult)body;
            return Result.failure(errorResult.getCode(),errorResult.getMessage,errorResult.getErrors())
        }
        
		return Result.success(body);
    }
}

重写controller:

@RestController
@RequestMapping ("/orders")
@ResponseResult
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
	@GetMapping ("{id}")
	public Order get0rder(@PathVariable("id") Integer id) {
        return orderService.get0rderById(id);
    }
}

在controller上或者方法体上加上@ResponseResult注解,就ok拉!

还想优化?

比如,每次请求都要反射一下,获取请求的方法是否需要包装,其实这里可以做个缓存,不需要每次都做解析

posted @ 2021-01-25 23:25  王帅真  阅读(98)  评论(0编辑  收藏  举报