es = request.getParameterNames();
+ if (!es.hasMoreElements()) {
+ return "";
+ }
+ String parameterName, parameterValue;
+ StringBuilder params = new StringBuilder();
+ while (es.hasMoreElements()) {
+ parameterName = es.nextElement();
+ parameterValue = request.getParameter(parameterName);
+ params.append(parameterName).append("=").append(parameterValue).append("&");
+ }
+ return params.deleteCharAt(params.length() - 1).toString();
+ }
+
+ /**
+ * Return the path within the web application for the given request.
+ * Detects include request URL if called within a RequestDispatcher include.
+ *
+ * For example, for a request to URL
+ *
+ * http://www.somehost.com/myapp/my/url.jsp
,
+ *
+ * for an application deployed to /mayapp
(the application's context path), this method would return
+ *
+ * /my/url.jsp
.
+ *
+ * 该方法,是从 Shiro 源码中,扣出来。add by 芋艿
+ *
+ * @param request current HTTP request
+ * @return the path within the web application
+ */
+ public static String getPathWithinApplication(HttpServletRequest request) {
+ String contextPath = getContextPath(request);
+ String requestUri = getRequestUri(request);
+ if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
+ // Normal case: URI contains context path.
+ String path = requestUri.substring(contextPath.length());
+ return (StringUtils.hasText(path) ? path : "/");
+ } else {
+ // Special case: rather unusual.
+ return requestUri;
+ }
+ }
+
+ /**
+ * Return the request URI for the given request, detecting an include request
+ * URL if called within a RequestDispatcher include.
+ * As the value returned by request.getRequestURI()
is not
+ * decoded by the servlet container, this method will decode it.
+ *
The URI that the web container resolves should be correct, but some
+ * containers like JBoss/Jetty incorrectly include ";" strings like ";jsessionid"
+ * in the URI. This method cuts off such incorrect appendices.
+ *
+ * @param request current HTTP request
+ * @return the request URI
+ */
+ public static String getRequestUri(HttpServletRequest request) {
+ String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
+ if (uri == null) {
+ uri = request.getRequestURI();
+ }
+ return normalize(decodeAndCleanUriString(request, uri));
+ }
+
+ /**
+ * Normalize a relative URI path that may have relative values ("/./",
+ * "/../", and so on ) it it. WARNING - This method is
+ * useful only for normalizing application-generated paths. It does not
+ * try to perform security checks for malicious input.
+ * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
+ * Tomcat trunk, r939305
+ *
+ * @param path Relative path to be normalized
+ * @return normalized path
+ */
+ public static String normalize(String path) {
+ return normalize(path, true);
+ }
+
+ /**
+ * Normalize a relative URI path that may have relative values ("/./",
+ * "/../", and so on ) it it. WARNING - This method is
+ * useful only for normalizing application-generated paths. It does not
+ * try to perform security checks for malicious input.
+ * Normalize operations were was happily taken from org.apache.catalina.util.RequestUtil in
+ * Tomcat trunk, r939305
+ *
+ * @param path Relative path to be normalized
+ * @param replaceBackSlash Should '\\' be replaced with '/'
+ * @return normalized path
+ */
+ private static String normalize(String path, boolean replaceBackSlash) {
+
+ if (path == null)
+ return null;
+
+ // Create a place for the normalized path
+ String normalized = path;
+
+ if (replaceBackSlash && normalized.indexOf('\\') >= 0)
+ normalized = normalized.replace('\\', '/');
+
+ if (normalized.equals("/."))
+ return "/";
+
+ // Add a leading "/" if necessary
+ if (!normalized.startsWith("/"))
+ normalized = "/" + normalized;
+
+ // Resolve occurrences of "//" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("//");
+ if (index < 0)
+ break;
+ normalized = normalized.substring(0, index) +
+ normalized.substring(index + 1);
+ }
+
+ // Resolve occurrences of "/./" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("/./");
+ if (index < 0)
+ break;
+ normalized = normalized.substring(0, index) +
+ normalized.substring(index + 2);
+ }
+
+ // Resolve occurrences of "/../" in the normalized path
+ while (true) {
+ int index = normalized.indexOf("/../");
+ if (index < 0)
+ break;
+ if (index == 0)
+ return (null); // Trying to go outside our context
+ int index2 = normalized.lastIndexOf('/', index - 1);
+ normalized = normalized.substring(0, index2) +
+ normalized.substring(index + 3);
+ }
+
+ // Return the normalized path that we have completed
+ return (normalized);
+ }
+
+ /**
+ * Decode the supplied URI string and strips any extraneous portion after a ';'.
+ *
+ * @param request the incoming HttpServletRequest
+ * @param uri the application's URI string
+ * @return the supplied URI string stripped of any extraneous portion after a ';'.
+ */
+ private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
+ uri = decodeRequestString(request, uri);
+ int semicolonIndex = uri.indexOf(';');
+ return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
+ }
+
+ /**
+ * Return the context path for the given request, detecting an include request
+ * URL if called within a RequestDispatcher include.
+ *
As the value returned by request.getContextPath()
is not
+ * decoded by the servlet container, this method will decode it.
+ *
+ * @param request current HTTP request
+ * @return the context path
+ */
+ public static String getContextPath(HttpServletRequest request) {
+ String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE);
+ if (contextPath == null) {
+ contextPath = request.getContextPath();
+ }
+ if ("/".equals(contextPath)) {
+ // Invalid case, but happens for includes on Jetty: silently adapt it.
+ contextPath = "";
+ }
+ return decodeRequestString(request, contextPath);
+ }
+
+ /**
+ * Decode the given source string with a URLDecoder. The encoding will be taken
+ * from the request, falling back to the default "ISO-8859-1".
+ *
The default implementation uses URLDecoder.decode(input, enc)
.
+ *
+ * @param request current HTTP request
+ * @param source the String to decode
+ * @return the decoded String
+ * @see #DEFAULT_CHARACTER_ENCODING
+ * @see javax.servlet.ServletRequest#getCharacterEncoding
+ * @see java.net.URLDecoder#decode(String, String)
+ * @see java.net.URLDecoder#decode(String)
+ */
+ @SuppressWarnings({"deprecation"})
+ public static String decodeRequestString(HttpServletRequest request, String source) {
+ String enc = determineEncoding(request);
+ try {
+ return URLDecoder.decode(source, enc);
+ } catch (UnsupportedEncodingException ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Could not decode request string [" + source + "] with encoding '" + enc +
+ "': falling back to platform default encoding; exception message: " + ex.getMessage());
+ }
+ return URLDecoder.decode(source);
+ }
+ }
+
+ /**
+ * Determine the encoding for the given request.
+ * Can be overridden in subclasses.
+ *
The default implementation checks the request's
+ * {@link ServletRequest#getCharacterEncoding() character encoding}, and if that
+ * null
, falls back to the {@link #DEFAULT_CHARACTER_ENCODING}.
+ *
+ * @param request current HTTP request
+ * @return the encoding for the request (never null
)
+ * @see javax.servlet.ServletRequest#getCharacterEncoding()
+ */
+ protected static String determineEncoding(HttpServletRequest request) {
+ String enc = request.getCharacterEncoding();
+ if (enc == null) {
+ enc = DEFAULT_CHARACTER_ENCODING;
+ }
+ return enc;
+ }
+
}
\ No newline at end of file
diff --git a/common/common-framework/src/main/java/cn/iocoder/common/framework/util/StringUtil.java b/common/common-framework/src/main/java/cn/iocoder/common/framework/util/StringUtil.java
index 8e98169ed..c81be713f 100644
--- a/common/common-framework/src/main/java/cn/iocoder/common/framework/util/StringUtil.java
+++ b/common/common-framework/src/main/java/cn/iocoder/common/framework/util/StringUtil.java
@@ -27,4 +27,8 @@ public class StringUtil {
return array;
}
+ public static String substring(String str, int start) {
+ return org.apache.commons.lang3.StringUtils.substring(str, start);
+ }
+
}
\ No newline at end of file
From 6f0a002ec367e66a44c07f09f6ba0827ee57bcfa Mon Sep 17 00:00:00 2001
From: YunaiV <>
Date: Tue, 19 Mar 2019 06:25:58 +0800
Subject: [PATCH 8/8] =?UTF-8?q?=E5=90=8E=E7=AB=AF=EF=BC=9A=E5=A2=9E?=
=?UTF-8?q?=E5=8A=A0=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