前言
敲代码的时候会遇到各种各样的异常,有业务异常,有代码异常,如果都用try-catch处理,那真的是要了命了。所以,我们就想个办法来全局处理异常。下面就是一个简单的思路,实际应用的时候可以基于此优化。
一、定义统一返回数据
为了和前端交流更加方便,定义一个类用来返回前端。
@Data
public class Result<T> implements Serializable {
/**
* 错误码
*/
private Integer code;
/**
* 提示信息
*/
private String msg;
/**
* 返回的具体内容
*/
private T data;
private Result(){}
private Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> Result<T> success(){
return success(null);
}
public static <T> Result<T> success(T data){
return success("success", data);
}
public static <T> Result<T> success(String msg, T data){
return success(200, msg, data);
}
/**
* 成功时调用
*
* @param code 错误码
* @param msg 返回信息
* @param data 返回数据
* @return {@link Result<T>}
*/
public static <T> Result<T> success(Integer code, String msg, T data){
return new Result<>(code, msg, data);
}
public static Result<Object> error(String msg){
return error(500, msg, null);
}
public static Result<Object> error(Integer code, String msg){
return error(code, msg, null);
}
/**
* 失败
*
* @param code 错误码
* @param msg 错误信息
* @param cause 异常信息
* @return {@link Result}
*/
public static Result<Object> error(Integer code, String msg, Object cause){
Assert.notNull(code, "错误码不能为空");
Assert.notNull(msg, "错误信息不能为空");
msg = msg.trim();
return new Result<>(code, msg, cause);
}
}
二、自定义异常
自定义一个异常类,出现业务异常的时候可以抛出此异常
public class CustomException extends RuntimeException{
private int code;
public void setCode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public CustomException(int code, String message) {
super(message);
this.code = code;
}
}
三、全局抓取异常
使用@RestControllerAdvice注解和@ExceptionHandler注解来全局抓取异常,然后进行相应的处理,怎么处理就看自己的业务了。
@RestControllerAdvice
public class CustomExceptionHandler {
/**
* 全局异常处理
*
* @param exception 异常
* @return Object
*/
@ExceptionHandler(value = Exception.class)
public Object exceptionHandler(Exception exception) {
if (exception instanceof CustomException){
CustomException customException = (CustomException) exception;
return Result.error(customException.getCode(), customException.getMessage(), customException.getCause());
}else {
String stacktrace = ExceptionUtil.getMessage(exception);
return Result.error(stacktrace);
}
}
}
四、登录拦截
1、定义一个注解用于忽略认证
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Ignore {
}
2、定义一个认证拦截器,拦截未登录的请求
public class AuthenticationInterceptor implements HandlerInterceptor {
private static final String OPTIONS = "OPTIONS";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行options请求
if (request.getMethod().toUpperCase().equals(OPTIONS)) {
passOptions(response);
return false;
}
Method method = null;
if (handler instanceof HandlerMethod) {
method = ((HandlerMethod) handler).getMethod();
}
// 检查是否忽略权限验证
if (method == null || checkIgnore(method)) {
return true;
}
// 获取token
String accessToken = takeToken(request);
if (StrUtil.isBlank(accessToken)) {
throw new CustomException(1001, "Token不能为空");
}
// 解析token
String username = parseToken(accessToken);
HttpSession session = request.getSession();
String tokenInSession = (String) session.getAttribute(username);
if (StrUtil.isBlank(tokenInSession)){
throw new CustomException(1002, "token错误");
}
if (!StrUtil.equals(accessToken, tokenInSession)){
throw new CustomException(1002, "token错误");
}
return true;
}
/**
* 放行options请求
*/
public static void passOptions(HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, x-requested-with, X-Custom-Header, Authorization");
}
/**
* 检查是否忽略权限
*/
public static boolean checkIgnore(Method method) {
Ignore annotation = method.getAnnotation(Ignore.class);
// 方法上没有注解再检查类上面有没有注解
if (annotation == null) {
annotation = method.getDeclaringClass().getAnnotation(Ignore.class);
return annotation != null;
}
return true;
}
/**
* 取出前端传递的token
*/
public static String takeToken(HttpServletRequest request) {
String accessToken = request.getParameter("accessToken");
if (StrUtil.isBlank(accessToken)) {
accessToken = request.getHeader("Authorization");
}
return accessToken;
}
public static String parseToken(String accessToken){
String[] token = accessToken.split("--");
return token[0];
}
}
3、登录
@RestController
@RequestMapping("/api/v1/system")
public class LoginController {
@Autowired
private HttpServletRequest request;
@Ignore
@GetMapping("/login")
public Result login(String username, String password){
HttpSession session = request.getSession();
String sessionId = session.getId();
String token = username + "--" + sessionId;
session.setAttribute(username, token);
Map<String, Object> map = new HashMap<>(1);
map.put("token", token);
return Result.success(map);
}
@GetMapping("/info")
public Result getUserInfo(){
Map<String, Object> userInfo = new HashMap<>(2);
Map<String, Object> user = new HashMap<>(1);
user.put("nickname", "1234");
userInfo.put("user", user);
Set<String> roles = new HashSet<>();
roles.add("admin");
userInfo.put("roles", roles);
return Result.success(userInfo);
}
}
写在最后的话
这篇文章只是一个简单的思路,稍微修改一下就可以完成简单的认证。复杂的认证和授权可以使用springSecurity实现。