通常只要是java web项目基本都离不开项目日记,项目日记存在的意义很多,例如:安全审计,问题追踪都离不开项目日记。下面我们说一下项目日记实现最常用的两种方式 。
一 拉截器实现项目日记
1 实现一个拦截器基类,用于事件项目的请求日记
用拦截器实现记录日记代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.time.LocalDateTime;/*** @author hua* @Description 自定义拦截器,用于记录处理Web请求的基本功能。*/
public class BaseInterceptor extends HandlerInterceptorAdapter {// ThreadLocal 用于在每个线程中存储API消息日志ThreadLocal<ApiMessageLog> apiMessageLogThreadLocal = new ThreadLocal<>();// 用于记录日志的Loggerpublic static final Logger logger = LoggerFactory.getLogger(BaseInterceptor.class);/*** 在请求完成后调用的方法,用于执行任何必要的清理或日志记录。** @param request HTTP请求对象。* @param response HTTP响应对象。* @param handler 处理请求的处理程序(控制器方法)。* @param ex 在请求处理过程中发生的任何异常。* @throws Exception 如果在完成后处理过程中发生异常。*/public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 获取当前线程的API消息日志ApiMessageLog apiMessageLog = apiMessageLogThreadLocal.get();// 检查API消息日志是否非空if (apiMessageLog != null) {// 计算请求所用时间long time = Duration.between(apiMessageLog.getCtime(), LocalDateTime.now()).getSeconds();apiMessageLog.setTimeLong((int) time);// 如果请求时间超过8秒,则记录错误日志if (time > 8) {logger.error("超时 > " + time + " > " + apiMessageLog.getApiName() + "\n" + apiMessageLog);}// 如果在请求处理过程中发生异常,将其设置为API消息日志的结果if (ex != null) {apiMessageLog.setResult(ex.getMessage());} else {// 如果没有异常,从请求属性中设置结果String result = (String) request.getAttribute("_REQ_RESULT");if (result != null) {apiMessageLog.setResult(result);}// 从请求属性中设置API订单号String _REQ_ORDER_SN = (String) request.getAttribute("_REQ_ORDER_SN");if (_REQ_ORDER_SN != null) {apiMessageLog.setApiOrderNo(_REQ_ORDER_SN);}// 从请求属性中设置连接器IDString _REQ_CONNECTOR_ID = (String) request.getAttribute("_REQ_CONNECTOR_ID");if (_REQ_ORDER_SN != null) {apiMessageLog.setConnectorId(_REQ_CONNECTOR_ID);}}// 将API消息日志添加到内存队列,队列批量保存效率更高MemDataTable.API_MESSAGE_LOGS.offer(apiMessageLog);}}
}
继承上面写日记基类拦截器(如果项目简单且不用选择性记录,可以把以下代码合并成一个拦截器即可)。
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;/*** @author hua* @Description 统一处理中电联的拦截器*/
@Component
public class ZdlInterceptor extends BaseInterceptor {// 注入StringRedisTemplate实例@AutowiredStringRedisTemplate stringRedisTemplate;// 注入ApiMessageLogServiceImpl实例@AutowiredApiMessageLogServiceImpl apiMessageLogService;/*** 在请求处理前执行的方法** @param request HTTP请求对象* @param response HTTP响应对象* @param handler 处理请求的处理程序* @return 如果继续处理请求,则返回true;否则,返回false* @throws Exception 如果在处理过程中发生异常*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从请求中获取JSON字符串String reqJsonStr = IOUtils.toString(request.getInputStream(), "UTF-8");// 创建API消息日志实体并设置基本信息ApiMessageLog apiMessageLog = new ApiMessageLog();apiMessageLog.setApiName(request.getRequestURI());apiMessageLog.setMsg(reqJsonStr);apiMessageLog.setType("1");apiMessageLog.setCtime(LocalDateTime.now());// 尝试解析JSON字符串JSONObject json = null;try {json = JSONObject.parseObject(reqJsonStr);} catch (JSONException e) {// 如果JSON格式错误,返回错误信息并记录日志String r = "请求JSON格式错误!!!";response.getOutputStream().write(r.getBytes("utf-8"));apiMessageLog.setResult(r);MemDataTable.API_MESSAGE_LOGS.offer(apiMessageLog);return false;}// 从JSON中获取OperatorID并设置到API消息日志实体String operatorID = json.getString("OperatorID");apiMessageLog.setOperatorId(operatorID);// 将API消息日志的ID设置到请求属性中request.setAttribute("ApiMessageLogId", apiMessageLog.getId());// 将API消息日志存储到ThreadLocal中,以便后续处理apiMessageLogThreadLocal.set(apiMessageLog);return true;}
}
2 拦截器配置
这里可以选择性只记录哪些请求开头的日记。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author hua* @Description Web配置类,实现WebMvcConfigurer接口用于配置Web相关的功能。*/
@Configuration
public class WebConfiguration implements WebMvcConfigurer {/*** 配置跨域访问规则** @return CorsConfiguration对象*/private CorsConfiguration corsConfig() {CorsConfiguration corsConfiguration = new CorsConfiguration();// 允许所有来源corsConfiguration.addAllowedOrigin("*");corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");return corsConfiguration;}/*** 配置CorsFilter,用于处理跨域请求** @return CorsFilter对象*/@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", corsConfig());return new CorsFilter(source);}/*** 配置中电联拦截器的Bean** @return ZdlInterceptor对象*/@Beanpublic HandlerInterceptor getZdlInterceptor() {return new ZdlInterceptor();}/*** 配置拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getZdlInterceptor())// 设置不拦截的路径.excludePathPatterns("/public/**", "")// 设置拦截的路径.addPathPatterns("/api/zdl/**");}
}
3 实现效果
最终保存在数据库效果,可以根据业务情况记录,如记录访问ip,时间等。
二 注解方式实现项目日记
1 引入需用到jar包
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.5.4</version></dependency>
2 实现日记注解类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 日记*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnno {/*** 操作内容*/String desc() default "";/*** 关键参数* @return*/String key() default "";}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.LocalDateTime;@Aspect
@Component
public class LogAspect {@Autowiredprivate ISysLogService sysLogService;/*** 定义切点*/@Pointcut("@annotation(cn.enjoyiot.backend.config.aop.LogAnno)")public void initLogAnno() {}@Around(value = "initLogAnno()")public Object saveSysLog(ProceedingJoinPoint joinPoint) {SysLog sysLog = new SysLog();MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取切入点所在的方法Method method = signature.getMethod();LogAnno operation = method.getAnnotation(LogAnno.class);if (operation != null) {String value = operation.desc();sysLog.setOperation(value);//保存获取的操作}//获取请求的类名String className = joinPoint.getTarget().getClass().getName();//获取请求的方法名String methodName = method.getName();sysLog.setMethod(className + "." + methodName);//请求的参数Object[] args = joinPoint.getArgs();//将参数所在的数组转换成jsonString params = "";try {if (args.length > 0) {for (Object p : args) {if(checkLogParam(p)){params = params + JSONObject.toJSONString(p) + " ";}}}} catch (Exception e) {e.printStackTrace();}sysLog.setParams(params);HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();sysLog.setIp(IpUtil.getRealIP(request));sysLog.setUri(request.getRequestURI());SysUser sysUser = SessionUtil.getSysUser();if (sysUser != null) {sysLog.setUserId(sysUser.getId());sysLog.setUsername(sysUser.getUsername());sysLog.setAccount(sysUser.getAccount());}LocalDateTime begin = LocalDateTime.now();/*执行目标方法*/try {Object result = joinPoint.proceed();return result;} catch (Throwable e) {e.printStackTrace();sysLog.setExceptionMessage(e.getMessage());return RespUtil.respErr(e.getMessage());} finally {LocalDateTime end = LocalDateTime.now();long sec = Duration.between(begin, end).getSeconds();sysLog.setCreateTime(end);sysLog.setTime(sec);sysLogService.save(sysLog);}}private boolean checkLogParam(Object p) {if(p==null){return false;}if(StringUtils.isEmpty(p)){return false;}if(p instanceof HttpServletRequest){return false;}else if(p instanceof HttpServletResponse){return false;}else if(p instanceof InputStreamSource){return false;}return true;}}
3 注解使用
@PostMapping(value = "/couponList")@LogAnno(desc="账户列表")@PreAuthorize("hasPermission(filterObject,'accountList')")public String accountList(@RequestBody PageParam param) {return "test";}