基于AOP实现访客日志记录
1.准备所需的日志表
1 2 3 4 5 6 7 8 9 10 11
| create table t_visit_log ( id int auto_increment comment 'id' primary key, page varchar(50) null comment '访问页面', ip_address varchar(50) null comment '访问ip', ip_source varchar(50) null comment '访问地址', os varchar(50) null comment '操作系统', browser varchar(50) null comment '浏览器', create_time datetime not null comment '访问时间' );
|
2.pom依赖
…
3.实体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
@Data public class VisitLog {
@TableId(type = IdType.AUTO) private Integer id;
private String page;
private String ipAddress;
private String ipSource;
private String os;
private String browser;
@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;
}
|
4.自定义日志注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface VisitLogger {
String value() default ""; }
|
5.Spring实例获取工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public final class SpringUtils implements BeanFactoryPostProcessor {
private static ConfigurableListableBeanFactory beanFactory;
@Override public void postProcessBeanFactory(@NotNull ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; }
@SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); }
public static <T> T getBean(Class<T> clz) throws BeansException { return beanFactory.getBean(clz); }
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } }
|
6.IP地址工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| package com.ehzyil.utils;
import com.ehzyil.exception.ServiceException; import org.lionsoul.ip2region.xdb.Searcher; import org.springframework.core.io.ClassPathResource; import org.springframework.util.FileCopyUtils; import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Objects;
@SuppressWarnings("all") public class IpUtils {
private static Searcher searcher;
static { try { InputStream inputStream = new ClassPathResource("/ipdb/ip2region.xdb").getInputStream(); byte[] cBuff = FileCopyUtils.copyToByteArray(inputStream); searcher = Searcher.newWithBuffer(cBuff); } catch (IOException e) { throw new ServiceException("ip2region.xdb加载失败"); }
}
public static String getIpAddress(HttpServletRequest request) { String ip; try { 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.getHeader("Proxy-Client-IP"); } 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("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) { InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { throw new UnknownHostException("无法确定主机的IP地址"); } ip = inet.getHostAddress(); } } if (!StringUtils.hasText(ip) && Objects.requireNonNull(ip).length() > 15) { int idx = ip.indexOf(","); if (idx > 0) { ip = ip.substring(0, idx); } } } catch (Exception e) { ip = ""; } return ip; }
public static String getIpSource(String ip) { try { String address = searcher.searchByStr(ip); if (StringUtils.hasText(address)) { address = address.replace("|0", ""); address = address.replace("0|", ""); return address; } return address; } catch (Exception e) { return ""; } }
}
|
7.线程池配置
ThreadPoolProperties类是一个自定义的属性类,用于配置线程池的相关属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.ican.config.properties;
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration;
@Data @Configuration @ConfigurationProperties(prefix = "thread.pool") public class ThreadPoolProperties {
private int corePoolSize;
private int maxPoolSize;
private int queueCapacity;
private int keepAliveSeconds; }
|
线程池配置
threadPoolTaskExecutor()方法,使用@Bean注解将其声明为一个Bean。该方法返回了一个ThreadPoolTaskExecutor对象,用于创建线程池。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
@Configuration public class ThreadPoolConfig {
@Autowired private ThreadPoolProperties threadPoolProperties;
@Bean public ThreadPoolTaskExecutor threadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(threadPoolProperties.getCorePoolSize()); executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize()); executor.setQueueCapacity(threadPoolProperties.getQueueCapacity()); executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds()); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; }
@Bean(name = "scheduledExecutorService") protected ScheduledExecutorService scheduledExecutorService() { return new ScheduledThreadPoolExecutor(threadPoolProperties.getCorePoolSize(), new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), new ThreadPoolExecutor.CallerRunsPolicy()) { @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); ThreadUtils.printException(r, t); } }; }
}
|
使用了ThreadPoolExecutor.CallerRunsPolicy(),表示当线程池无法接受新任务时,将任务回退到调用者线程中执行。
8.异步任务配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.ehzyil.manager;
import com.ehzyil.utils.SpringUtils; import com.ehzyil.utils.ThreadUtils;
import java.util.TimerTask; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;
public class AsyncManager {
private AsyncManager() { }
private static final AsyncManager INSTANCE = new AsyncManager();
public static AsyncManager getInstance() { return INSTANCE; }
private final ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
public void execute(TimerTask task) { executor.schedule(task, 10, TimeUnit.MILLISECONDS); }
public void shutdown() { ThreadUtils.shutdownAndAwaitTermination(executor); }
}
|
线程工具类ThreadUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| package com.ehzyil.utils;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
public class ThreadUtils {
private static final Logger logger = LoggerFactory.getLogger(ThreadUtils.class);
private static final long OVERTIME = 120;
public static void shutdownAndAwaitTermination(ExecutorService pool) { if (pool != null && !pool.isShutdown()) { pool.shutdown(); try { if (!pool.awaitTermination(OVERTIME, TimeUnit.SECONDS)) { pool.shutdownNow(); if (!pool.awaitTermination(OVERTIME, TimeUnit.SECONDS)) { logger.info("Pool did not terminate"); } } } catch (InterruptedException ie) { pool.shutdownNow(); Thread.currentThread().interrupt(); } } }
public static void printException(Runnable r, Throwable t) { if (t == null && r instanceof Future<?>) { try { Future<?> future = (Future<?>) r; if (future.isDone()) { future.get(); } } catch (CancellationException ce) { t = ce; } catch (ExecutionException ee) { t = ee.getCause(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } if (t != null) { logger.error(t.getMessage(), t); } } }
|
9.任务工厂配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public class AsyncFactory {
public static TimerTask recordVisit(VisitLog visitLog) { return new TimerTask() { @Override public void run() { SpringUtils.getBean(VisitLogService.class).saveVisitLog(visitLog); } }; } }
|
核心切面配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
@Aspect @Component public class VisitLogAspect {
@Pointcut("@annotation(com.ehzyil.annotation.VisitLogger)") public void visitLogPointCut() { }
@AfterReturning(value = "visitLogPointCut()", returning = "result") public void doAfterReturning(JoinPoint joinPoint, Object result) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); VisitLogger visitLogger = method.getAnnotation(VisitLogger.class); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); VisitLog visitLog = new VisitLog(); String ipAddress = IpUtils.getIpAddress(request); String ipSource = IpUtils.getIpSource(ipAddress); Map<String, String> userAgentMap = UserAgentUtils.parseOsAndBrowser(request.getHeader("User-Agent")); visitLog.setIpAddress(ipAddress); visitLog.setIpSource(ipSource); visitLog.setOs(userAgentMap.get("os")); visitLog.setBrowser(userAgentMap.get("browser")); visitLog.setPage(visitLogger.value()); AsyncManager.getInstance().execute(AsyncFactory.recordVisit(visitLog)); }
}
|
日志Service层
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public interface VisitLogService extends IService<VisitLog> {
void saveVisitLog(VisitLog visitLog); }
|
1 2 3 4 5 6 7 8 9 10 11
| @Service public class VisitLogServiceImpl extends ServiceImpl<VisitLogMapper, VisitLog> implements VisitLogService {
@Autowired private VisitLogMapper visitLogMapper;
@Override public void saveVisitLog(VisitLog visitLog) { visitLogMapper.insert(visitLog); }
|
控制器测试
1 2 3 4 5 6 7 8 9 10 11
|
@VisitLogger(value = "首页") @ApiOperation(value = "查看首页文章列表") @GetMapping("/article/list") public Result<PageResult<ArticleHomeVO>> listArticleHomeVO() { return Result.success(articleService.listArticleHomeVO()); }
|