From 6f0a002ec367e66a44c07f09f6ba0827ee57bcfa Mon Sep 17 00:00:00 2001 From: YunaiV <> Date: Tue, 19 Mar 2019 06:25:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=EF=BC=9A=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=AE=BF=E9=97=AE=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/config/MVCConfiguration.java | 10 +- .../interceptor/UserAccessLogInterceptor.java | 78 +++++++++++ .../interceptor/UserSecurityInterceptor.java | 7 + .../service/api/UserAccessLogService.java | 10 ++ .../service/api/dto/UserAccessLogAddDTO.java | 131 +++++++++++++++++ .../user/convert/UserAccessLogConvert.java | 17 +++ .../mall/user/dao/UserAccessLogMapper.java | 11 ++ .../mall/user/dataobject/UserAccessLogDO.java | 132 ++++++++++++++++++ .../service/UserAccessLogServiceImpl.java | 56 ++++++++ .../resources/mapper/UserAccessLogMapper.xml | 20 +++ 10 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserAccessLogInterceptor.java create mode 100644 user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/UserAccessLogService.java create mode 100644 user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/dto/UserAccessLogAddDTO.java create mode 100644 user/user-service-impl/src/main/java/cn/iocoder/mall/user/convert/UserAccessLogConvert.java create mode 100644 user/user-service-impl/src/main/java/cn/iocoder/mall/user/dao/UserAccessLogMapper.java create mode 100644 user/user-service-impl/src/main/java/cn/iocoder/mall/user/dataobject/UserAccessLogDO.java create mode 100644 user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/UserAccessLogServiceImpl.java create mode 100644 user/user-service-impl/src/main/resources/mapper/UserAccessLogMapper.xml diff --git a/user/user-application/src/main/java/cn/iocoder/mall/user/application/config/MVCConfiguration.java b/user/user-application/src/main/java/cn/iocoder/mall/user/application/config/MVCConfiguration.java index 3fd525fa5..23e561992 100644 --- a/user/user-application/src/main/java/cn/iocoder/mall/user/application/config/MVCConfiguration.java +++ b/user/user-application/src/main/java/cn/iocoder/mall/user/application/config/MVCConfiguration.java @@ -2,6 +2,7 @@ package cn.iocoder.mall.user.application.config; import cn.iocoder.common.framework.config.GlobalExceptionHandler; 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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @@ -18,13 +19,18 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; public class MVCConfiguration implements WebMvcConfigurer { @Autowired - private UserSecurityInterceptor securityInterceptor; + private UserSecurityInterceptor userSecurityInterceptor; + @Autowired + private UserAccessLogInterceptor userAccessLogInterceptor; @Autowired private AdminSecurityInterceptor adminSecurityInterceptor; @Override 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/**"); // 只拦截我们定义的接口 } diff --git a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserAccessLogInterceptor.java b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserAccessLogInterceptor.java new file mode 100644 index 000000000..c52646fcf --- /dev/null +++ b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserAccessLogInterceptor.java @@ -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 START_TIME = new ThreadLocal<>(); + /** + * 管理员编号 + */ + private static final ThreadLocal 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(); + } + +} diff --git a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserSecurityInterceptor.java b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserSecurityInterceptor.java index b11c643b8..73605789a 100644 --- a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserSecurityInterceptor.java +++ b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserSecurityInterceptor.java @@ -40,6 +40,13 @@ public class UserSecurityInterceptor extends HandlerInterceptorAdapter { // 添加到 SecurityContext UserSecurityContext context = new UserSecurityContext(authentication.getUserId()); UserSecurityContextHolder.setContext(context); + // 同时也记录管理员编号到 AdminAccessLogInterceptor 中。因为: + // AdminAccessLogInterceptor 需要在 AdminSecurityInterceptor 之前执行,这样记录的访问日志才健全 + // AdminSecurityInterceptor 执行后,会移除 AdminSecurityContext 信息,这就导致 AdminAccessLogInterceptor 无法获得管理员编号 + // 因此,这里需要进行记录 + if (authentication.getUserId() != null) { + UserAccessLogInterceptor.setUserId(authentication.getUserId()); + } } // 校验是否需要已授权 HandlerMethod method = (HandlerMethod) handler; diff --git a/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/UserAccessLogService.java b/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/UserAccessLogService.java new file mode 100644 index 000000000..c840b8ffc --- /dev/null +++ b/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/UserAccessLogService.java @@ -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 addUserAccessLog(UserAccessLogAddDTO userAccessLogAddDTO); + +} \ No newline at end of file diff --git a/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/dto/UserAccessLogAddDTO.java b/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/dto/UserAccessLogAddDTO.java new file mode 100644 index 000000000..bccb98895 --- /dev/null +++ b/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/dto/UserAccessLogAddDTO.java @@ -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; + } + +} \ No newline at end of file diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/convert/UserAccessLogConvert.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/convert/UserAccessLogConvert.java new file mode 100644 index 000000000..a6e7ddb5e --- /dev/null +++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/convert/UserAccessLogConvert.java @@ -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); + +} \ No newline at end of file diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/dao/UserAccessLogMapper.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/dao/UserAccessLogMapper.java new file mode 100644 index 000000000..c86c58b40 --- /dev/null +++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/dao/UserAccessLogMapper.java @@ -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); + +} diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/dataobject/UserAccessLogDO.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/dataobject/UserAccessLogDO.java new file mode 100644 index 000000000..4c10fa920 --- /dev/null +++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/dataobject/UserAccessLogDO.java @@ -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; + } + +} \ No newline at end of file diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/UserAccessLogServiceImpl.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/UserAccessLogServiceImpl.java new file mode 100644 index 000000000..7c409586e --- /dev/null +++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/UserAccessLogServiceImpl.java @@ -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 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); + } + +} \ No newline at end of file diff --git a/user/user-service-impl/src/main/resources/mapper/UserAccessLogMapper.xml b/user/user-service-impl/src/main/resources/mapper/UserAccessLogMapper.xml new file mode 100644 index 000000000..4168673ac --- /dev/null +++ b/user/user-service-impl/src/main/resources/mapper/UserAccessLogMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + 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} + ) + + + \ No newline at end of file