1.使用背景:
项目需要实现用户访问的功能菜单进行日志记录登记。
2.整体思路
1.获取到所有的请求(这里使用过滤器获取用户请求)。例如:请求:http://127.0.0.1/xxxx.yyyyyy.do?xxx=xxx
2.配置需要记录的功能和请求map[格式:请求=功能名称](这里使用监听器监听应用启动完后加载配置文件内容放入全局MAP中)。
例如:yyyyyy.do =系统配置
3.通过请求和配置的记录信息判断是否为需要记录的功能,符合则将其保存入库(这里开启多线程,保存记录)。
3.基础知识
参考:添加链接描述
4.功能实现
- 定义过滤器
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Date;
public class FunctionLogFilter implements Filter {
/**
* 初始化
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/**
* 过滤逻辑
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 过滤用户请求,判断是否登录
HttpServletRequest httpServletRequest = (HttpServletRequest)request;//转为HttpServletRequest
HttpServletResponse httpServletResponse = (HttpServletResponse)response;//转为HttpServletResponse
httpServletResponse.setContentType("text/html;charset=utf-8");
httpServletRequest.setCharacterEncoding("utf-8");
httpServletResponse.setCharacterEncoding("utf-8");
//1.获取当前的登录用户
OpuOmUser loginUser = SecurityContext.getCurrentUser();
//2.获取访问请求方法
String functionUrl = httpServletRequest.getServletPath();
//3.处理时间
Timestamp dealTime = new Timestamp(new Date().getTime());
//4.逻辑判断保存
ThreadPoolUtil.startCallable1(dealTime,functionUrl,loginUser,httpServletRequest);
chain.doFilter(request, response);
}
/**
* 銷毀
*/
@Override
public void destroy() {
}
}
- 将过虑器添加至springMVC配置文件中
/**
* 注册过滤器
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean filterRegist() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new FunctionLogFilter());
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
- 定义监听器
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
/**
* ApplicationStartedEvent:在刷新上下文之后,但在调用任何应用程序和命令行运行程序之前.
* 加载logurls.properties文件
*/
@Component /*这个要加上 不然spring不认识 就不能使用配置文件中发布的方式*/
public class FunctionLogListener implements ApplicationListener<ApplicationStartedEvent> {
public FunctionLogListener() {
}
@SuppressWarnings("unchecked")
public void contextInitialized() {
Properties properties = new Properties();
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("logurls.properties");
if (in == null) {
System.out.println("没有找到logurls.properties文件!");
} else {
try {
properties.load(new InputStreamReader(in, "utf-8"));
Set propertiesSet = properties.keySet();
Iterator ite = propertiesSet.iterator();
while (ite.hasNext()) {
String key = ite.next().toString();
ToolUtils.LOG_URLS_MAP.put(key.replaceAll("logUrls_key_", ""), properties.getProperty(key));//LOG_URLS_MAP 全局变量MAP
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
contextInitialized();
}
}
- 发布监听器(在spring boot配置文件中添加的方式)
#启动功能使用日志监听
#格式:org.springframework.context.ApplicationListener=监听器全类路径
org.springframework.context.ApplicationListener=com.xxx.xxxx.listener.FunctionLogListener
- 定义线程类
package com.xxx.xxx.thread;
import com.xxx.xxx.framework.security.user.OpuOmUser;
import com.xxx.xxx.domain.XmjgFunctionLog;
import com.xxx.xxx.service.FunctionLogService;
import com.xxx.xxx.service.impl.FunctionLogServiceImpl;
import com.xxx.xxx.util.SpringContextUtil;
import org.apache.commons.lang3.StringUtils;
import java.sql.Timestamp;
import java.util.concurrent.Callable;
/**
*功能模块操作日志信息保存线程
*/
public class FunctionLogDataCallable implements Callable<Long> {
private Timestamp dealTime; //操作时间
private String functionName;// 功能模块名称
private String functionUrl;// 功能模块访问url
private String clientIp; //客户端ip
private OpuOmUser opuOmUser;//用户对象
//多线程下 无法使用注解方式注入,解决方案为手动获取
private FunctionLogService functionLogService= SpringContextUtil.getBean("functionLogServiceImpl", FunctionLogServiceImpl.class);
public FunctionLogDataCallable(Timestamp dealTime, String functionUrl, String functionName, OpuOmUser opuOmUser, String clientIp ) {
this.dealTime = dealTime;
this.functionUrl = functionUrl;
this.functionName = functionName;
this.clientIp = clientIp;
this.opuOmUser = opuOmUser;
}
@Override
public Long call() throws Exception {
XmjgFunctionLog form=new XmjgFunctionLog();
if(null !=opuOmUser){
form.setUserLoginName(opuOmUser.getLoginName());
form.setUserCode(opuOmUser.getUserId());
form.setUserName(opuOmUser.getUserName());
}
form.setDealTime(dealTime);
if(StringUtils.isNotEmpty(clientIp)) {
form.setClientIp(clientIp);
}
if(StringUtils.isNotEmpty(functionUrl)) {
form.setFunctionUrl(functionUrl);
}
if(StringUtils.isNotEmpty(functionName)) {
form.setFunctionName(functionName);
}
functionLogService.saveFunctionLog(form);
return 1L;
}
}
- 定义线程池和业务逻辑判断工具类
package com.xxx.xxx.util;
import com.xxxx.xxxx.framework.security.user.OpuOmUser;
import com.xxx.xxx.thread.FunctionLogDataCallable;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolUtil {
private static ExecutorService executorService=null;
public static void startCallable1(Timestamp dealTime, String functionUrl, OpuOmUser loginUser, HttpServletRequest request) {
if(loginUser!=null) {
String clientIp=request.getRemoteAddr();
startCallable(dealTime,functionUrl,null,loginUser,clientIp);
}
}
public static void startCallable(Timestamp dealTime, String functionUrl, String functionName, OpuOmUser loginUser, String clientIp) {
if(StringUtils.isEmpty(functionName) && StringUtils.isNotEmpty(functionUrl)) {
functionName=getFunctionNameByUrl(functionUrl);
}
if(StringUtils.isNotEmpty(functionName)) {
getInstance();//初始化线程池
executorService.submit(new FunctionLogDataCallable(dealTime, functionUrl, functionName,loginUser,clientIp));//线程执行
}
}
/**
* 初始化线程池
*/
private synchronized static void getInstance() {
if (executorService == null) {
synchronized(ThreadPoolUtil.class) {
if (executorService == null) {
executorService= Executors.newFixedThreadPool(10);
}
}
}
}
private static String getFunctionNameByUrl(String functionUrl) {
if(StringUtils.isNotEmpty(functionUrl) && ToolUtils.LOG_URLS_MAP.containsKey(functionUrl)) {
return ToolUtils.LOG_URLS_MAP.get(functionUrl);
}
return null;
}
}
解决多线程下无法使用自动注解(@Autowired)问题
-
原因:每一个新的线程开始,不在spring容器内,所以无法使用
-
解决方式:手动获取bean,进行注入。
-
具体实现:
定义一个类去实现ApplicationContextAware 接口获取应用的上下文环境信息,在该类中编写获取应用环境中的bean的方法,并将该类交给spring管理。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringContextUtil.applicationContext == null){
SpringContextUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}