后端:增加用户访问日志

This commit is contained in:
YunaiV 2019-03-19 06:25:58 +08:00
parent 532daf6299
commit 6f0a002ec3
10 changed files with 470 additions and 2 deletions

View File

@ -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/**"); // 只拦截我们定义的接口
} }

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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>