SpringBoot使用AOP注解记录操作日志

devtools/2024/9/23 2:10:41/

一、前言

日志:指系统所指定对象的某些操作和其操作结果按时间有序的集合。
操作日志:主要是对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强。比如张三在某个时间下了订单买了某个商品!

二、功能描述

  • 使用aop切面编程解耦
  • 使用spring事件监听保存日志
  • 日志内容详细

2.1 AOP

概述:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

切面(Aspect): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
切点(Pointcut): 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
连接点(Joint point): 表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
通知(Advice): Advice 定义了在 pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

  • 前置通知(Before):在目标方法调用前调用通知功能;
  • 后置通知(After):在目标方法调用之后调用通知功能,不关心方法的返回结果;
  • 返回通知(AfterReturning):在目标方法成功执行之后调用通知功能;
  • 异常通知(AfterThrowing):在目标方法抛出异常后调用通知功能;
  • 环绕通知(Around):通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为。

三、代码实现

3.1 数据库准备

CREATE TABLE `operation_log`  (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'id',`module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '模块',`operation_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作名',`method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '方法',`request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求url',`request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方式',`request_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求ip',`request_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求地点',`request_param` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求参数',`cost_time` bigint NULL DEFAULT NULL COMMENT '耗时(ms)',`success_flag` tinyint(1) NULL DEFAULT 0 COMMENT '成功标志',`response_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '响应数据',`error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '错误',`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志' ROW_FORMAT = DYNAMIC;

3.2 引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.21</version>
</dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.5</version>
</dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.34</version>
</dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.22</version>
</dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId>
</dependency><!-- ip2region的封装 -->
<dependency><groupId>net.dreamlu</groupId><artifactId>mica-ip2region</artifactId><version>2.5.4</version>
</dependency>

3.3 配置文件

server:port: 8022spring:#数据库配置datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/boot_codegen??useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: rootmvc:pathmatch:matching-strategy: ant_path_matcher# mybatis-plus配置
mybatis-plus:# MyBaits别名包扫描路径type-aliases-package: com.qiangesoft.log.entity# Mapper所对应的XML文件位置 默认【classpath*:/mapper/**/*.xml】mapper-locations: classpath*:/mapper/*Mapper.xmlconfiguration:# 日志打印log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 是否开启自动驼峰命名规则map-underscore-to-camel-case: true

3.4 AOP切面

切面注解

java">package com.qiangesoft.log.core;import java.lang.annotation.*;/*** 自定义操作日志记录注解** @author qiangesoft* @date 2024-04-02*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {/*** 模块*/String module() default "";/*** 操作名称*/String operationName() default "";
}

日志切面实现

java">package com.qiangesoft.log.core;import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.qiangesoft.log.entity.OperationLog;
import com.qiangesoft.log.utils.IpUtil;
import com.qiangesoft.log.utils.ServletUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
import net.dreamlu.mica.ip2region.core.IpInfo;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Map;/*** 操作日志记录处理** @author qiangesoft* @date 2024-04-02*/
@Slf4j
@RequiredArgsConstructor
@Aspect
@Component
public class LogAspect {private final Ip2regionSearcher ip2regionSearcher;/*** 消耗时间*/private static final ThreadLocal<Long> THREAD_COST_TIME = new NamedThreadLocal<>("CostTime");/*** 处理请求前执行** @param joinPoint* @param log*/@Before(value = "@annotation(log)")public void doBefore(JoinPoint joinPoint, Log log) {THREAD_COST_TIME.set(System.currentTimeMillis());}/*** 处理请求后执行** @param joinPoint* @param log* @param jsonResult*/@AfterReturning(pointcut = "@annotation(log)", returning = "jsonResult")public void doAfterReturning(JoinPoint joinPoint, Log log, Object jsonResult) {handleLog(joinPoint, log, jsonResult, null);}/*** 请求发生异常** @param joinPoint* @param log* @param e*/@AfterThrowing(value = "@annotation(log)", throwing = "e")public void doAfterThrowing(JoinPoint joinPoint, Log log, Exception e) {handleLog(joinPoint, log, null, e);}/*** 处理日志** @param joinPoint* @param log* @param jsonResult* @param e*/private void handleLog(final JoinPoint joinPoint, Log log, Object jsonResult, final Exception e) {try {OperationLog operationLog = new OperationLog();// 业务信息operationLog.setModule(log.module());operationLog.setOperationName(log.operationName());String className = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();operationLog.setMethod(className + "." + methodName + "()");// 请求信息HttpServletRequest request = ServletUtil.getRequest();operationLog.setRequestUrl(request.getRequestURI());operationLog.setRequestMethod(request.getMethod());String ipAddr = IpUtil.getIpAddr(request);operationLog.setRequestIp(ipAddr);IpInfo ipInfo = ip2regionSearcher.memorySearch(ipAddr);if (ipInfo != null) {operationLog.setRequestLocation(StringUtils.isNotBlank(ipInfo.getCountry()) ? ipInfo.getCountry() + ipInfo.getProvince() + ipInfo.getCity() + ipInfo.getIsp() : ipInfo.getIsp());}operationLog.setRequestParam(getRequestParam(joinPoint));// 响应信息operationLog.setSuccessFlag(true);operationLog.setResponseData(jsonResult == null ? null : JSONObject.toJSONString(jsonResult));if (e != null) {operationLog.setSuccessFlag(false);operationLog.setErrorMsg(e.getMessage());}operationLog.setCostTime(System.currentTimeMillis() - THREAD_COST_TIME.get());// 入库SpringUtil.publishEvent(new OperationLogEvent(operationLog));} catch (Exception exp) {exp.printStackTrace();} finally {THREAD_COST_TIME.remove();}}/*** 获取请求的参数** @param joinPoint* @return*/private String getRequestParam(JoinPoint joinPoint) {HttpServletRequest request = ServletUtil.getRequest();Map<?, ?> paramsMap = ServletUtil.getParamMap(request);String requestMethod = request.getMethod();if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {StringBuilder param = new StringBuilder();Object[] params = joinPoint.getArgs();if (params != null) {for (Object o : params) {if (o != null && !isFilterObject(o)) {try {param.append(JSON.toJSONString(o)).append(" ");} catch (Exception e) {e.printStackTrace();}}}}return param.toString().trim();} else {return JSON.toJSONString(paramsMap);}}/*** 判断是否需要过滤的对象** @param o* @return*/@SuppressWarnings("rawtypes")public 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;}
}

3.5 日志记录

定义spring事件

java">package com.qiangesoft.log.core;import com.qiangesoft.log.entity.OperationLog;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;/*** 操作日志记录事件** @author qiangesoft* @date 2024-04-02*/
@Getter
public class OperationLogEvent extends ApplicationEvent {private OperationLog operationLog;public OperationLogEvent(OperationLog operationLog) {super(operationLog);this.operationLog = operationLog;}
}

事件监听

java">package com.qiangesoft.log.core;import com.qiangesoft.log.service.IOperationLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;/*** 操作日志记录处理监听器** @author qiangesoft* @date 2024-04-02*/
@Component
@RequiredArgsConstructor
public class OperationLogListener implements ApplicationListener<OperationLogEvent> {private final IOperationLogService operationLogService;@Overridepublic void onApplicationEvent(OperationLogEvent event) {operationLogService.save(event.getOperationLog());}
}

3.6 各层代码

实体类

java">package com.qiangesoft.log.entity;import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;import java.io.Serializable;
import java.time.LocalDateTime;/*** <p>* 操作日志* </p>** @author qiangesoft* @date 2024-04-01*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("operation_log")
public class OperationLog implements Serializable {private static final long serialVersionUID = 1L;/*** id*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 模块*/private String module;/*** 操作名*/private String operationName;/*** 方法*/private String method;/*** 请求url*/private String requestUrl;/*** 请求方式*/private String requestMethod;/*** 请求ip*/private String requestIp;/*** 请求地点*/private String requestLocation;/*** 请求参数*/private String requestParam;/*** 耗时(ms)*/private Long costTime;/*** 成功标志*/private Boolean successFlag;/*** 响应数据*/private String responseData;/*** 错误*/private String errorMsg;/*** 创建时间*/@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;}

mapper层

java">package com.qiangesoft.log.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qiangesoft.log.entity.OperationLog;/*** <p>* 操作日志 Mapper 接口* </p>** @author qiangesoft* @date 2024-04-01*/
public interface OperationLogMapper extends BaseMapper<OperationLog> {}

service层

java">package com.qiangesoft.log.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.qiangesoft.log.entity.OperationLog;/*** <p>* 操作日志 服务类* </p>** @author qiangesoft* @date 2024-04-01*/
public interface IOperationLogService extends IService<OperationLog> {}
java">package com.qiangesoft.log.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qiangesoft.log.entity.OperationLog;
import com.qiangesoft.log.mapper.OperationLogMapper;
import com.qiangesoft.log.service.IOperationLogService;
import org.springframework.stereotype.Service;/*** <p>* 操作日志 服务实现类* </p>** @author qiangesoft* @date 2024-04-01*/
@Service
public class OperationLogServiceImpl extends ServiceImpl<OperationLogMapper, OperationLog> implements IOperationLogService {}

控制层测试

java">package com.qiangesoft.log.controller;import com.alibaba.fastjson2.JSONObject;
import com.qiangesoft.log.core.Log;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 测试** @author qiangesoft* @date 2024-04-29*/
@RequestMapping("/test")
@RestController
public class TestController {@Log(module = "测试", operationName = "查询xx")@GetMapping("/get")public JSONObject get(String id) {JSONObject jsonObject = new JSONObject();jsonObject.put("id", 111);jsonObject.put("name", "张三");return jsonObject;}}

3.7 工具类

java">package com.qiangesoft.log.utils;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;/*** 获取IP方法** @author qiangesoft* @date 2024-03-19*/
@Slf4j
public class IpUtil {public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";// 匹配 ippublic final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))";// 匹配网段public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")";/*** 获取客户端IP** @return IP地址*/public static String getIpAddr() {return getIpAddr(ServletUtil.getRequest());}/*** 获取客户端IP** @param request 请求对象* @return IP地址*/public static String getIpAddr(HttpServletRequest request) {if (request == null) {return "unknown";}String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-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.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("X-Real-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);}/*** 检查是否为内部IP地址** @param ip IP地址* @return 结果*/public static boolean internalIp(String ip) {byte[] addr = textToNumericFormatV4(ip);return internalIp(addr) || "127.0.0.1".equals(ip);}/*** 检查是否为内部IP地址** @param addr byte地址* @return 结果*/private static boolean internalIp(byte[] addr) {if (addr == null || addr.length < 2) {return true;}final byte b0 = addr[0];final byte b1 = addr[1];// 10.x.x.x/8final byte SECTION_1 = 0x0A;// 172.16.x.x/12final byte SECTION_2 = (byte) 0xAC;final byte SECTION_3 = (byte) 0x10;final byte SECTION_4 = (byte) 0x1F;// 192.168.x.x/16final byte SECTION_5 = (byte) 0xC0;final byte SECTION_6 = (byte) 0xA8;switch (b0) {case SECTION_1:return true;case SECTION_2:if (b1 >= SECTION_3 && b1 <= SECTION_4) {return true;}case SECTION_5:switch (b1) {case SECTION_6:return true;}default:return false;}}/*** 将IPv4地址转换成字节** @param text IPv4地址* @return byte 字节*/public static byte[] textToNumericFormatV4(String text) {if (text.length() == 0) {return null;}byte[] bytes = new byte[4];String[] elements = text.split("\\.", -1);try {long l;int i;switch (elements.length) {case 1:l = Long.parseLong(elements[0]);if ((l < 0L) || (l > 4294967295L)) {return null;}bytes[0] = (byte) (int) (l >> 24 & 0xFF);bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 2:l = Integer.parseInt(elements[0]);if ((l < 0L) || (l > 255L)) {return null;}bytes[0] = (byte) (int) (l & 0xFF);l = Integer.parseInt(elements[1]);if ((l < 0L) || (l > 16777215L)) {return null;}bytes[1] = (byte) (int) (l >> 16 & 0xFF);bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 3:for (i = 0; i < 2; ++i) {l = Integer.parseInt(elements[i]);if ((l < 0L) || (l > 255L)) {return null;}bytes[i] = (byte) (int) (l & 0xFF);}l = Integer.parseInt(elements[2]);if ((l < 0L) || (l > 65535L)) {return null;}bytes[2] = (byte) (int) (l >> 8 & 0xFF);bytes[3] = (byte) (int) (l & 0xFF);break;case 4:for (i = 0; i < 4; ++i) {l = Integer.parseInt(elements[i]);if ((l < 0L) || (l > 255L)) {return null;}bytes[i] = (byte) (int) (l & 0xFF);}break;default:return null;}} catch (NumberFormatException e) {return null;}return bytes;}/*** 获取IP地址** @return 本地IP地址*/public static String getHostIp() {try {return InetAddress.getLocalHost().getHostAddress();} catch (UnknownHostException e) {}return "127.0.0.1";}/*** 获取主机名** @return 本地主机名*/public static String getHostName() {try {return InetAddress.getLocalHost().getHostName();} catch (UnknownHostException e) {}return "未知";}/*** 从多级反向代理中获得第一个非unknown IP地址** @param ip 获得的IP地址* @return 第一个非unknown IP地址*/public static String getMultistageReverseProxyIp(String ip) {// 多级反向代理检测if (ip != null && ip.indexOf(",") > 0) {final String[] ips = ip.trim().split(",");for (String subIp : ips) {if (!isUnknown(subIp)) {ip = subIp;break;}}}return StringUtils.substring(ip, 0, 255);}/*** 检测给定字符串是否为未知,多用于检测HTTP请求相关** @param checkString 被检测的字符串* @return 是否未知*/public static boolean isUnknown(String checkString) {return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);}/*** 是否为IP*/public static boolean isIP(String ip) {return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP);}/*** 是否为IP,或 *为间隔的通配符地址*/public static boolean isIpWildCard(String ip) {return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD);}/*** 检测参数是否在ip通配符里*/public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip) {String[] s1 = ipWildCard.split("\\.");String[] s2 = ip.split("\\.");boolean isMatchedSeg = true;for (int i = 0; i < s1.length && !s1[i].equals("*"); i++) {if (!s1[i].equals(s2[i])) {isMatchedSeg = false;break;}}return isMatchedSeg;}/*** 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串*/public static boolean isIPSegment(String ipSeg) {return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG);}/*** 判断ip是否在指定网段中*/public static boolean ipIsInNetNoCheck(String iparea, String ip) {int idx = iparea.indexOf('-');String[] sips = iparea.substring(0, idx).split("\\.");String[] sipe = iparea.substring(idx + 1).split("\\.");String[] sipt = ip.split("\\.");long ips = 0L, ipe = 0L, ipt = 0L;for (int i = 0; i < 4; ++i) {ips = ips << 8 | Integer.parseInt(sips[i]);ipe = ipe << 8 | Integer.parseInt(sipe[i]);ipt = ipt << 8 | Integer.parseInt(sipt[i]);}if (ips > ipe) {long t = ips;ips = ipe;ipe = t;}return ips <= ipt && ipt <= ipe;}/*** 校验ip是否符合过滤串规则** @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`* @param ip     校验IP地址* @return boolean 结果*/public static boolean isMatchedIp(String filter, String ip) {if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip)) {return false;}String[] ips = filter.split(";");for (String iStr : ips) {if (isIP(iStr) && iStr.equals(ip)) {return true;} else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) {return true;} else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) {return true;}}return false;}
}
java">package com.qiangesoft.log.utils;import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;/*** 客户端工具类** @author qiangesoft* @date 2024-03-19*/
public class ServletUtil {/*** 获取String参数*/public static String getParameter(String name) {return getRequest().getParameter(name);}/*** 获得所有请求参数** @param request 请求对象{@link ServletRequest}* @return Map*/public static Map<String, String[]> getParams(ServletRequest request) {final Map<String, String[]> map = request.getParameterMap();return Collections.unmodifiableMap(map);}/*** 获得所有请求参数** @param request 请求对象{@link ServletRequest}* @return Map*/public static Map<String, String> getParamMap(ServletRequest request) {Map<String, String> params = new HashMap<>();for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {params.put(entry.getKey(), StringUtils.join(entry.getValue(), ","));}return params;}/*** 获取request*/public static HttpServletRequest getRequest() {return getRequestAttributes().getRequest();}/*** 获取response*/public static HttpServletResponse getResponse() {return getRequestAttributes().getResponse();}/*** 获取session*/public static HttpSession getSession() {return getRequest().getSession();}public static ServletRequestAttributes getRequestAttributes() {RequestAttributes attributes = RequestContextHolder.getRequestAttributes();return (ServletRequestAttributes) attributes;}/*** 将字符串渲染到客户端** @param response 渲染对象* @param string   待渲染的字符串*/public static void renderString(HttpServletResponse response, String string) {try {response.setStatus(200);response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().print(string);} catch (IOException e) {e.printStackTrace();}}/*** 是否是Ajax异步请求** @param request*/public static boolean isAjaxRequest(HttpServletRequest request) {String accept = request.getHeader("accept");if (accept != null && accept.contains("application/json")) {return true;}String xRequestedWith = request.getHeader("X-Requested-With");if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {return true;}String uri = request.getRequestURI();if (inStringIgnoreCase(uri, ".json", ".xml")) {return true;}String ajax = request.getParameter("__ajax");return inStringIgnoreCase(ajax, "json", "xml");}private static boolean inStringIgnoreCase(String str, String... strs) {if (str != null && strs != null) {for (String s : strs) {if (str.equalsIgnoreCase(str.trim())) {return true;}}}return false;}/*** 内容编码** @param str 内容* @return 编码后的内容*/public static String urlEncode(String str) throws UnsupportedEncodingException {return URLEncoder.encode(str, StandardCharsets.UTF_8.displayName());}/*** 内容解码** @param str 内容* @return 解码后的内容*/public static String urlDecode(String str) throws UnsupportedEncodingException {return URLDecoder.decode(str, StandardCharsets.UTF_8.displayName());}
}

3.8 测试

在这里插入图片描述
在这里插入图片描述


http://www.ppmy.cn/devtools/33620.html

相关文章

Java web第五次作业

1.在idea中配置好数据源 2、视频案例中只给出了查询所有结果的示例&#xff0c;请自己完成添加、删除、修改操作的代码。以下供参 考。 Delete("delete from emp where id#{id}") public void delete(Integer id); 测试代码 Test public void testDelete(){ empMa…

Transformer中的数据输入构造

文章目录 1. 文本内容2. 字典构造2.1 定义一个类用于字典构造2.2 拆分文本2.3 构造结果 3. 完整代码 1. 文本内容 假如我们有如下一段文本内容&#xff1a; Optics It is the branch of physics that studies the behaviour and properties of light . Optical Science 这段…

【华为】路由综合实验(OSPF+BGP基础)

【华为】路由综合实验 实验需求拓扑配置AR1AR2AR3AR4AR5PC1PC2 查看通信OSPF邻居OSPF路由表 BGPBGP邻居BGP 路由表 配置文档 实验需求 ① 自行规划IP地址 ② 在区域1里面 启用OSPF ③ 在区域1和区域2 启用BGP&#xff0c;使AR4和AR3成为eBGP&#xff0c;AR4和AR5成为iBGP对等体…

500行代码实现贪吃蛇(1)

文章目录 目录1. Win32 API 介绍1.1 Win32 API1.2 控制台程序&#xff08;Console&#xff09;1.3 控制台屏幕上的坐标COORD1.4 [GetStdHandle](https://learn.microsoft.com/zh-cn/windows/console/getstdhandle)1.5 [GetConsoleCursorInfo](https://learn.microsoft.com/zh-c…

AI图书推荐:杀手级ChatGPT提示词——利用人工智能实现成功与盈利

《杀手级ChatGPT提示词——利用人工智能实现成功与盈利》&#xff08;Killer ChatGPT Prompts_ Harness the Power of AI for Success and Profit &#xff09;一书是作者Guy Hart-Davis关于ChatGPT的指南&#xff0c;ChatGPT是OpenAI开发的大语言模型。这本书提供了各种职业角…

Python爬取豆瓣电影Top250数据

任务 爬取豆瓣电影top250中的影片名称、影片海报、年份、地区、类型、评分、评价人数、总体评价&#xff0c;并输出到douban_top250.xlsx文件中 环境 Python 3.8 requests bs4 openpyxl 源码 # 创建一个新的Excel工作簿 workbook openpyxl.Workbook() # 获取默认的工作表…

Golang 设计模式(结构型)

文章目录 代理模式门面模式桥接模式适配器模式外观模式享元模式装饰器模式组合模式 代理模式 代理模式是一种结构型设计模式&#xff0c;用于控制对其他对象的访问。它允许你创建一个代理对象&#xff0c;该对象可以代表原始对象&#xff0c;从而可以控制客户端对原始对象的访…

超级简单羊毛绒吊顶天花板(华为公司iPhone茅台酒),花几分钟换回来几百块(非脚本软件)

项目简介&#xff1a;本项目旨在详细介绍当前市场上正规平台的溢价产品信息&#xff08;如华为、iPhone、茅台酒等&#xff09;&#xff0c;分享实际操作的限时抢购步骤&#xff0c;分析出货流程和策略。只需每天投入数分钟&#xff0c;点击操作&#xff0c;就有机会获得几百元…