大家好,我是程序员大猩猩。
很多时候,我们后端开发80%的工作都是在实践业务上不停地敲代码,而有些相对来说的基础的东西却无法掌握。
举个例子:我这有一个轮子,你给我仿照一下造一个新的轮子,这对于我们有模仿、抄袭基因的人来说很容易。但是当没有这个轮子,让你新造一个轮子的时候,就没有那么简单了。
所以在我们业务需求不忙的时候,或者在忙于增删改查时,我们抽出来几分钟的时候,看看轮子到底是怎么造的,我们怎么也能自己造轮子。
相信开发对写一个Controller,想想那肯定是驾轻就熟,很简单吗?我们来看看代码:
@RequestMapping(path = "/userInfo", method = RequestMethod.POST)
public Map<String, Object> getUserInfo(@RequestBody UserInfoCnd cnd) {
return userService.inviteRegister(cnd);
}
@RequestMapping(path = "/changePwd", method = RequestMethod.POST)
public Integer changePwd(@RequestBody ChangePwdCnd cnd) {
return userService.changePwd(cnd);
}
@RequestMapping(path = "/changeInfo", method = RequestMethod.POST)
public String changeInfo(Long userId, String userName) {
try {
return userService.changeInfo(userId, userName);
} catch (Exception ex) {
log.error("error", ex);
return ex.getMessage();
}
}
我们来看看上面的代码块,它犯了哪些不优雅的说不上错误的错误呢?
一、返回格式不统一
上面三个接口分别返回了Map、Integer、还有String,Map返回value还是Object类型,根本没有可读性。
二、没有考虑错误的返回,虽然changeInfo看似在异常快捕获后,返回了错误信息字符串,但是我们想一想,这样的返回并不符合我的规则,前端对接起来,是不是很困难。要处理单独的逻辑呢?
三、参数性质不一,前面俩个方式是整体的Body传入,而后一个是属性字段传入,我们其实需要和上面二个接口方法一样,必须闯入Bean类。
四、接口传参可以进行前期判断,加入@RequestBody @Valid然后在入参Bean内传入相关的基础判断拦截。
那么我们该如何正确的书写一段Controller呢?
我们针对以上四点问题进行一一优化
一、统一返回格式
首先,我们写一个统一的返回类型Bean,支持泛型及支持中英文语言资源切换
@Data
@Component
public class ResponseData<T> implements Serializable {
private static final long serialVersionUID = -6936648847780505144L;
/**
* 狀態碼
*/
public Integer code;
/**
* 返回的消息
*/
public String message;
/**
* 返回的數據
*/
public T data;
@Autowired
private static ResponseData responseData;
/**
* 支持中英文资源获取
*/
@JsonIgnore
@Autowired
private MessageSource messageSource;
@PostConstruct
public void init() {
responseData = this;
responseData.messageSource = this.messageSource;
}
public ResponseData() {
}
public ResponseData(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
}
返回实体Bean确定后,我们来书写一下基础的方法类,表示成功与失败的返回结果体。
public static <T> ResponseData<T> success() {
return success(ResultStatusEnum.SYSTEM_SUC.getCode(), ResultStatusEnum.SYSTEM_SUC.getMsg(), null);
}
public static <T> ResponseData<T> success(T data) {
return success(ResultStatusEnum.SYSTEM_SUC.getCode(), ResultStatusEnum.SYSTEM_SUC.getMsg(), data);
}
public static <T> ResponseData<T> success(Integer code, String message, T data) {
return build(code, message, data);
}
public static <T> ResponseData<T> error(ResultStatusEnum status, String message) {
if (message == null || message.isEmpty()) {
message = status.getMsg();
}
Integer code = status.getCode();
List<String> split = Splitter.on("&").splitToList(message);
if (split.size() > 1) {
String codeInteger = split.get(0);
if (isInteger(codeInteger)) {
code = Integer.parseInt(codeInteger);
message = StrUtil.replace(message, codeInteger + "&", "");
}
}
return error(code, message, null);
}
public static <T> ResponseData<T> error(Integer code, String message) {
return error(code, message, null);
}
public static <T> ResponseData<T> error(Integer code, String message, T data) {
return build(code, message, data);
}
public static <T> ResponseData<T> build(ResultStatusEnum status) {
return build(status.getCode(), status.getMsg(), null);
}
public static <T> ResponseData<T> build(ResultStatusEnum status, T data) {
return build(status.getCode(), status.getMsg(), data);
}
public static <T> ResponseData<T> build(Integer code, String message, T data) {
return new <T>ResponseData<T>(code, getMsgValue(message), data);
}
/**
* <b>功能描述:</b>查詢國際化配置信息<br>
* <b>修訂記錄:</b><br>
* <li>20201009 | HMBB | 創建方法</li><br>
*/
private static String getMsgValue(String msg) {
if (isContainChinese(msg)) {
return msg;
}
if (msg == null || msg.isEmpty()) {
return "failed";
}
try {
Locale LOCALE = LocaleContextHolder.getLocale();
if (LOCALE.equals(Locale.ENGLISH)) {
LOCALE = Locale.US;
}
if (LOCALE.equals(Locale.CHINESE)) {
LOCALE = Locale.SIMPLIFIED_CHINESE;
}
if (LOCALE.equals(Locale.TRADITIONAL_CHINESE)) {
LOCALE = Locale.TRADITIONAL_CHINESE;
}
String value = responseData.messageSource.getMessage(msg, null, LOCALE);
if (value == null) {
return msg;
}
return value;
} catch (Exception ex) {
return msg;
}
}
返回类创建后,我们就可以在Controller类中使用统一使用ResponseData了
二、如何处理异常返回
统一异常,我们不在Controller类中使用,查看我之前的文章,可以操作。
统一异常在于我们不管在services模块,还是在内部哪里,捕获异常或者抛出异常时,所有的异常捕获都在@ControllerAdvice内。
三、参数性质不一,我们讲多个或者一个参数属性,都使用Bean包装传入即可。
四、接口传参可以进行前期判断
@RequestMapping(path = "/inviteRegister", method = RequestMethod.POST)
public ResponseData<Integer> inviteRegister(@RequestBody @Valid InviteRegisterCnd cnd) throws Exception {
return ResponseData.success(userService.inviteRegister(cnd));
}
如上接口参数内
@RequestBody @Valid InviteRegisterCnd cnd
@RequestBody 这个不必说了。
@Valid 加入这个注解参数的主要作用是用于数据校验,可以在定义的实体中的属性上,添加不同的注解来完成不同的校验规则,而在接口类中的接收数据参数中添加 @valid 注解,这时你的实体将会开启一个校验的功能。
@Data
public class InviteRegisterCnd implements Serializable {
private static final long serialVersionUID = 4061633295778293743L;
/**
* 手机号
*/
@ApiModelProperty(value = "手机号", required = true)
@NotNull(message = "手机号不可为空")
@NotBlank(message = "手机号不可为空")
@Pattern(regexp = "1[3|4|5|6|7|8|9][0-9]\\d{8}", message = "手机号码格式错误")
private String phone;
/**
* 短信验证码
*/
@ApiModelProperty(value = "短信验证码", required = true)
@NotNull(message = "短信验证码不可为空")
@NotBlank(message = "短信验证码不可为空")
private String smsCode;
}如上InviteRegisterCnd内的属性字段 @NotNull @NotBlank @Pattern的校验注解都可以起作用了。
最后,其实我们写代码用什么方法,只要功能正常就可以,这是最基础的要求。但是我们在开发工程中加入自己的思维和想法,那么我们写的代码就是有效的工作和经验。