一.统一接口响应
1.定义
package com.example.base.common;
import lombok.Data;
/**
* @version 1.0
* @date 2024/5/22
* @description 统一响应
*/
@Data
public class ResultData<T> {
private Integer code;
private String message;
private T data;
public ResultData() {
}
public ResultData(Integer code, String message) {
super();
this.code = code;
this.message = message;
}
public ResultData(Integer code, String message, T data) {
super();
this.code = code;
this.message = message;
this.data = data;
}
public ResultData(ApiCode apiCode, T data) {
super();
this.code = apiCode.getCode();
this.message = apiCode.getMessage();
this.data = data;
}
public ResultData(ApiCode apiCode, String message, T data) {
super();
this.code = apiCode.getCode();
this.message = message;
this.data = data;
}
public static <T> ResultData<T> success(T data) {
return new ResultData<>(ApiCode.SUCCESS, data);
}
public static <T> ResultData<T> fail(int code, String message) {
return new ResultData<>(code, message);
}
}
2.定义统一状态码
package com.example.base.common;
/**
* @auth :
*/
public enum ApiCode {
SUCCESS(200, "OK"),
PARAMETER_ERROR(401, "参数有误"),
FAIL(402, "处理失败"),
FORBIDDEN(403, "已过期,请重新登录"),
NOT_FOUND(404, "请求资源不存在"),
REQUEST_METHOD_NOT_SUPPORTED(405, "不支持的请求方法"),
;
private int code;
private String message;
ApiCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
3.controller接口使用
package com.example.base.controller;
import com.example.base.annotation.LegalEnum;
import com.example.base.annotation.RateLimit;
import com.example.base.common.ApiCode;
import com.example.base.common.ResultData;
import com.example.base.entity.Address;
import com.example.base.entity.WelcomeDto;
import com.example.base.enums.ScopeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author 土
* @version 1.0
* @description controller
*/
@RestController
@RequestMapping("/hello")
@Slf4j
@RequiredArgsConstructor
@Validated
public class ApiController {
@PostMapping(value = "addOne", produces = MediaType.APPLICATION_JSON_VALUE)
public ResultData<List<Address>> addOne(@Validated @RequestBody WelcomeDto dto) {
System.out.println("进来了");
return ResultData.success(dto.getUserList().get(0).getAddressList());
}
}
二.统一接口日志打印
1.定义打印的实体
package com.example.base.common.log;
import com.example.base.utils.JsonUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* 请求日志
*
* @author 土
* @date 2020/10/19 15:26
*/
@Data
@Slf4j
public class LogBean {
private static final ThreadLocal<LogBean> LOG_BEAN_THREAD_LOCAL = ThreadLocal.withInitial(LogBean::new);
private int code;
private long startTime;
private long consumeTime;
private String path;
// 表单参数
private Object params;
private Object result;
private String error;
private String ip;
private String method;
public static LogBean start() {
LogBean logBean = LOG_BEAN_THREAD_LOCAL.get();
logBean.setStartTime(System.currentTimeMillis());
return logBean;
}
public static LogBean get() {
return LOG_BEAN_THREAD_LOCAL.get();
}
public static void end() {
LogBean logBean = get();
logBean.setConsumeTime(System.currentTimeMillis() - logBean.getStartTime());
logBean.print();
LOG_BEAN_THREAD_LOCAL.remove();
}
private void print() {
if (log.isInfoEnabled()) {
log.info(JsonUtils.toJsonString(this));
}
}
}
2.定义接口请求拦截器
package com.example.base.aspect.interceptor;
import com.example.base.common.log.LogBean;
import com.example.base.utils.IpUtils;
import com.example.base.utils.JsonUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import java.util.Enumeration;
/**
* @author 土
* @version 1.0
* @date 2024/5/26
* @description 接口请求拦截器
*/
public class LogInterceptor implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LogBean logBean = LogBean.start();
logBean.setIp(IpUtils.getIpAddr(request));
logBean.setPath(request.getRequestURI());
logBean.setParams(getParameterJson(request));
logBean.setMethod(request.getMethod());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
LogBean.end();
}
/**
* 获取 RequestParam 从参数
*
* @param request
* @return
*/
private JsonNode getParameterJson(HttpServletRequest request) {
ObjectNode json = JsonUtils.createObjectNode();
Enumeration<String> names = request.getParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
json.put(name, request.getParameter(name));
}
return json;
}
}
获取请求IP地址工具类
package com.example.base.utils;
import jakarta.servlet.http.HttpServletRequest;
/**
* 获取IP方法
*
* @author 土
*/
public class IpUtils {
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("X-Real-IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
3.注册请求拦截器
package com.example.base.config;
import com.example.base.aspect.interceptor.LogInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 通用配置
*
* @author
*/
@Configuration
@Slf4j
public class ResourcesConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
;
}
}
4.统一日志处理切面
package com.example.base.aspect;
import com.example.base.common.ResultData;
import com.example.base.common.log.LogBean;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
/**
* @author 土
* @version 1.0
* @date 2024/5/26
* @description 统一日志处理切面
*/
@Aspect
@Component
@Slf4j
public class RestControllerAspect {
@Pointcut("execution(public * com.example.base.controller..*.*(..))")
public void controllerPointCut() {
}
/**
* 输出接口入参
*
* @param joinPoint
*/
@Before("controllerPointCut()")
public void doBefore(JoinPoint joinPoint) {
Optional.ofNullable(getRequestBody(joinPoint)).ifPresent(LogBean.get()::setParams);
}
@AfterReturning(pointcut = "controllerPointCut()", returning = "result")
public void doAfterReturining(Object result) {
handleWebLog(result);
}
private void handleWebLog(Object result) {
LogBean logBean = LogBean.get();
if (result instanceof ResultData) {
logBean.setCode(((ResultData) result).getCode());
logBean.setResult(result);
}
}
/**
* 获取 RequestBody 的参数
*
* @param joinPoint
* @return
*/
private Object getRequestBody(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Object[] args = joinPoint.getArgs();//获取参数值
Parameter[] parameters = signature.getMethod().getParameters();//获取参数名
for (int i = 0; i < parameters.length; i++) {
if (args[i] == null || isFilterObject(args[i])) {//大结构体类型的参数,则不处理
continue;
}
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
if (requestBody != null) {
return args[i];//参数名和参数值一一对应起来的
}
}
return null;
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
private boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.entrySet()) {
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
三.全局异常处理器
package com.example.base.common.exception;
import com.example.base.common.ApiCode;
import com.example.base.common.ResultData;
import com.example.base.common.log.LogBean;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Objects;
/**
* @author t
* @version 1.0
* @date 2024/5/24
* @description 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler<T> {
/**
* @param e
* @return
* @description jakarta包下面的注解使用 异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResultData<T> validatedConstraintViolationException(ConstraintViolationException e) {
return logAndResult(e, ApiCode.PARAMETER_ERROR, e.getMessage(), false);
}
/**
* @param e
* @return
* @description 应用场景:校验@RequestParam定义的参数是否输入
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResultData<T> validMissingServletRequestParameterException(MissingServletRequestParameterException e) {
String message = "参数" + e.getParameterName() + "不能为空";
return logAndResult(e, ApiCode.PARAMETER_ERROR, message, false);
}
/**
* @description 应用场景:参数前面有@RequestBody
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultData<T> validMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
return logAndResult(e, ApiCode.PARAMETER_ERROR, message, false);
}
private ResultData<T> logAndResult(Exception e, ApiCode apiCode, String message) {
return logAndResult(e, apiCode, message, true);
}
private ResultData<T> logAndResult(Exception e, ApiCode apiCode, String message, boolean printErrorStack) {
if (printErrorStack) {
log.error(e.getMessage(), e);
} else {
log.warn(e.getMessage());
}
LogBean logBean = LogBean.get();
logBean.setCode(apiCode.getCode());
if (message != null) {
logBean.setError(message);
} else {
logBean.setError(apiCode.getMessage());
}
ResultData<T> resultData = ResultData.fail(apiCode.getCode(), logBean.getError());
logBean.setResult(resultData);
return resultData;
}
}