通常只要是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<>();
// 用于记录日志的Logger
public 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);
}
// 从请求属性中设置连接器ID
String _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实例
@Autowired
StringRedisTemplate stringRedisTemplate;
// 注入ApiMessageLogServiceImpl实例
@Autowired
ApiMessageLogServiceImpl apiMessageLogService;
/**
* 在请求处理前执行的方法
*
* @param request HTTP请求对象
* @param response HTTP响应对象
* @param handler 处理请求的处理程序
* @return 如果继续处理请求,则返回true;否则,返回false
* @throws Exception 如果在处理过程中发生异常
*/
@Override
public 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对象
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig());
return new CorsFilter(source);
}
/**
* 配置中电联拦截器的Bean
*
* @return ZdlInterceptor对象
*/
@Bean
public HandlerInterceptor getZdlInterceptor() {
return new ZdlInterceptor();
}
/**
* 配置拦截器
*/
@Override
public 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 {
@Autowired
private 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();
//将参数所在的数组转换成json
String 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";
}