diff --git a/pom.xml b/pom.xml index 5bb6512..a178321 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,11 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + diff --git a/src/main/java/org/anyin/gitee/shiro/advisor/ApiMessageAdvisor.java b/src/main/java/org/anyin/gitee/shiro/advisor/ApiMessageAdvisor.java new file mode 100644 index 0000000..e50bc04 --- /dev/null +++ b/src/main/java/org/anyin/gitee/shiro/advisor/ApiMessageAdvisor.java @@ -0,0 +1,150 @@ +package org.anyin.gitee.shiro.advisor; + +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.anyin.gitee.shiro.base.BusinessCodeEnum; +import org.anyin.gitee.shiro.base.BusinessException; +import org.anyin.gitee.shiro.base.Response; +import org.anyin.gitee.shiro.utils.RequestIdUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.MDC; +import org.springframework.core.annotation.Order; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.commons.CommonsMultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Optional; +import java.util.UUID; + +@Aspect +@Order +@Slf4j +public class ApiMessageAdvisor { + + + @Around("execution(public * org.anyin.gitee.shiro.controller..*Controller.*(..))") + public Object invokeAPI(ProceedingJoinPoint pjp) { + String apiName = this.getApiName(pjp); + String requestId = this.getRequestId(); + // 配置日志文件打印 REQUEST_ID + MDC.put("REQUEST_ID", requestId); + Object returnValue = null; + try{ + this.printRequestParam(apiName, pjp); + returnValue = pjp.proceed(); + this.handleRequestId(returnValue); + }catch (BusinessException ex){ + returnValue = this.handleBusinessException(apiName, ex); + }catch (Throwable ex){ + returnValue = this.handleSystemException(apiName, ex); + }finally { + this.printResponse(apiName, returnValue); + RequestIdUtils.removeRequestId(); + MDC.clear(); + } + return returnValue; + } + + /** + * 处理系统异常 + * @param apiName 接口名称 + * @param ex 系统异常 + * @return 返回参数 + */ + private Response handleSystemException(String apiName, Throwable ex){ + log.error("@Meet unknown error when do " + apiName + ":" + ex.getMessage(), ex); + Response response = new Response(BusinessCodeEnum.UNKNOWN_ERROR.getCode(), BusinessCodeEnum.UNKNOWN_ERROR.getMsg()); + response.setRequestId(RequestIdUtils.getRequestId().toString()); + return response; + } + + /** + * 处理业务异常 + * @param apiName 接口名称 + * @param ex 业务异常 + * @return 返回参数 + */ + private Response handleBusinessException(String apiName, BusinessException ex){ + log.error("@Meet error when do " + apiName + "[" + ex.getCode() + "]:" + ex.getMsg(), ex); + Response response = new Response(ex.getCode(), ex.getMsg()); + response.setRequestId(RequestIdUtils.getRequestId().toString()); + return response; + } + + /** + * 填充RequestId + * @param returnValue 返回参数 + */ + private void handleRequestId(Object returnValue){ + if(returnValue instanceof Response){ + Response response = (Response)returnValue; + response.setRequestId(RequestIdUtils.getRequestId().toString()); + } + } + + /** + * 打印响应参数信息 + * @param apiName 接口名称 + * @param returnValue 返回值 + */ + private void printResponse(String apiName, Object returnValue){ + if (log.isInfoEnabled()) { + log.info("@@{} done, response: {}", apiName, JSONUtil.toJsonStr(returnValue)); + } + } + + /** + * 打印请求参数信息 + * @param apiName 接口名称 + * @param pjp 切点 + */ + private void printRequestParam(String apiName, ProceedingJoinPoint pjp){ + Object[] args = pjp.getArgs(); + if(log.isInfoEnabled() && args != null&& args.length > 0){ + for(Object o : args) { + if(!(o instanceof HttpServletRequest) && !(o instanceof HttpServletResponse) && !(o instanceof CommonsMultipartFile)) { + log.info("@@{} started, request: {}", apiName, JSONUtil.toJsonStr(o)); + } + } + } + } + + /** + * 获取RequestId + * 优先从header头获取,如果没有则自己生成 + * @return RequestId + */ + private String getRequestId(){ + UUID existUUID = RequestIdUtils.getRequestId(); + if(existUUID != null){ + return existUUID.toString(); + } + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if(attributes == null || !StringUtils.hasText(attributes.getRequest().getHeader("x-request-id"))) { + RequestIdUtils.generateRequestId(); + return RequestIdUtils.getRequestId().toString(); + } + // 因为如果有网关,则一般会从网关传递过来,所以优先从header头获取 + HttpServletRequest request = attributes.getRequest(); + String requestId = request.getHeader("x-request-id"); + UUID uuid = UUID.fromString(requestId); + RequestIdUtils.generateRequestId(uuid); + return requestId; + } + + /** + * 获取当前接口对应的类名和方法名 + * @param pjp 切点 + * @return apiName + */ + private String getApiName(ProceedingJoinPoint pjp){ + String apiClassName = pjp.getTarget().getClass().getSimpleName(); + String methodName = pjp.getSignature().getName(); + return apiClassName.concat(":").concat(methodName); + } +} diff --git a/src/main/java/org/anyin/gitee/shiro/base/BusinessCodeEnum.java b/src/main/java/org/anyin/gitee/shiro/base/BusinessCodeEnum.java index 2a7cf06..33ee71c 100644 --- a/src/main/java/org/anyin/gitee/shiro/base/BusinessCodeEnum.java +++ b/src/main/java/org/anyin/gitee/shiro/base/BusinessCodeEnum.java @@ -4,6 +4,7 @@ import lombok.Getter; @Getter public enum BusinessCodeEnum { + UNKNOWN_ERROR("1000", "未知错误"), USER_UN_LOGIN("1008", "用户未登录"), USER_NOT_FOUND("B1001", "用户账号密码错误"), PWD_NOT_MATCH("B1002", "用户账号密码错误"), diff --git a/src/main/java/org/anyin/gitee/shiro/base/BusinessException.java b/src/main/java/org/anyin/gitee/shiro/base/BusinessException.java index b92e5d7..22a97b1 100644 --- a/src/main/java/org/anyin/gitee/shiro/base/BusinessException.java +++ b/src/main/java/org/anyin/gitee/shiro/base/BusinessException.java @@ -16,6 +16,6 @@ public class BusinessException extends RuntimeException{ public BusinessException(String code, String msg){ this.code = code; - this.msg = code; + this.msg = msg; } } diff --git a/src/main/java/org/anyin/gitee/shiro/base/Response.java b/src/main/java/org/anyin/gitee/shiro/base/Response.java index 9ce86ff..cd409e2 100644 --- a/src/main/java/org/anyin/gitee/shiro/base/Response.java +++ b/src/main/java/org/anyin/gitee/shiro/base/Response.java @@ -7,20 +7,25 @@ import lombok.Data; public class Response { private static final String SUCCESS_CODE = "200"; + private static final String SUCCESS_MSG = "处理成功"; private String code; private String msg; + private String requestId; + private T data; public Response(){ this.code = SUCCESS_CODE; + this.msg = SUCCESS_MSG; } public Response(T data){ this.data = data; this.code = SUCCESS_CODE; + this.msg = SUCCESS_MSG; } public Response(String code, String msg){ diff --git a/src/main/java/org/anyin/gitee/shiro/config/AppConfig.java b/src/main/java/org/anyin/gitee/shiro/config/AppConfig.java new file mode 100644 index 0000000..fe1b69f --- /dev/null +++ b/src/main/java/org/anyin/gitee/shiro/config/AppConfig.java @@ -0,0 +1,14 @@ +package org.anyin.gitee.shiro.config; + +import org.anyin.gitee.shiro.advisor.ApiMessageAdvisor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig { + + @Bean + public ApiMessageAdvisor apiMessageAdvisor(){ + return new ApiMessageAdvisor(); + } +} diff --git a/src/main/java/org/anyin/gitee/shiro/controller/LogTestController.java b/src/main/java/org/anyin/gitee/shiro/controller/LogTestController.java new file mode 100644 index 0000000..7cd1474 --- /dev/null +++ b/src/main/java/org/anyin/gitee/shiro/controller/LogTestController.java @@ -0,0 +1,27 @@ +package org.anyin.gitee.shiro.controller; + +import org.anyin.gitee.shiro.base.BusinessCodeEnum; +import org.anyin.gitee.shiro.base.Response; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/log-test") +public class LogTestController { + + @GetMapping("/success") + public Response success(){ + return new Response<>("success"); + } + + @GetMapping("/business-exception") + public Response businessException(){ + throw BusinessCodeEnum.PWD_NOT_MATCH.getException(); + } + + @GetMapping("/system-exception") + public Response systemException(){ + throw new NullPointerException("空指针异常"); + } +} diff --git a/src/main/java/org/anyin/gitee/shiro/controller/LoginController.java b/src/main/java/org/anyin/gitee/shiro/controller/LoginController.java index b4eb792..3f8eeee 100644 --- a/src/main/java/org/anyin/gitee/shiro/controller/LoginController.java +++ b/src/main/java/org/anyin/gitee/shiro/controller/LoginController.java @@ -2,7 +2,6 @@ package org.anyin.gitee.shiro.controller; import org.anyin.gitee.shiro.base.Response; import org.anyin.gitee.shiro.controller.form.LoginForm; -import org.anyin.gitee.shiro.model.UserInfo; import org.anyin.gitee.shiro.service.ILoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; diff --git a/src/main/java/org/anyin/gitee/shiro/shiro/TokenFilter.java b/src/main/java/org/anyin/gitee/shiro/shiro/TokenFilter.java index 91ec11d..789a837 100644 --- a/src/main/java/org/anyin/gitee/shiro/shiro/TokenFilter.java +++ b/src/main/java/org/anyin/gitee/shiro/shiro/TokenFilter.java @@ -1,11 +1,15 @@ package org.anyin.gitee.shiro.shiro; import cn.hutool.json.JSONUtil; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.anyin.gitee.shiro.base.BusinessCodeEnum; +import org.anyin.gitee.shiro.base.BusinessException; import org.anyin.gitee.shiro.base.Response; import org.anyin.gitee.shiro.base.SpringContext; import org.anyin.gitee.shiro.service.ITokenService; +import org.anyin.gitee.shiro.utils.RequestIdUtils; +import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.springframework.util.ObjectUtils; @@ -16,6 +20,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.UUID; @Slf4j public class TokenFilter extends AuthenticatingFilter { @@ -52,7 +57,7 @@ public class TokenFilter extends AuthenticatingFilter { String token = this.getToken(request); if(ObjectUtils.isEmpty(token)){ - this.respUnLogin(request, response); + this.responseException(request, response, BusinessCodeEnum.USER_UN_LOGIN.getException()); return false; } @@ -62,19 +67,28 @@ public class TokenFilter extends AuthenticatingFilter { } if(!tokenService.check(token)){ - this.respUnLogin(request, response); + this.responseException(request, response, BusinessCodeEnum.USER_UN_LOGIN.getException()); } // 根据token获取用户信息,会执行 TokenRealm#doGetAuthenticationInfo 方法 return executeLogin(servletRequest, servletResponse); } - private void respUnLogin(HttpServletRequest request, HttpServletResponse response) throws IOException { + @SneakyThrows + @Override + protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest servletRequest, ServletResponse servletResponse) { + HttpServletRequest request = (HttpServletRequest)servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + this.responseException(request, response, BusinessCodeEnum.TOKEN_INVALID.getException()); + return false; + } + + private void responseException(HttpServletRequest request, HttpServletResponse response, BusinessException ex) throws IOException { response.setContentType("application/json;charset=utf-8"); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); - - Response resp = new Response(BusinessCodeEnum.USER_UN_LOGIN.getCode(), BusinessCodeEnum.USER_UN_LOGIN.getMsg()); + Response resp = new Response(ex.getCode(), ex.getMsg()); + resp.setRequestId(UUID.randomUUID().toString()); response.getWriter().print(JSONUtil.toJsonStr(resp)); } diff --git a/src/main/java/org/anyin/gitee/shiro/utils/RequestIdUtils.java b/src/main/java/org/anyin/gitee/shiro/utils/RequestIdUtils.java new file mode 100644 index 0000000..8d09735 --- /dev/null +++ b/src/main/java/org/anyin/gitee/shiro/utils/RequestIdUtils.java @@ -0,0 +1,26 @@ +package org.anyin.gitee.shiro.utils; + +import java.util.UUID; + +public class RequestIdUtils { + private static final ThreadLocal requestIdHolder = new ThreadLocal<>(); + + public RequestIdUtils() { + } + + public static void generateRequestId() { + requestIdHolder.set(UUID.randomUUID()); + } + + public static void generateRequestId(UUID uuid) { + requestIdHolder.set(uuid); + } + + public static UUID getRequestId() { + return (UUID)requestIdHolder.get(); + } + + public static void removeRequestId() { + requestIdHolder.remove(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 458884f..9097c01 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,9 @@ spring: application: - name: shiro-to-token \ No newline at end of file + name: shiro-to-token + +logging: + level: + root: info + file: + path: ./logs/shiro-to-token.log \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..3878a19 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,34 @@ + + + + logback + + + + + + System.out + + DEBUG + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%X{REQUEST_ID}] [%thread] [%-5level] [%logger{0}:%L] : %msg%n + + + + + ${path} + + ${path}.%d{yyyy-MM-dd}.zip + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%X{REQUEST_ID}] [%thread] [%-5level] [%logger{0}:%L] : %msg%n + + + + + + + + + \ No newline at end of file