后端:增加用户访问日志
parent
532daf6299
commit
6f0a002ec3
|
@ -2,6 +2,7 @@ package cn.iocoder.mall.user.application.config;
|
||||||
|
|
||||||
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
|
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
|
||||||
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
|
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
|
||||||
|
import cn.iocoder.mall.user.sdk.interceptor.UserAccessLogInterceptor;
|
||||||
import cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor;
|
import cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -18,13 +19,18 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
public class MVCConfiguration implements WebMvcConfigurer {
|
public class MVCConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserSecurityInterceptor securityInterceptor;
|
private UserSecurityInterceptor userSecurityInterceptor;
|
||||||
|
@Autowired
|
||||||
|
private UserAccessLogInterceptor userAccessLogInterceptor;
|
||||||
@Autowired
|
@Autowired
|
||||||
private AdminSecurityInterceptor adminSecurityInterceptor;
|
private AdminSecurityInterceptor adminSecurityInterceptor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(securityInterceptor).addPathPatterns("/users/**"); // 只拦截我们定义的接口
|
// 用户
|
||||||
|
registry.addInterceptor(userAccessLogInterceptor).addPathPatterns("/users/**");
|
||||||
|
registry.addInterceptor(userSecurityInterceptor).addPathPatterns("/users/**"); // 只拦截我们定义的接口
|
||||||
|
// 管理员
|
||||||
registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**"); // 只拦截我们定义的接口
|
registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**"); // 只拦截我们定义的接口
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package cn.iocoder.mall.user.sdk.interceptor;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.util.HttpUtil;
|
||||||
|
import cn.iocoder.mall.user.service.api.UserAccessLogService;
|
||||||
|
import cn.iocoder.mall.user.service.api.dto.UserAccessLogAddDTO;
|
||||||
|
import com.alibaba.dubbo.config.annotation.Reference;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问日志拦截器
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class UserAccessLogInterceptor extends HandlerInterceptorAdapter {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始时间
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<Date> START_TIME = new ThreadLocal<>();
|
||||||
|
/**
|
||||||
|
* 管理员编号
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<Integer> USER_ID = new ThreadLocal<>();
|
||||||
|
|
||||||
|
@Reference
|
||||||
|
private UserAccessLogService userAccessLogService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
// 记录当前时间
|
||||||
|
START_TIME.set(new Date());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||||
|
UserAccessLogAddDTO accessLog = new UserAccessLogAddDTO();
|
||||||
|
try {
|
||||||
|
accessLog.setUserId(USER_ID.get());
|
||||||
|
if (accessLog.getUserId() == null) {
|
||||||
|
accessLog.setUserId(UserAccessLogAddDTO.USER_ID_NULL);
|
||||||
|
}
|
||||||
|
accessLog.setUri(request.getRequestURI()); // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
|
||||||
|
accessLog.setQueryString(HttpUtil.buildQueryString(request));
|
||||||
|
accessLog.setMethod(request.getMethod());
|
||||||
|
accessLog.setUserAgent(HttpUtil.getUserAgent(request));
|
||||||
|
accessLog.setIp(HttpUtil.getIp(request));
|
||||||
|
accessLog.setStartTime(START_TIME.get());
|
||||||
|
accessLog.setResponseTime((int) (System.currentTimeMillis() - accessLog.getStartTime().getTime()));// 默认响应时间设为0
|
||||||
|
userAccessLogService.addUserAccessLog(accessLog);
|
||||||
|
// TODO 提升:暂时不考虑 ELK 的方案。而是基于 MySQL 存储。如果访问日志比较多,需要定期归档。
|
||||||
|
} catch (Throwable th) {
|
||||||
|
logger.error("[afterCompletion][插入管理员访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
|
||||||
|
} finally {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUserId(Integer userId) {
|
||||||
|
USER_ID.set(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
START_TIME.remove();
|
||||||
|
USER_ID.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -40,6 +40,13 @@ public class UserSecurityInterceptor extends HandlerInterceptorAdapter {
|
||||||
// 添加到 SecurityContext
|
// 添加到 SecurityContext
|
||||||
UserSecurityContext context = new UserSecurityContext(authentication.getUserId());
|
UserSecurityContext context = new UserSecurityContext(authentication.getUserId());
|
||||||
UserSecurityContextHolder.setContext(context);
|
UserSecurityContextHolder.setContext(context);
|
||||||
|
// 同时也记录管理员编号到 AdminAccessLogInterceptor 中。因为:
|
||||||
|
// AdminAccessLogInterceptor 需要在 AdminSecurityInterceptor 之前执行,这样记录的访问日志才健全
|
||||||
|
// AdminSecurityInterceptor 执行后,会移除 AdminSecurityContext 信息,这就导致 AdminAccessLogInterceptor 无法获得管理员编号
|
||||||
|
// 因此,这里需要进行记录
|
||||||
|
if (authentication.getUserId() != null) {
|
||||||
|
UserAccessLogInterceptor.setUserId(authentication.getUserId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 校验是否需要已授权
|
// 校验是否需要已授权
|
||||||
HandlerMethod method = (HandlerMethod) handler;
|
HandlerMethod method = (HandlerMethod) handler;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package cn.iocoder.mall.user.service.api;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.vo.CommonResult;
|
||||||
|
import cn.iocoder.mall.user.service.api.dto.UserAccessLogAddDTO;
|
||||||
|
|
||||||
|
public interface UserAccessLogService {
|
||||||
|
|
||||||
|
CommonResult<Boolean> addUserAccessLog(UserAccessLogAddDTO userAccessLogAddDTO);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package cn.iocoder.mall.user.service.api.dto;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户访问日志添加 DTO
|
||||||
|
*/
|
||||||
|
public class UserAccessLogAddDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户编号 - 空
|
||||||
|
*/
|
||||||
|
public static final Integer USER_ID_NULL = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户编号.
|
||||||
|
*
|
||||||
|
* 当用户为空时,该值为0
|
||||||
|
*/
|
||||||
|
@NotNull(message = "用户编号不能为空")
|
||||||
|
private Integer userId;
|
||||||
|
/**
|
||||||
|
* 访问地址
|
||||||
|
*/
|
||||||
|
@NotNull(message = "访问地址不能为空")
|
||||||
|
private String uri;
|
||||||
|
/**
|
||||||
|
* 参数
|
||||||
|
*/
|
||||||
|
@NotNull(message = "请求参数不能为空")
|
||||||
|
private String queryString;
|
||||||
|
/**
|
||||||
|
* http 方法
|
||||||
|
*/
|
||||||
|
@NotNull(message = "http 请求方法不能为空")
|
||||||
|
private String method;
|
||||||
|
/**
|
||||||
|
* User Agent
|
||||||
|
*/
|
||||||
|
@NotNull(message = "User-Agent 不能为空")
|
||||||
|
private String userAgent;
|
||||||
|
/**
|
||||||
|
* ip
|
||||||
|
*/
|
||||||
|
@NotNull(message = "ip 不能为空")
|
||||||
|
private String ip;
|
||||||
|
/**
|
||||||
|
* 请求时间
|
||||||
|
*/
|
||||||
|
@NotNull(message = "请求时间不能为空")
|
||||||
|
private Date startTime;
|
||||||
|
/**
|
||||||
|
* 响应时长 -- 毫秒级
|
||||||
|
*/
|
||||||
|
@NotNull(message = "响应时长不能为空")
|
||||||
|
private Integer responseTime;
|
||||||
|
|
||||||
|
public Integer getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setUserId(Integer userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setUri(String uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueryString() {
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setQueryString(String queryString) {
|
||||||
|
this.queryString = queryString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAgent() {
|
||||||
|
return userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setUserAgent(String userAgent) {
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setIp(String ip) {
|
||||||
|
this.ip = ip;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setStartTime(Date startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getResponseTime() {
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogAddDTO setResponseTime(Integer responseTime) {
|
||||||
|
this.responseTime = responseTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package cn.iocoder.mall.user.convert;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.user.dataobject.UserAccessLogDO;
|
||||||
|
import cn.iocoder.mall.user.service.api.dto.UserAccessLogAddDTO;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mappings;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserAccessLogConvert {
|
||||||
|
|
||||||
|
UserAccessLogConvert INSTANCE = Mappers.getMapper(UserAccessLogConvert.class);
|
||||||
|
|
||||||
|
@Mappings({})
|
||||||
|
UserAccessLogDO convert(UserAccessLogAddDTO adminAccessLogAddDTO);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package cn.iocoder.mall.user.dao;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.user.dataobject.UserAccessLogDO;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserAccessLogMapper {
|
||||||
|
|
||||||
|
void insert(UserAccessLogDO entity);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
package cn.iocoder.mall.user.dataobject;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.dataobject.BaseDO;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户访问日志 DO
|
||||||
|
*/
|
||||||
|
public class UserAccessLogDO extends BaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编号
|
||||||
|
*/
|
||||||
|
private Integer id;
|
||||||
|
/**
|
||||||
|
* 用户编号.
|
||||||
|
*
|
||||||
|
* 当用户编号为空时,该值为0
|
||||||
|
*/
|
||||||
|
private Integer userId;
|
||||||
|
/**
|
||||||
|
* 访问地址
|
||||||
|
*/
|
||||||
|
private String uri;
|
||||||
|
/**
|
||||||
|
* 参数
|
||||||
|
*/
|
||||||
|
private String queryString;
|
||||||
|
/**
|
||||||
|
* http 方法
|
||||||
|
*/
|
||||||
|
private String method;
|
||||||
|
/**
|
||||||
|
* userAgent
|
||||||
|
*/
|
||||||
|
private String userAgent;
|
||||||
|
/**
|
||||||
|
* ip
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
/**
|
||||||
|
* 请求时间
|
||||||
|
*/
|
||||||
|
private Date startTime;
|
||||||
|
/**
|
||||||
|
* 响应时长 -- 毫秒级
|
||||||
|
*/
|
||||||
|
private Integer responseTime;
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setId(Integer id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setUserId(Integer userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setUri(String uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQueryString() {
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setQueryString(String queryString) {
|
||||||
|
this.queryString = queryString;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setMethod(String method) {
|
||||||
|
this.method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAgent() {
|
||||||
|
return userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setUserAgent(String userAgent) {
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setIp(String ip) {
|
||||||
|
this.ip = ip;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setStartTime(Date startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getResponseTime() {
|
||||||
|
return responseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAccessLogDO setResponseTime(Integer responseTime) {
|
||||||
|
this.responseTime = responseTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package cn.iocoder.mall.user.service;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.util.StringUtil;
|
||||||
|
import cn.iocoder.common.framework.vo.CommonResult;
|
||||||
|
import cn.iocoder.mall.user.convert.UserAccessLogConvert;
|
||||||
|
import cn.iocoder.mall.user.dao.UserAccessLogMapper;
|
||||||
|
import cn.iocoder.mall.user.dataobject.UserAccessLogDO;
|
||||||
|
import cn.iocoder.mall.user.service.api.UserAccessLogService;
|
||||||
|
import cn.iocoder.mall.user.service.api.dto.UserAccessLogAddDTO;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@com.alibaba.dubbo.config.annotation.Service(validation = "true")
|
||||||
|
public class UserAccessLogServiceImpl implements UserAccessLogService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求参数最大长度。
|
||||||
|
*/
|
||||||
|
private static final Integer QUERY_STRING_MAX_LENGTH = 4096;
|
||||||
|
/**
|
||||||
|
* 请求地址最大长度。
|
||||||
|
*/
|
||||||
|
private static final Integer URI_MAX_LENGTH = 4096;
|
||||||
|
/**
|
||||||
|
* User-Agent 最大长度。
|
||||||
|
*/
|
||||||
|
private static final Integer USER_AGENT_MAX_LENGTH = 1024;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserAccessLogMapper userAccessLogMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<Boolean> addUserAccessLog(UserAccessLogAddDTO userAccessLogAddDTO) {
|
||||||
|
// 创建 UserAccessLogDO
|
||||||
|
UserAccessLogDO accessLog = UserAccessLogConvert.INSTANCE.convert(userAccessLogAddDTO);
|
||||||
|
accessLog.setCreateTime(new Date());
|
||||||
|
// 截取最大长度
|
||||||
|
if (accessLog.getUri().length() > URI_MAX_LENGTH) {
|
||||||
|
accessLog.setUri(StringUtil.substring(accessLog.getUri(), URI_MAX_LENGTH));
|
||||||
|
}
|
||||||
|
if (accessLog.getQueryString().length() > QUERY_STRING_MAX_LENGTH) {
|
||||||
|
accessLog.setQueryString(StringUtil.substring(accessLog.getQueryString(), QUERY_STRING_MAX_LENGTH));
|
||||||
|
}
|
||||||
|
if (accessLog.getUserAgent().length() > USER_AGENT_MAX_LENGTH) {
|
||||||
|
accessLog.setUserAgent(StringUtil.substring(accessLog.getUserAgent(), USER_AGENT_MAX_LENGTH));
|
||||||
|
}
|
||||||
|
// 插入
|
||||||
|
userAccessLogMapper.insert(accessLog);
|
||||||
|
// 返回成功
|
||||||
|
return CommonResult.success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="cn.iocoder.mall.user.dao.UserAccessLogMapper">
|
||||||
|
|
||||||
|
<!--<sql id="FIELDS">-->
|
||||||
|
<!--id, username, nickname, password, status,-->
|
||||||
|
<!--create_time-->
|
||||||
|
<!--</sql>-->
|
||||||
|
|
||||||
|
<insert id="insert" parameterType="UserAccessLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||||
|
INSERT INTO user_access_log (
|
||||||
|
user_id, uri, query_string, method, user_agent,
|
||||||
|
ip, start_time, response_time, create_time
|
||||||
|
) VALUES (
|
||||||
|
#{userId}, #{uri}, #{queryString}, #{method}, #{userAgent},
|
||||||
|
#{ip}, #{startTime}, #{responseTime}, #{createTime}
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
</mapper>
|
Loading…
Reference in New Issue