对于2最后的问题这次做统一的改造
三. 修改接口返回值
由于每个接口的功能不同,返回值也会有所不同,如果像第二章写的那样都返回一个成功或者失败的字符串,那么很多效果都无法实现,因此我们要返回一个包含多种信息的统一返回值。有两种方式进行处理,一种是使用Map存储返回值并将其返回;另一种是使用Java对象存储返回值。我们这里使用Java对象的方式进行返回,方便管理和读取数据。
- 创建response包(在com.forum包下)用来存放所有有关返回的类(后面分页的时候也会将分页对象放到这个包中);创建CommonReturnType类,这就是统一返回对象,在后面,接口的返回结果都会将数据分装到这个类中并将其返回
CommonReturnType.java
/**
* @author Xiaoxu
* @create 2020 -10 -16 16:38
*/
public class CommonReturnType {
/**
* 用来标志次此请求的状态
* success 请求成功
* fail 请求失败
*/
private String status;
/**
* 存放返回给前端的数据
*/
private Object data;
/**
* 请求成功,直接传数据对象过来就行
* @param data 返回给前端的数据
* @return 统一返回格式
*/
public static CommonReturnType create(Object data) {
return create("success", data);
}
/**
* 请求成功或者失败都可以使用这个方法,
* 值得注意的是,项目中将会采用统一的异常处理,如果请求失败将会以异常的形式抛给前端
* 此方法可能会被弃用
* @param status 请求状态
* @param data 请求数据
* @return 统一返回格式
*/
public static CommonReturnType create(String status, Object data) {
CommonReturnType commonReturnType = new CommonReturnType();
commonReturnType.setStatus(status);
commonReturnType.setData(data);
return commonReturnType;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
- 修改UserController类,将接口方法的返回值从String改为CommonReturnType
UserController.java
import com.forum.dataobject.UserDao;
import com.forum.dataobject.UserPasswordDao;
import com.forum.response.CommonReturnType;
import com.forum.service.UserService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Administrator
* @create 2020 -10 -13 11:30
* 由于采用前后端分离的方式,返回的数据都是restFul风格所以直接使用@RestController
*/
@RestController
@CrossOrigin(origins = "*", allowCredentials = "true")
@RequestMapping("/user")
public class UserController {
@Resource
HttpServletRequest httpServletRequest;
@Resource
UserService userService;
@RequestMapping("/getValidateCode")
public CommonReturnType getValidateCode(@RequestParam("telephone") String telephone) {
// 校验手机号格式,如果格式错误直接返回错误
String regex = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(17[013678])|(18[0,5-9]))\\d{8}$";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(telephone);
boolean isMatch = m.matches();
if (!isMatch) {
return CommonReturnType.create("failed", null);
}
// 检查手机号是否以被注册
if (userService.isExist(telephone) > 0) {
return CommonReturnType.create("failed", null);
}
//生成随机验证码
Random random = new Random();
String validateCode = String.valueOf(random.nextInt(999999));
// 使用session进行存储
HttpSession session = httpServletRequest.getSession();
// 设置session过期时间
session.setMaxInactiveInterval(5 * 60);
session.setAttribute(telephone, validateCode);
return CommonReturnType.create(validateCode);
}
@RequestMapping("/register")
public CommonReturnType register(UserDao userDao, @RequestParam("password") String password, @RequestParam("validateCode") String validateCode) {
// 获取session中的验证码,进行校验
if (!httpServletRequest.getSession().getAttribute(userDao.getTelephone()).equals(validateCode)) {
return CommonReturnType.create("failed",null);
} else {
// 校验通过,删除此手机号对应的验证码
httpServletRequest.getSession().removeAttribute(userDao.getTelephone());
}
userDao.setAccount(userDao.getTelephone());
userDao.setStatus(true);
UserPasswordDao userPasswordDao = new UserPasswordDao();
userPasswordDao.setUserPassword(password);
try {
userService.insertUser(userDao, userPasswordDao);
} catch (Exception e) {
return CommonReturnType.create("failed",null);
}
// 直接返回空数据,调用的是CommonReturnType请求成功的方法,表示本次请求成功,但是没有数据
return CommonReturnType.create(null);
}
}
总结:这样我们就完成了对返回值的包装返回,但是这里还有一个问题,这里仅仅是修改了返回值类型,请求失败的原因并没有返回给前端,如果单纯的将错误原因用字符串的方式返回给前端,那么前端也无法进行分类判断,很不友好,所以需要继续改进,增加请求失败类型
四. 统一异常处理
- 创建error包(在com.forum包下),分别创建CommonError接口、BusinessException类、EmBusinessError类用来统一解决项目中产生的异常、错误。
CommonError.java
/**
* @author XiaoXu
*/
public interface CommonError {
/**
* 获取错误信息
* @return 错误信息
*/
String getErrorMsg();
/**
* 获取错误码
* @return 错误码
*/
int getErrorCode();
/**
* 设置错误信息并返回统一错误对象
* @param errorMsg 错误信息
* @return 统一错误对象
*/
CommonError setErrorMsg(String errorMsg);
}
BusinessException.java
/**
* @author Administrator
* @create 2020 -10 -16 16:51
*/
public class BusinessException extends Exception implements CommonError {
private final CommonError commonError;
public BusinessException(CommonError commonError) {
super();
this.commonError = commonError;
}
public BusinessException(CommonError commonError, String errorMsg) {
super();
this.commonError = commonError;
this.commonError.setErrorMsg(errorMsg);
}
@Override
public String getErrorMsg() {
return this.commonError.getErrorMsg();
}
@Override
public int getErrorCode() {
return this.commonError.getErrorCode();
}
@Override
public CommonError setErrorMsg(String errorMsg) {
this.commonError.setErrorMsg(errorMsg);
return this;
}
}
EmBusinessError.java
/**
* 存放项目错误信息,错误码的枚举类
* 这里也要实现CommonError,就相当于跟BusinessException是兄弟关系,
* 可以直接将此类作为参数传递给BusinessException的构造方法
* @author Xiaoxu
*/
public enum EmBusinessError implements CommonError{
// 通用类型错误,1开头
// 未知错误
UNKNOWN_ERROR(10001, "未知错误"),
// 参数错误
PARAMETER_INVALID(10002, "参数不合法"),
// 定义用户相关的错误,2开头
// 用户不存在
USER_NOT_EXIST(20001, "用户不存在"),
// 登录错误
USER_LOGIN_ERROR(20002, "手机号不存在或密码错误"),
// 用户未登录
USER_NOT_LOGIN(20003, "用户未登录"),
// 请求用户与登录用户不一致
LOGIN_USER_DIFFERENT(20004, "请求用户与登录用户不一致,请重新登录"),
// 定义文件类错误,3开头
// 文件上传错误
FILE_UPLOAD_ERROR(30001, "文件上传失败!")
;
/**
* 错误码不可变,因为需要通过调用枚举来确定错误类型,实际上错误码就相当于错误类型
*/
private final int errorCode;
/**
* 错误信息是可以变得
*/
private String errorMsg;
EmBusinessError(int errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
@Override
public String getErrorMsg() {
return this.errorMsg;
}
@Override
public int getErrorCode() {
return this.errorCode;
}
@Override
public CommonError setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
return this;
}
}
说明:统一异常错误类型的好处是我们能够自定义所有的错误类型并统一返回,前端可以根据错误类型自定义错误提示。大多数代码都有注释,如有疑问可以再交流。
- 添加handlerException方法统一解决没有被处理的异常,在UserController中添加handlerException方法
/**
* 可以通过面向对象的方式将处理方式封装到一个类中,让controller去继承该类以达到同样的效果
* 定义exceptionHandler解决未被controller层吸收的exception
* 通过捕获异常并将状态码设置为200的方式来骗过浏览器,从而达到设置自定义错误页面的目的
* 但是使用handler的方式仅仅只能返回一个路径,那么加入@ResponseBody注解就可以了
* @param e 参数类型
* @return 返回统一对象
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public CommonReturnType handlerException(Exception e) {
// 创建一个map用来存储错误信息
Map<String, Object> map = new HashMap<>(2);
// 判断异常类型是否是我们自定的异常类型
if (e instanceof BusinessException) {
BusinessException businessException = (BusinessException) e;
map.put("errorCode", businessException.getErrorCode());
map.put("errorMsg", businessException.getErrorMsg());
} else {
map.put("errorCode", EmBusinessError.UNKNOWN_ERROR.getErrorCode());
map.put("errorMsg", EmBusinessError.UNKNOWN_ERROR.getErrorMsg());
}
return CommonReturnType.create("failed", map);
}
说明:Springboot中内置了统一异常处理,就是通过@ExceptionHandler这个注解来实现,使用这个注解的方法就会被用来解决未处理的异常。@ExceptionHandler(Exception.class)表示这个方法能够处理的异常类型是Exception,这个是最大的异常类型,为了简单起见就直接使用Exception类。@ResponseStatus(HttpStatus.OK)表示这个方法处理完异常之后返回给前端的状态码为HttpStatus.OK(200)表示请求成功。
最后的改造由于我们目前只有UserController这一个控制器,所以把handlerException直接放到了此类中,后面还会有各种控制器,那么如果每个控制器都写handlerException方法就会造成代码冗余,所以最后将这个方法提取成一个类BaseController,让每个控制器去继承这个类就可以了,并且后面也会用到相同的一些常量,也可以直接定义在BaseController中。
BaseController.java
import com.forum.error.BusinessException;
import com.forum.error.EmBusinessError;
import com.forum.response.CommonReturnType;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.HashMap;
import java.util.Map;
/**
* @author Administrator
* @create 2020 -10 -20 8:58
*/
public class BaseController {
/**
* 可以通过面向对象的方式将处理方式封装到一个类中,让controller去继承该类以达到同样的效果
* 定义exceptionHandler解决未被controller层吸收的exception
* 通过捕获异常并将状态码设置为200的方式来骗过浏览器,从而达到设置自定义错误页面的目的
* 但是使用handler的方式仅仅只能返回一个路径,那么加入@ResponseBody注解就可以了
* @param e 参数类型
* @return 返回统一对象
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public CommonReturnType handlerException(Exception e) {
// 创建一个map用来存储错误信息
Map<String, Object> map = new HashMap<>(2);
// 判断异常类型是否是我们自定的异常类型
if (e instanceof BusinessException) {
BusinessException businessException = (BusinessException) e;
map.put("errorCode", businessException.getErrorCode());
map.put("errorMsg", businessException.getErrorMsg());
} else {
map.put("errorCode", EmBusinessError.UNKNOWN_ERROR.getErrorCode());
map.put("errorMsg", EmBusinessError.UNKNOWN_ERROR.getErrorMsg());
}
return CommonReturnType.create("failed", map);
}
}
最后记得使用extends关键字来继承BaseController!
五. 修改请求失败的情况,使其返回我们自定义的错误类型
将所有直接返回"failed"的地方都修改成我们自定义的错误类型并将错误抛出去让handlerException来统一处理。修改完的UserController:
import com.forum.dataobject.UserDao;
import com.forum.dataobject.UserPasswordDao;
import com.forum.error.BusinessException;
import com.forum.error.EmBusinessError;
import com.forum.response.CommonReturnType;
import com.forum.service.UserService;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Administrator
* @create 2020 -10 -13 11:30
* 由于采用前后端分离的方式,返回的数据都是restFul风格所以直接使用@RestController
*/
@RestController
@CrossOrigin(origins = "*", allowCredentials = "true")
@RequestMapping("/user")
public class UserController extends BaseController{
@Resource
HttpServletRequest httpServletRequest;
@Resource
UserService userService;
@RequestMapping("/getValidateCode")
public CommonReturnType getValidateCode(@RequestParam("telephone") String telephone) throws BusinessException{
// 校验手机号格式,如果格式错误直接返回错误
String regex = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(17[013678])|(18[0,5-9]))\\d{8}$";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(telephone);
boolean isMatch = m.matches();
if (!isMatch) {
throw new BusinessException(EmBusinessError.PARAMETER_INVALID, "手机号格式错误!");
}
// 检查手机号是否以被注册
if (userService.isExist(telephone) > 0) {
throw new BusinessException(EmBusinessError.PARAMETER_INVALID, "手机号已被注册!请直接登录");
}
//生成随机验证码
Random random = new Random();
String validateCode = String.valueOf(random.nextInt(999999));
// 使用session进行存储
HttpSession session = httpServletRequest.getSession();
// 设置session过期时间
session.setMaxInactiveInterval(5 * 60);
session.setAttribute(telephone, validateCode);
return CommonReturnType.create(validateCode);
}
@RequestMapping("/register")
public CommonReturnType register(UserDao userDao,
@RequestParam("password") String password,
@RequestParam("validateCode") String validateCode)
throws BusinessException, NoSuchAlgorithmException {
// 获取session中的验证码,进行校验
String code = (String) httpServletRequest.getSession().getAttribute(userDao.getTelephone());
if(code == null) {
throw new BusinessException(EmBusinessError.USER_REGISTER_ERROR, "验证码错误,请重新获取验证码!");
}
if (!code.equals(validateCode)) {
throw new BusinessException(EmBusinessError.PARAMETER_INVALID, "验证码错误!");
} else {
// 校验通过,删除此手机号对应的验证码
httpServletRequest.getSession().removeAttribute(userDao.getTelephone());
}
userDao.setAccount(userDao.getTelephone());
userDao.setStatus((byte) 1);
UserPasswordDao userPasswordDao = new UserPasswordDao();
userPasswordDao.setUserPassword(encodeByMd5(password));
try {
userService.insertUser(userDao, userPasswordDao);
} catch (Exception e) {
throw new BusinessException(EmBusinessError.USER_REGISTER_ERROR);
}
// 直接返回空数据,调用的是CommonReturnType请求成功的方法,表示本次请求成功,但是没有数据
return CommonReturnType.create(null);
}
private String encodeByMd5(String str) throws NoSuchAlgorithmException {
// 使用MessageDigest获取Md5实例
MessageDigest md5 = MessageDigest.getInstance("MD5");
// 使用Base64Encoder进行加迷
BASE64Encoder base64 = new BASE64Encoder();
return base64.encode(md5.digest(str.getBytes()));
}
说明:为了安全,这里也将用户密码使用md5的方式进行了加密处理













网友评论