关于后端统一数据格式的一点总结

最近在学SpringBoot的时候,偶然又看到了统一数据格式这一块的内容,所有打算写点东西总结一下。

以前写项目的时候,代码满天飞,想返回什么就返回什么,想返回一个字符串就返回一个字符串,想返回一个对象就返回一个对象,完全不考虑别人受不受的了。然鹅在现在这种提倡前后端分离的时代,这么搞显然是不行的,不然会被同事打死滴。

举例子来看,比如注册用户的时候,验证用户的年龄是否符合条件,根据年龄给出不同的提示

// 字段验证
@Min(value = 18, message = "未成年人禁止入内")
private Integer age;
//其他字段省略

插入一条记录

    /**
	 * 添加一条记录
	 * @param man 结果集合
	 * @Validated 注解
	 * @return
	 */
	@GetMapping("/insertMan")
	public Object insertOne(@Validated Man man, BindingResult br) {
		// 如果不符合条件,则不插入
		if(br.hasErrors()) {
			return "插入失败";
		}
		man.setName(man.getName());
		man.setAge(man.getAge());
		return manDao.save(man);
	}

可以看到,如果年龄不满18岁,就返回一个字符串,否则返回插入的对象。如果你一个人包揽前后端,那随便你写,反正是你自己的事,但如果你分工合作的话,这样写显然就不太合适了,一个方法这样,那几十个上百个呢,我前端还怎么接收数据啊。

所以我们得统一下返回的格式啊,暂且先定义一个类ResultData吧

/**
 * 封装结果集成统一的json样式
 * @author liu
 */
public class ResultData<T> {
	// 信息状态码,0表示成功,1表示失败
	private Integer code;
	// 提示信息
	private String msg;
	// 返回的对象
	private T data;
    //省略set/get方法。。。。。
}

有了这么一个类,那我不可能每次要都new一个ResultData然后调用set方法设置属性后返回吧,这样肯定会有大量的重复代码。所以我们还得定义一个类去专门封装这些属性并进行返回。暂且定义为ResultUtils吧。

/**
 * 编写相应的方法,减少重复代码
 * @author liu
 */
public class ResultUtils {
	// 统一结果集对象
	private static ResultData<Object> result = new ResultData<>();
	
	/**
	 * 成功时调用的函数
	 * @param object 要返回的对象
	 * @return
	 */
	public static ResultData<Object> success(Object object) {
		result.setCode(0);
		result.setMsg("插入成功");
		result.setData(object);
		return result;
	}
	
	/**
	 * 成功时,如果没有要返回的对象,调用此方法
	 * @return
	 */
	public static ResultData<Object> success() {
		return success(null);
	}
	
	/**
	 * 失败时调用的函数
	 * @param code 要返回的状态码
	 * @param msg 要返回的信息
	 * @return
	 */
	public static ResultData<Object> fail(Integer code, String msg) {
		result.setCode(code);
		result.setMsg(msg);
		return result;
	}
}

有了这两个类,我们基本的也就实现了,修改上面插入数据的代码如下

    /**
	 * 添加一条记录
	 * @param man 结果集合
	 * @Validated 注解
	 * @return
	 */
	@GetMapping("/insertMan")
	public Object insertOne(@Validated Man man, BindingResult br) {
		// 如果不符合条件,则不插入
		if(br.hasErrors()) {
			return ResultUtils.fail(1, "插入失败");
		}
		man.setName(man.getName());
		man.setAge(man.getAge());
		return ResultUtils.success(manDao.save(man));
	}

发送请求看看返回的结果

首先插入一个年龄10岁的对象
15.png

再插入一个年龄20岁的对象
16.png
可以看到,不论插入成功或者插入失败,都返回一个统一的数据格式。

现在我们来通过id查询一条记录

    public void queryById(Integer id) throws Exception {
		Man man = manDao.findOne(id);
		if(man.getAge() < 10) {
			throw new Exception("年龄太小了,好好学习");
		} else if(man.getAge() > 10 && man.getAge() < 16) {
			throw new Exception("你也太小了,多吃点饭");
		} else {
			throw new Exception("未知错误");
		}
	}

在controller里进行调用

    /**
	 * 根据id查询记录,带有异常
	 * @param id
	 * @throws Exception
	 */
	@RequestMapping("/queryById/{id}")
	public void queryById(@PathVariable("id") Integer id) throws Exception {
		manService.queryById(id);
	}

现在我们来进行查询看看返回什么
17.png

可以看到返回了一个非常不友好的东西,这肯定不是我们想要的。

现在我们试着来定义一个自己的异常MyException

/**
 * 自定义异常,统一异常处理
 * @author liu
 */
@SuppressWarnings("serial")
public class MyException extends RuntimeException {
	// 状态码
	private Integer code;
	
	public MyException(String message, Integer code) {
		super(message);
		this.code = code;
	}

	public Integer getCode() {
		return code;
	}

	public void setCode(Integer code) {
		this.code = code;
	}
}

有了自己的异常类,我们得进行处理啊,封装数据进行返回,所以定义一个异常处理类HandlerException

**
 * 封装异常为统一json样式
 * @ControllerAdvice 注解,用于拦截全局的Controller的异常
 * @author liu
 */
@ControllerAdvice
public class HandlerException {
	
	/**
	 * @ExceptionHandler 该注解表示要处理哪些异常
	 * @ResponseBody 该注解表示返回格式为json
	 * @param e
	 * @return
	 */
	@ExceptionHandler(value = Exception.class)
	@ResponseBody
	public ResultData<Object> handler(Exception e) {
		// 如果是自定义的异常
		if(e instanceof MyException) {
			MyException exception = (MyException) e;
			return ResultUtils.fail(exception.getCode(), e.getMessage());
		} else {
			return ResultUtils.fail(-1, e.getMessage());
		}
	}
}

注意注解@ControllerAdvice,用于拦截全局的Controller的异常。

有了这两个类之后,我们就可以修改查询方法的代码如下

    public void queryById(Integer id) throws Exception {
        Man man = manDao.findOne(id);
        if(man.getAge() < 10) {
            throw new MyException("年龄太小了,好好学习", 100);
        } else if(man.getAge() > 10 && man.getAge() < 16) {
            throw new MyException("你也太小了,多吃点饭", 101);
        } else {
            throw new MyException("未知错误", -1);
        }
    }

现在我们再来看看查询返回什么
18.png

19.png
可以看到查询id为2的和查询id为10的记录,返回了一样的数据格式。

你以为到这就完事了?你还是太年轻了。看看我们在代码里写的msg,“年龄太小了,好好学习”,“你也太小了,多吃点饭”。。。如果这些信息一多起来,慢屏幕的文字,一个是显的low,另一个是不好维护,如果要修改就得修改多个地方。

这时,我们可以定义一个枚举ResultEnum

/**
 * 结果的枚举,统一进行管理,方便维护
 * @author liu
 */
public enum ResultEnum {
	UNKNOW_ERROR(-1, "未知错误"),
	INSERT_SUCCESS(0, "插入成功"),
	INSERT_FAIL(1, "插入失败"),
	TOO_YOUNG(100, "年龄太小了,好好学习"),
	YOUNG(101, "你也太小了,多吃点饭")
	;
	// 状态码
	private Integer code;
	// 信息
	private String msg;
	private ResultEnum(Integer code, String msg) {
		this.code = code;
		this.msg = msg;
	}
	public Integer getCode() {
		return code;
	}
	public void setCode(Integer code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
}

通过把程序中用到的状态码和信息存入枚举的一个对象中,方便维护。

这时可以修改上面的程序,查询对象的代码改为如下

    public void queryById(Integer id) throws Exception {
		Man man = manDao.findOne(id);
		if(man.getAge() < 10) {
			throw new MyException(ResultEnum.TOO_YOUNG);
		} else if(man.getAge() > 10 && man.getAge() < 16) {
			throw new MyException(ResultEnum.YOUNG);
		} else {
			throw new MyException(ResultEnum.UNKNOW_ERROR);
		}
	}

自定义异常的构造函数改为下面的样子,从传入的枚举对象中获取状态码和信息。

    public MyException(ResultEnum re) {
		super(re.getMsg());
		this.code = re.getCode();
	}

其他保持不变,统一数据格式的工作就基本完成了。


工作中还是要多总结多学习多思考。让代码更简洁更好维护。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值