统一返回对象——学习开源项目的封装

统一返回对象——学习开源项目的封装

Scroll Down

开源项目学习——统一返回对象

1.学习路径

学习的开源项目lin-cms地址:http://sleeve.7yue.pro/#/login
github地址:https://github.com/TaleLin/lin-cms-spring-boot

2.统一返回对象的意义

在前后台分离开发中,后台统一返回对象有固定的格式和内容形式,用于与前台进行交互,例如:

格式:
{
    "code": 2, //业务状态码处理
    "message": "密码修改成功",//请求信息
    "request": "POST /TestDto"//请求方式/路径
}

内容形式:经常为json
同时resultful风格的接口,也适用于多端的交互,比如微信小程序的后台开发等

3.统一返回对象——一般封装

对于业务不是很复制的系统,我们的封装对象不需要太复杂,例如:

package life.xp.jinglongeat.common.utils;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
import life.xp.jinglongeat.common.eunms.ErrorCodeEnum;

/*
*@Description @Description: 自定义响应数据结构
 * 				本类可提供给 H5/ios/安卓/公众号/小程序 使用
 * 				前端接受此类数据(json object)后,可自行根据业务去实现相关功能
 *  小程序-->ErrorCodeEnum
 *
*@Param
*@return 
**/
public class JSONResult {

    // 定义jackson对象
    private static final ObjectMapper MAPPER = new ObjectMapper();

    // 响应业务状态
    private Integer status;

    // 响应消息
    private String msg;

    // 响应中的数据
    private Object data;
    
    @JsonIgnore
    private String ok;	// 不使用

    public static JSONResult build(Integer status, String msg, Object data) {
        return new JSONResult(status, msg, data);
    }

    public static JSONResult build(Integer status, String msg, Object data, String ok) {
        return new JSONResult(status, msg, data, ok);
    }
    
    public static JSONResult ok(Object data) {
        return new JSONResult(ErrorCodeEnum.ERROR_CODE_0.type,ErrorCodeEnum.ERROR_CODE_0.value,data);
    }

    public static JSONResult ok() {
        return new JSONResult(null);
    }

    public static JSONResult error(ErrorCodeEnum errorCodeEnum) {
        return new JSONResult(errorCodeEnum.type, errorCodeEnum.value, null);
    }

    public static JSONResult errorMsg(String msg) {
        return new JSONResult(500, msg, null);
    }
    
    public static JSONResult errorMap(Object data) {
        return new JSONResult(501, "error", data);
    }
    
    public static JSONResult errorTokenMsg(String msg) {
        return new JSONResult(502, msg, null);
    }
    
    public static JSONResult errorException(String msg) {
        return new JSONResult(555, msg, null);
    }
    
    public static JSONResult errorUserQQ(String msg) {
        return new JSONResult(556, msg, null);
    }

    public JSONResult() {

    }

    public JSONResult(Integer status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }
    
    public JSONResult(Integer status, String msg, Object data, String ok) {
        this.status = status;
        this.msg = msg;
        this.data = data;
        this.ok = ok;
    }

    public JSONResult(Object data) {
        this.status = 200;
        this.msg = "OK";
        this.data = data;
    }

    public Boolean isOK() {
        return this.status == 200;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

	public String getOk() {
		return ok;
	}

	public void setOk(String ok) {
		this.ok = ok;
	}

}

使用:

    @RequestMapping("/hello")
    public JSONResult hello(){
        return JSONResult.error(ErrorCodeEnum.ERROR_CODE_50008);
    }

4.统一返回对象——复杂封装

参看lin-cms的统一对象封装:

1.结果码封装

提供一个code.properties,统一管理code码

# 消息码配置文件
# 格式:消息码 -> 消息
# 若消息码被注释,则表示该消息码默认已经使用,贴在这希望告诉开发者注意
# 若无特殊需求,不要在配置文件中启用它,否则内置的异常信息将会被覆盖
# lin.cms.code-message[0]=成功
# lin.cms.code-message[1]=创建成功
lin.cms.code-message[2]=密码修改成功
lin.cms.code-message[3]=删除用户成功
lin.cms.code-message[4]=更新用户成功
lin.cms.code-message[5]=更新分组成功
lin.cms.code-message[6]=删除分组成功
lin.cms.code-message[7]=添加权限成功
lin.cms.code-message[8]=删除权限成功
lin.cms.code-message[9]=注册成功
lin.cms.code-message[10]=新建图书成功
lin.cms.code-message[11]=更新图书成功
lin.cms.code-message[12]=删除图书成功
lin.cms.code-message[13]=新建分组成功
# lin.cms.code-message[9999]=服务器未知错误
# lin.cms.code-message[10000]=认证失败
lin.cms.code-message[10001]=权限不足
# lin.cms.code-message[10010]=授权失败
lin.cms.code-message[10011]=更新密码失败
lin.cms.code-message[10012]=请传入认证头字段
lin.cms.code-message[10013]=认证头字段解析失败
# lin.cms.code-message[10020]=资源不存在
lin.cms.code-message[10021]=用户不存在
lin.cms.code-message[10022]=未找到相关书籍
lin.cms.code-message[10023]=分组不存在,无法新建用户
lin.cms.code-message[10024]=分组不存在
lin.cms.code-message[10025]=找不到相应的视图处理器
lin.cms.code-message[10026]=未找到文件
# lin.cms.code-message[10030]=参数错误
lin.cms.code-message[10031]=用户名或密码错误
lin.cms.code-message[10032]=请输入正确的密码
# lin.cms.code-message[10040]=令牌失效
lin.cms.code-message[10041]=令牌损坏,解析失败
# lin.cms.code-message[10050]=令牌过期
lin.cms.code-message[10051]=令牌过期
# lin.cms.code-message[10060]=字段重复
# lin.cms.code-message[10070]=禁止操作
lin.cms.code-message[10071]=已经有用户使用了该名称,请重新输入新的用户名
lin.cms.code-message[10072]=分组名已被使用,请重新填入新的分组名
lin.cms.code-message[10073]=root分组不可添加用户
lin.cms.code-message[10074]=root分组不可删除
lin.cms.code-message[10075]=guest分组不可删除
lin.cms.code-message[10076]=邮箱已被使用,请重新填入新的邮箱
lin.cms.code-message[10077]=不可将用户分配给不存在的分组
# lin.cms.code-message[10080]=请求方法不允许
# lin.cms.code-message[10100]=刷新令牌获取失败
# lin.cms.code-message[10110]=文件体积过大
# lin.cms.code-message[10120]=文件数量过多
lin.cms.code-message[10121]=文件太多,文件总数不可超过${lin.cms.file.nums}
# lin.cms.code-message[10130]=文件扩展名不符合规范
# lin.cms.code-message[10140]=请求过于频繁,请稍后重试
lin.cms.code-message[10150]=丢失参数:
lin.cms.code-message[10160]=类型错误
lin.cms.code-message[10170]=请求体不可为空
lin.cms.code-message[10180]=全部文件大小不能超过
lin.cms.code-message[10190]=读取文件数据失败
# lin.cms.code-message[10200]=失败

使用一个CodeConfig读取code.properties,然后封装到map中

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
@ConfigurationProperties(prefix = "lin.cms")
@PropertySource(value = "classpath:code.properties", encoding = "UTF-8")
public class CodeConfig {

    private static Map<Integer, String> codeMessage = new HashMap<>();

    public static String getMessage(Integer code) {
        return codeMessage.get(code);
    }

    public Map<Integer, String> getCodeMessage() {
        return codeMessage;
    }

    public void setCodeMessage(Map<Integer, String> codeMessage) {
        CodeConfig.codeMessage = codeMessage;
    }
}

/**
 * 消息码
 */
public enum Code {

    SUCCESS(0, "OK", "成功"),

    CREATED(1, "Created", "创建成功"),

    FAIL(10200, "Failed", "失败"),

    UN_AUTHORIZATION(10000, "Authorization Failed", "认证失败"),

    UN_AUTHENTICATION(10010, "Authentication Failed", "授权失败"),

    NOT_FOUND(10020, "Not Found", "资源不存在"),

    PARAMETER_ERROR(10030, "Parameters Error", "参数错误"),

    TOKEN_INVALID(10040, "Token Invalid", "令牌失效"),

    TOKEN_EXPIRED(10050, "Token Expired", "令牌过期"),

    REPEAT(10060, "Repeat", "字段重复"),

    INTERNAL_SERVER_ERROR(9999, "Internal Server Error", "服务器未知错误"),

    FORBIDDEN(10070, "Forbidden", "禁止操作"),

    METHOD_NOT_ALLOWED(10080, "Method Not Allowed", "请求方法不允许"),

    REFRESH_FAILED(10100, "Get Refresh Token Failed", "刷新令牌获取失败"),

    FILE_TOO_LARGE(10110, "File Too Large", "文件体积过大"),

    FILE_TOO_MANY(10120, "File Too Many", "文件数量过多"),

    FILE_EXTENSION(10130, "File Extension Not Allowed", "文件扩展名不符合规范"),

    REQUEST_LIMIT(10140, "Too Many Requests", "请求过于频繁,请稍后重试");

    private int code;

    private String description;

    private String zhDescription;

    Code(int code, String description, String zhDescription) {
        this.code = code;
        this.description = description;
        this.zhDescription = zhDescription;
    }

    public int getCode() {
        return code;
    }

    public String getDescription() {
        return description;
    }

    public String getZhDescription() {
        return zhDescription;
    }
}

2.统一返回对象的封装

UnifyResponseVO返回对象

/**
 * 统一API响应结果封装
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UnifyResponseVO<T> {

    private int code;

    private T message;

    private String request;
}

ResponseUtil:响应结果生成工具,提供了几种返回值对象方法:传入对象;传入code码对象;直接输入code码值


import life.xp.jinglongeat.base.impl.Created;
import life.xp.jinglongeat.base.impl.Success;
import life.xp.jinglongeat.beans.Code;
import life.xp.jinglongeat.exception.HttpException;
import life.xp.jinglongeat.vo.UnifyResponseVO;
import lombok.extern.slf4j.Slf4j;


/**
 * 响应结果生成工具
 */
@Slf4j
public class ResponseUtil {

    public static UnifyResponseVO generateUnifyResponse(HttpException e) {
        return UnifyResponseVO.builder()
                .message(e.getMessage())
                .code(e.getCode())
                .request(RequestUtil.getSimpleRequest())
                .build();
    }

    public static <T> UnifyResponseVO<T> generateSuccessResponse(T data) {
        Success success = new Success();
        return (UnifyResponseVO<T>) UnifyResponseVO.builder()
                .message(data)
                .code(success.getCode())
                .request(RequestUtil.getSimpleRequest())
                .build();
    }

    public static <T> UnifyResponseVO<T> generateUnifyResponse(int errorCode) {
        return (UnifyResponseVO<T>) UnifyResponseVO.builder()
                .code(errorCode)
                .request(RequestUtil.getSimpleRequest())
                .build();
    }

    public static <T> UnifyResponseVO<T> generateCreatedResponse(T data) {
        Created created = new Created();
        return (UnifyResponseVO<T>) UnifyResponseVO.builder()
                .message(data)
                .code(created.getCode())
                .request(RequestUtil.getSimpleRequest())
                .build();
    }

    public static <T> UnifyResponseVO<T> generateUnifyResponse(Code code, int httpCode) {
        return (UnifyResponseVO<T>) UnifyResponseVO.builder()
                .code(code.getCode())
                .message(code.getDescription())
                .request(RequestUtil.getSimpleRequest())
                .build();
    }
}

其中用到的对象:

1.BaseResponse 返回值接口


/**
 * 基础的返回接口
 * 必须实现以下三个方法
 */
public interface BaseResponse {

    /**
     * 返回的信息
     *
     * @return 返回的信息
     */
    String getMessage();

    /**
     * http 状态码
     *
     * @return http 状态码
     */
    int getHttpCode();

    /**
     * 错误码
     *
     * @return 错误码
     */
    int getCode();
}

2.Success 成功对象


import life.xp.jinglongeat.base.BaseResponse;
import life.xp.jinglongeat.beans.Code;
import lombok.Getter;
import org.springframework.http.HttpStatus;

/**
 * 成功响应
 */
public class Success implements BaseResponse {

    @Getter
    protected String message = Code.SUCCESS.getDescription();

    @Getter
    protected int code = Code.SUCCESS.getCode();

    @Getter
    protected int httpCode = HttpStatus.OK.value();


    public Success(String message) {
        this.message = message;
    }

    public Success() {
    }
}

3.Created响应对象


import life.xp.jinglongeat.base.BaseResponse;
import life.xp.jinglongeat.beans.Code;
import lombok.Getter;
import org.springframework.http.HttpStatus;

/**
 * 新建响应
 */
public class Created implements BaseResponse {

    @Getter
    protected String message = Code.CREATED.getDescription();

    @Getter
    protected int code = Code.CREATED.getCode();

    @Getter
    protected int httpCode = HttpStatus.CREATED.value();


    public Created(String message) {
        this.message = message;
    }

    public Created() {
    }
}

3.使用切面对我们传入的code值进行转换为对应提示内容


import cn.hutool.core.util.StrUtil;
import life.xp.jinglongeat.configure.CodeConfig;
import life.xp.jinglongeat.vo.UnifyResponseVO;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;


/**
 * 处理返回结果为 UnifyResponseVO 的视图函数
 * 默认的返回均是英文,在此处通过code替换成中文
 */
@Aspect
@Component
@Slf4j
public class ResultAspect {


    @Pointcut("execution(public * life.xp.jinglongeat.controller..*.*(..))")
    public void handlePlaceholder() {
    }

    @AfterReturning(returning = "ret", pointcut = "handlePlaceholder()")
    public void doAfterReturning(Object ret) throws Throwable {
        if (ret instanceof UnifyResponseVO) {
            UnifyResponseVO result = (UnifyResponseVO) ret;
            int code = result.getCode();
            String message = CodeConfig.getMessage(code);
            if (StrUtil.isNotBlank(message)) {
                result.setMessage(message);
            }
        }
    }
}

5.原理

code码的转换的时候使用了AOP进行转换,另外在UnifyResponseVO时候是用了lombok的@Builder注解,建造者模式的形式将对象创建,即在生产对象的时候会在里面创建一个静态内部类,可以调用builder()拿到对应的bulider来操作下一步,可以链式语法的形式创建对象,如下:

    public static <T> UnifyResponseVO<T> generateCreatedResponse(T data) {
        Created created = new Created();
        return (UnifyResponseVO<T>) UnifyResponseVO.builder()
                .message(data)
                .code(created.getCode())
                .request(RequestUtil.getSimpleRequest())
                .build();
    }

祝君,好梦!