zoukankan      html  css  js  c++  java
  • 软工后端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拉!

    还想优化?

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

  • 相关阅读:
    BZOJ 1002 轮状病毒
    poj_1952最大下降子序列,统计个数
    poj_3468线段树成段更新求区间和
    hdu_4707
    uva_644暴力加字典树解法
    正则表达式:处理文本内容中特定的字符串
    grep:文本搜索工具
    分析文本的工具:wc,sort,uniq,diff和patch
    按列抽取文本cut和合并文件paste
    显示文本前或后行内容:head,tail
  • 原文地址:https://www.cnblogs.com/qizong007/p/14328045.html
Copyright © 2011-2022 走看看