SpringBoot统一接口响应、统一接口日志打印、全局异常处理器

一.统一接口响应

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;
    }
}

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值