- 后端:替换 swagger bootstrap ui ,一下子接口文档好看了。

- 后端:增加 swagger AutoConfiguration 配置类
- 后端:统一访问日志的记录
This commit is contained in:
YunaiV 2019-05-10 18:47:29 +08:00
parent 3ff9f1b326
commit 53fff39a6c
67 changed files with 899 additions and 894 deletions

View File

@ -90,6 +90,12 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>6.1.0</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,42 @@
package cn.iocoder.common.framework.constant;
/**
* Mall 全局枚举
*/
public interface MallConstants {
// 全局请求路径枚举类用于定义不同用户类型的根请求路径
/**
* 根路径 - 用户
*/
String ROOT_PATH_USER = "/users";
/**
* 根路径 - 管理员
*/
String ROOT_PATH_ADMIN = "/admins";
// 用户类型
/**
* 用户类型 - 用户
*/
Integer USER_TYPE_USER = 1;
/**
* 用户类型 - 管理员
*/
Integer USER_TYPE_ADMIN = 2;
// HTTP Request Attr
/**
* HTTP Request Attr - 用户编号
*/
String REQUEST_ATTR_USER_ID_KEY = "mall_user_id";
/**
* HTTP Request Attr - 用户类型
*/
String REQUEST_ATTR_USER_TYPE_KEY = "mall_user_type";
/**
* HTTP Request Attr - Controller 执行返回
*/
String REQUEST_ATTR_COMMON_RESULT = "mall_common_result";
}

View File

@ -1,7 +1,6 @@
package cn.iocoder.common.framework.config; package cn.iocoder.common.framework.exception;
import cn.iocoder.common.framework.constant.SysErrorCodeEnum; import cn.iocoder.common.framework.constant.SysErrorCodeEnum;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.vo.CommonResult; import cn.iocoder.common.framework.vo.CommonResult;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@ -5,6 +5,11 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
/**
* Cors 过滤器
*
* 未来使用 {@link org.springframework.web.filter.CorsFilter} 替换
*/
public class CorsFilter implements Filter { public class CorsFilter implements Filter {
@Override @Override

View File

@ -44,7 +44,7 @@ public class HttpUtil {
*/ */
public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1"; public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
public static String obtainAccess(HttpServletRequest request) { public static String obtainAuthorization(HttpServletRequest request) {
String authorization = request.getHeader("Authorization"); String authorization = request.getHeader("Authorization");
if (!StringUtils.hasText(authorization)) { if (!StringUtils.hasText(authorization)) {
return null; return null;

View File

@ -0,0 +1,53 @@
package cn.iocoder.common.framework.util;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.vo.CommonResult;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import javax.servlet.ServletRequest;
import java.util.UUID;
public class MallUtil {
public static Integer getUserId(ServletRequest request) {
return (Integer) request.getAttribute(MallConstants.REQUEST_ATTR_USER_ID_KEY);
}
public static void setUserId(ServletRequest request, Integer userId) {
request.setAttribute(MallConstants.REQUEST_ATTR_USER_ID_KEY, userId);
}
public static Integer getUserType(ServletRequest request) {
return (Integer) request.getAttribute(MallConstants.REQUEST_ATTR_USER_TYPE_KEY);
}
public static void setUserType(ServletRequest request, Integer userType) {
request.setAttribute(MallConstants.REQUEST_ATTR_USER_TYPE_KEY, userType);
}
public static CommonResult getCommonResult(ServletRequest request) {
return (CommonResult) request.getAttribute(MallConstants.REQUEST_ATTR_COMMON_RESULT);
}
public static void setCommonResult(ServletRequest request, CommonResult result) {
request.setAttribute(MallConstants.REQUEST_ATTR_COMMON_RESULT, result);
}
/**
* 获得链路追踪编号
*
* 一般来说通过链路追踪编号可以将访问日志错误日志链路追踪日志logger 打印日志等结合在一起从而进行排错
*
* 默认情况下我们使用 Apache SkyWalking traceId 作为链路追踪编号当然可能会存在并未引入 Skywalking 的情况此时使用 UUID
*
* @return 链路追踪编号
*/
public static String getTraceId() {
String traceId = TraceContext.traceId();
if (StringUtil.hasText(traceId)) {
return traceId;
}
return UUID.randomUUID().toString();
}
}

View File

@ -5,7 +5,7 @@ import org.springframework.util.Assert;
import java.io.Serializable; import java.io.Serializable;
public class CommonResult<T> implements Serializable { public final class CommonResult<T> implements Serializable {
public static Integer CODE_SUCCESS = 0; public static Integer CODE_SUCCESS = 0;

View File

@ -47,6 +47,17 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -1,17 +0,0 @@
package cn.iocoder.mall.spring.boot.constant;
/**
* 全局请求路径枚举类用于定义不同用户类型的根请求路径
*/
public interface RootRequestPath {
/**
* 管理员
*/
String ADMIN = "/admins";
/**
* 用户
*/
String USER = "/users";
}

View File

@ -0,0 +1,57 @@
package cn.iocoder.mall.spring.boot.swagger;
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* 简单的 Swagger2 自动配置类
*
* 较为完善的可以了解 https://mvnrepository.com/artifact/com.spring4all/spring-boot-starter-swagger
*/
@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
@ConditionalOnProperty(prefix = "swagger", value = "enable", matchIfMissing = true) // 允许使用 swagger.enable=false 禁用 Swagger
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SwaggerProperties swaggerProperties() {
return new SwaggerProperties();
}
@Bean
public Docket createRestApi() {
SwaggerProperties properties = swaggerProperties();
// 创建 Docket 对象
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo(properties))
.select()
.apis(RequestHandlerSelectors.basePackage(properties.getBasePackage()))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo(SwaggerProperties properties) {
return new ApiInfoBuilder()
.title(properties.getTitle())
.description(properties.getDescription())
.version(properties.getVersion())
.build();
}
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.mall.spring.boot.swagger;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties("swagger")
public class SwaggerProperties {
private String title;
private String description;
private String version;
private String basePackage;
}

View File

@ -0,0 +1,64 @@
package cn.iocoder.mall.spring.boot.web;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.servlet.CorsFilter;
import cn.iocoder.mall.spring.boot.web.interceptor.AccessLogInterceptor;
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
import cn.iocoder.mall.spring.boot.web.handler.GlobalExceptionHandler;
import cn.iocoder.mall.spring.boot.web.handler.GlobalResponseBodyHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿未来可能考虑 REACTIVE
@ConditionalOnClass({DispatcherServlet.class, WebMvcConfigurer.class, // Spring MVC 容器
AdminSecurityInterceptor.class, AccessLogInterceptor.class}) // 有引入 system-sdk
public class AdminMVCAutoConfiguration implements WebMvcConfigurer {
@Bean
// @ConditionalOnMissingBean(AccessLogInterceptor.class)
public AccessLogInterceptor adminAccessLogInterceptor() {
return new AccessLogInterceptor();
}
@Bean
@ConditionalOnMissingBean(AdminSecurityInterceptor.class)
public AdminSecurityInterceptor adminSecurityInterceptor() {
return new AdminSecurityInterceptor();
}
@Bean
@ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
public GlobalResponseBodyHandler globalReturnValueHandler() {
return new GlobalResponseBodyHandler();
}
@Bean
@ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminAccessLogInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
registry.addInterceptor(adminSecurityInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
}
@Bean
@ConditionalOnMissingBean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CorsFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}

View File

@ -1,49 +0,0 @@
package cn.iocoder.mall.spring.boot.web;
import cn.iocoder.mall.admin.sdk.interceptor.AdminAccessLogInterceptor;
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
import cn.iocoder.mall.spring.boot.constant.RootRequestPath;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿未来可能考虑 REACTIVE
@ConditionalOnClass({DispatcherServlet.class, WebMvcConfigurer.class, // Spring MVC 容器
AdminSecurityInterceptor.class, AdminAccessLogInterceptor.class}) // 有引入 system-sdk
public class AdminMVCConfiguration implements WebMvcConfigurer {
@Bean
@ConditionalOnMissingBean(AdminSecurityInterceptor.class)
public AdminSecurityInterceptor adminSecurityInterceptor() {
return new AdminSecurityInterceptor();
}
@Bean
@ConditionalOnMissingBean(AdminAccessLogInterceptor.class)
public AdminAccessLogInterceptor adminAccessLogInterceptor() {
return new AdminAccessLogInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminAccessLogInterceptor()).addPathPatterns(RootRequestPath.ADMIN + "/**");
registry.addInterceptor(adminSecurityInterceptor()).addPathPatterns(RootRequestPath.ADMIN + "/**");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(RootRequestPath.USER + "/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true).maxAge(1800);
}
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.mall.spring.boot.web;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.servlet.CorsFilter;
import cn.iocoder.mall.spring.boot.web.interceptor.AccessLogInterceptor;
import cn.iocoder.mall.spring.boot.web.handler.GlobalExceptionHandler;
import cn.iocoder.mall.spring.boot.web.handler.GlobalResponseBodyHandler;
import cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿未来可能考虑 REACTIVE
@ConditionalOnClass({DispatcherServlet.class, WebMvcConfigurer.class, // Spring MVC 容器
UserSecurityInterceptor.class, // 有引入 user-sdk
AccessLogInterceptor.class}) // 有引入 system-sdk
public class UserMVCAutoConfiguration implements WebMvcConfigurer {
@Bean
// @ConditionalOnMissingBean(AccessLogInterceptor.class)
public AccessLogInterceptor userAccessLogInterceptor() {
return new AccessLogInterceptor();
}
@Bean
@ConditionalOnMissingBean(UserSecurityInterceptor.class)
public UserSecurityInterceptor userSecurityInterceptor() {
return new UserSecurityInterceptor();
}
@Bean
@ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
public GlobalResponseBodyHandler globalReturnValueHandler() {
return new GlobalResponseBodyHandler();
}
@Bean
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userAccessLogInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_USER + "/**");
registry.addInterceptor(userSecurityInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_USER + "/**");
}
@Bean
@ConditionalOnMissingBean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CorsFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}

View File

@ -1,49 +0,0 @@
package cn.iocoder.mall.spring.boot.web;
import cn.iocoder.mall.spring.boot.constant.RootRequestPath;
import cn.iocoder.mall.user.sdk.interceptor.UserAccessLogInterceptor;
import cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿未来可能考虑 REACTIVE
@ConditionalOnClass({DispatcherServlet.class, WebMvcConfigurer.class, // Spring MVC 容器
UserSecurityInterceptor.class, UserAccessLogInterceptor.class}) // 有引入 system-sdk
public class UserMVCConfiguration implements WebMvcConfigurer {
@Bean
@ConditionalOnMissingBean(UserAccessLogInterceptor.class)
public UserAccessLogInterceptor userAccessLogInterceptor() {
return new UserAccessLogInterceptor();
}
@Bean
@ConditionalOnMissingBean(UserSecurityInterceptor.class)
public UserSecurityInterceptor userSecurityInterceptor() {
return new UserSecurityInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userAccessLogInterceptor()).addPathPatterns(RootRequestPath.USER + "/**");
registry.addInterceptor(userSecurityInterceptor()).addPathPatterns(RootRequestPath.USER + "/**");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(RootRequestPath.USER + "/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true).maxAge(1800);
}
}

View File

@ -0,0 +1,70 @@
package cn.iocoder.mall.spring.boot.web.handler;
import cn.iocoder.common.framework.constant.SysErrorCodeEnum;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.vo.CommonResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
@ControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
// 逻辑异常
@ResponseBody
@ExceptionHandler(value = ServiceException.class)
public CommonResult serviceExceptionHandler(HttpServletRequest req, ServiceException ex) {
logger.debug("[serviceExceptionHandler]", ex);
return CommonResult.error(ex.getCode(), ex.getMessage());
}
// Spring MVC 参数不正确
@ResponseBody
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
return CommonResult.error(SysErrorCodeEnum.MISSING_REQUEST_PARAM_ERROR.getCode(), SysErrorCodeEnum.MISSING_REQUEST_PARAM_ERROR.getMessage() + ":" + ex.getMessage());
}
@ResponseBody
@ExceptionHandler(value = ConstraintViolationException.class)
public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
logger.info("[constraintViolationExceptionHandler]", ex);
// TODO 芋艿后续要想一个更好的方式
// 拼接详细报错
StringBuilder detailMessage = new StringBuilder("\n\n详细错误如下");
ex.getConstraintViolations().forEach(constraintViolation -> detailMessage.append("\n").append(constraintViolation.getMessage()));
return CommonResult.error(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(), SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getMessage()
+ detailMessage.toString());
}
@ResponseBody
@ExceptionHandler(value = Exception.class)
public CommonResult resultExceptionHandler(HttpServletRequest req, Exception e) {
logger.error("[resultExceptionHandler]", e);
// 返回
try {
addExceptionLog();
} catch (Throwable th) {
// TODO
}
return CommonResult.error(SysErrorCodeEnum.SYS_ERROR.getCode(), SysErrorCodeEnum.SYS_ERROR.getMessage());
}
// TODO 芋艿应该还有其它的异常需要进行翻译
@Async
public void addExceptionLog() {
}
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.mall.spring.boot.web.handler;
import cn.iocoder.common.framework.util.MallUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class GlobalResponseBodyHandler implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
if (returnType.getMethod() == null) {
return false;
}
return returnType.getMethod().getReturnType().isAssignableFrom(CommonResult.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
MallUtil.setCommonResult(((ServletServerHttpRequest) request).getServletRequest(), (CommonResult) body);
return body;
}
}

View File

@ -0,0 +1,97 @@
package cn.iocoder.mall.spring.boot.web.interceptor;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.MallUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.admin.api.SystemLogService;
import cn.iocoder.mall.admin.api.dto.AccessLogAddDTO;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
/**
* 访问日志拦截器
*/
@Component
public class AccessLogInterceptor extends HandlerInterceptorAdapter {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 开始时间
*/
private static final ThreadLocal<Date> START_TIME = new ThreadLocal<>();
@Reference(validation = "true", version = "${dubbo.consumer.AdminAccessLogService.version:1.0.0}")
private SystemLogService adminAccessLogService;
@Value("${spring.application.name}")
private String applicationName;
@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) {
AccessLogAddDTO accessLog = new AccessLogAddDTO();
try {
// 设置用户编号
accessLog.setUserId(MallUtil.getUserId(request));
if (accessLog.getUserId() == null) {
accessLog.setUserId(AccessLogAddDTO.USER_ID_NULL);
}
accessLog.setUserType(MallUtil.getUserType(request));
// 设置访问结果
CommonResult result = MallUtil.getCommonResult(request);
Assert.isTrue(result != null, "result 必须非空");
accessLog.setErrorCode(result.getCode())
.setErrorMessage(result.getMessage());
// 设置其它字段
accessLog.setTraceId(MallUtil.getTraceId())
.setApplicationName(applicationName)
.setUri(request.getRequestURI()) // TODO 提升如果想要优化可以使用 Swagger @ApiOperation 注解
.setQueryString(HttpUtil.buildQueryString(request))
.setMethod(request.getMethod())
.setUserAgent(HttpUtil.getUserAgent(request))
.setIp(HttpUtil.getIp(request))
.setStartTime(START_TIME.get())
.setResponseTime((int) (System.currentTimeMillis() - accessLog.getStartTime().getTime())); // 默认响应时间设为 0
// 执行插入
addAccessLog(accessLog);
// TODO 提升暂时不考虑 ELK 的方案而是基于 MySQL 存储如果访问日志比较多需要定期归档
} catch (Throwable th) {
logger.error("[afterCompletion][插入访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
} finally {
clear();
}
}
@Async // 异步入库
public void addAccessLog(AccessLogAddDTO accessLog) {
try {
adminAccessLogService.addAccessLog(accessLog);
} catch (Throwable th) {
logger.error("[addAccessLog][插入访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
}
}
private static void clear() {
START_TIME.remove();
}
}

View File

@ -1,3 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.spring.boot.web.AdminMVCConfiguration, \ cn.iocoder.mall.spring.boot.web.AdminMVCAutoConfiguration, \
cn.iocoder.mall.spring.boot.web.UserMVCConfiguration cn.iocoder.mall.spring.boot.web.UserMVCAutoConfiguration, \
cn.iocoder.mall.spring.boot.swagger.SwaggerAutoConfiguration

View File

@ -1,6 +1,6 @@
package cn.iocoder.mall.order.application.config; package cn.iocoder.mall.order.application.config;
import cn.iocoder.common.framework.config.GlobalExceptionHandler; import cn.iocoder.common.framework.exception.GlobalExceptionHandler;
import cn.iocoder.common.framework.servlet.CorsFilter; import cn.iocoder.common.framework.servlet.CorsFilter;
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.UserAccessLogInterceptor;
@ -49,12 +49,4 @@ public class MVCConfiguration implements WebMvcConfigurer {
return registrationBean; return registrationBean;
} }
// TODO 芋艿允许跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("*")
.allowedOrigins("*");
}
} }

View File

@ -17,6 +17,11 @@
<artifactId>common-framework</artifactId> <artifactId>common-framework</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>mall-spring-boot</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>cn.iocoder.mall</groupId> <groupId>cn.iocoder.mall</groupId>
<artifactId>pay-service-impl</artifactId> <artifactId>pay-service-impl</artifactId>
@ -63,8 +68,9 @@
<artifactId>springfox-swagger2</artifactId> <artifactId>springfox-swagger2</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>com.github.xiaoymin</groupId>
<artifactId>springfox-swagger-ui</artifactId> <artifactId>swagger-bootstrap-ui</artifactId>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -2,8 +2,10 @@ package cn.iocoder.mall.pay.application;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.pay"}) @SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.pay"})
@EnableAsync(proxyTargetClass = true)
public class PayApplication { public class PayApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -1,39 +0,0 @@
package cn.iocoder.mall.pay.application.config;
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
@Import(value = {GlobalExceptionHandler.class, // 统一全局返回
// AdminSecurityInterceptor.class
})
public class MVCConfiguration implements WebMvcConfigurer {
// @Autowired
// private UserSecurityInterceptor securityInterceptor;
// @Autowired
// private AdminSecurityInterceptor adminSecurityInterceptor;
////
// @Override
// public void addInterceptors(InterceptorRegistry registry) {
//// registry.addInterceptor(securityInterceptor).addPathPatterns("/user/**", "/admin/**"); // 只拦截我们定义的接口
// registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**")
// .excludePathPatterns("/admins/passport/login"); // 排除登陆接口
// }
// TODO 芋艿允许跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("*")
.allowedOrigins("*");
}
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.mall.pay.application.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2 // TODO 生产环境时禁用掉
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.iocoder.mall.pay.application.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("支付子系统")
.description("支付子系统")
.termsOfServiceUrl("http://www.iocoder.cn")
.version("1.0.0")
.build();
}
}

View File

@ -7,3 +7,9 @@ server:
port: 18084 port: 18084
servlet: servlet:
context-path: /pay-api/ context-path: /pay-api/
swagger:
title: 支付子系统
description: 支付子系统
version: 1.0.0
base-package: cn.iocoder.mall.pay.application.controller

14
pom.xml
View File

@ -37,6 +37,7 @@
<!-- <curator.version>4.0.1</curator.version>--> <!-- <curator.version>4.0.1</curator.version>-->
<!-- <zookeeper.version>3.4.14</zookeeper.version>--> <!-- <zookeeper.version>3.4.14</zookeeper.version>-->
<springfox-swagger.version>2.9.2</springfox-swagger.version> <springfox-swagger.version>2.9.2</springfox-swagger.version>
<swagger-bootstrap-ui.version>1.9.3</swagger-bootstrap-ui.version>
<mybatis-spring-boot-starter.version>2.0.0</mybatis-spring-boot-starter.version> <mybatis-spring-boot-starter.version>2.0.0</mybatis-spring-boot-starter.version>
<xxl-job.version>2.0.1</xxl-job.version> <xxl-job.version>2.0.1</xxl-job.version>
<guava.version>27.0.1-jre</guava.version> <guava.version>27.0.1-jre</guava.version>
@ -129,6 +130,11 @@
<artifactId>springfox-swagger-ui</artifactId> <artifactId>springfox-swagger-ui</artifactId>
<version>${springfox-swagger.version}</version> <version>${springfox-swagger.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger-bootstrap-ui.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.mybatis.spring.boot</groupId> <groupId>org.mybatis.spring.boot</groupId>
@ -184,6 +190,14 @@
<version>${qiniu.version}</version> <version>${qiniu.version}</version>
</dependency> </dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<!-- <scope>provided</scope>-->
<version>2.5</version>
<optional>true</optional>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@ -17,6 +17,11 @@
<artifactId>common-framework</artifactId> <artifactId>common-framework</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>mall-spring-boot</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>cn.iocoder.mall</groupId> <groupId>cn.iocoder.mall</groupId>
<artifactId>product-service-api</artifactId> <artifactId>product-service-api</artifactId>
@ -60,11 +65,10 @@
<artifactId>springfox-swagger2</artifactId> <artifactId>springfox-swagger2</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>com.github.xiaoymin</groupId>
<artifactId>springfox-swagger-ui</artifactId> <artifactId>swagger-bootstrap-ui</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -2,8 +2,10 @@ package cn.iocoder.mall.product.application;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.product"}) @SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.product"})
@EnableAsync(proxyTargetClass = true)
public class ProductApplication { public class ProductApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -1,42 +0,0 @@
package cn.iocoder.mall.product.application.config;
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
import cn.iocoder.common.framework.servlet.CorsFilter;
import cn.iocoder.mall.admin.sdk.interceptor.AdminAccessLogInterceptor;
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
@Import(value = {GlobalExceptionHandler.class, // 统一全局返回
AdminSecurityInterceptor.class, AdminAccessLogInterceptor.class})
public class MVCConfiguration implements WebMvcConfigurer {
@Autowired
private AdminSecurityInterceptor adminSecurityInterceptor;
@Autowired
private AdminAccessLogInterceptor adminAccessLogInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(securityInterceptor);
registry.addInterceptor(adminAccessLogInterceptor).addPathPatterns("/admins/**");
registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**");
}
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CorsFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.mall.product.application.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.iocoder.mall.product.application.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("商品子系统")
.description("商品子系统")
.termsOfServiceUrl("http://www.iocoder.cn")
.version("1.0.0")
.build();
}
}

View File

@ -7,3 +7,9 @@ server:
port: 18081 port: 18081
servlet: servlet:
context-path: /product-api/ context-path: /product-api/
swagger:
title: 商品子系统
description: 商品子系统
version: 1.0.0
base-package: cn.iocoder.mall.product.application.controller

View File

@ -31,6 +31,8 @@ dubbo:
version: 1.0.0 version: 1.0.0
ProductSpuService: ProductSpuService:
version: 1.0.0 version: 1.0.0
OAuth2Service:
version: 1.0.0
# rocketmq # rocketmq
rocketmq: rocketmq:

View File

@ -18,6 +18,11 @@
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>product-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>cn.iocoder.mall</groupId> <groupId>cn.iocoder.mall</groupId>
<artifactId>promotion-service-api</artifactId> <artifactId>promotion-service-api</artifactId>
@ -59,14 +64,8 @@
<artifactId>springfox-swagger2</artifactId> <artifactId>springfox-swagger2</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>com.github.xiaoymin</groupId>
<artifactId>springfox-swagger-ui</artifactId> <artifactId>swagger-bootstrap-ui</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>product-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -1,56 +0,0 @@
package cn.iocoder.mall.promotion.application.config;
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
import cn.iocoder.common.framework.servlet.CorsFilter;
import cn.iocoder.mall.admin.sdk.interceptor.AdminAccessLogInterceptor;
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.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
@Import(value = {GlobalExceptionHandler.class, // 统一全局返回
AdminSecurityInterceptor.class, UserAccessLogInterceptor.class,
UserSecurityInterceptor.class, AdminAccessLogInterceptor.class,
})
public class MVCConfiguration implements WebMvcConfigurer {
// @Autowired
// private UserSecurityInterceptor securityInterceptor;
@Autowired
private UserSecurityInterceptor userSecurityInterceptor;
@Autowired
private UserAccessLogInterceptor userAccessLogInterceptor;
@Autowired
private AdminSecurityInterceptor adminSecurityInterceptor;
@Autowired
private AdminAccessLogInterceptor adminAccessLogInterceptor;
//
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 用户
registry.addInterceptor(userAccessLogInterceptor).addPathPatterns("/users/**");
registry.addInterceptor(userSecurityInterceptor).addPathPatterns("/users/**"); // 只拦截我们定义的接口
// 管理员
// registry.addInterceptor(adminAccessLogInterceptor).addPathPatterns("/admins/**");
registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**");
}
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CorsFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}

View File

@ -7,3 +7,9 @@ server:
port: 18085 port: 18085
servlet: servlet:
context-path: /promotion-api/ context-path: /promotion-api/
swagger:
title: 营销子系统
description: 营销子系统
version: 1.0.0
base-package: cn.iocoder.mall.promotion.application.controller

View File

@ -17,6 +17,11 @@
<artifactId>common-framework</artifactId> <artifactId>common-framework</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>mall-spring-boot</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>cn.iocoder.mall</groupId> <groupId>cn.iocoder.mall</groupId>
<artifactId>user-sdk</artifactId> <artifactId>user-sdk</artifactId>
@ -48,8 +53,8 @@
<artifactId>springfox-swagger2</artifactId> <artifactId>springfox-swagger2</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>com.github.xiaoymin</groupId>
<artifactId>springfox-swagger-ui</artifactId> <artifactId>swagger-bootstrap-ui</artifactId>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -2,8 +2,10 @@ package cn.iocoder.mall.search.application;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.search"}) @SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.search"})
@EnableAsync(proxyTargetClass = true)
public class SearchApplication { public class SearchApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -1,51 +0,0 @@
package cn.iocoder.mall.search.application.config;
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
import cn.iocoder.common.framework.servlet.CorsFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
@Import(value = {GlobalExceptionHandler.class, // 统一全局返回
// AdminSecurityInterceptor.class, UserAccessLogInterceptor.class,
// UserSecurityInterceptor.class, AdminAccessLogInterceptor.class,
})
public class MVCConfiguration implements WebMvcConfigurer {
// @Autowired
// private UserSecurityInterceptor securityInterceptor;
// @Autowired
// private UserSecurityInterceptor userSecurityInterceptor;
// @Autowired
// private UserAccessLogInterceptor userAccessLogInterceptor;
// @Autowired
// private AdminSecurityInterceptor adminSecurityInterceptor;
// @Autowired
// private AdminAccessLogInterceptor adminAccessLogInterceptor;
//
@Override
public void addInterceptors(InterceptorRegistry registry) {
// // 用户
// registry.addInterceptor(userAccessLogInterceptor).addPathPatterns("/users/**");
// registry.addInterceptor(userSecurityInterceptor).addPathPatterns("/users/**"); // 只拦截我们定义的接口
// // 管理员
// registry.addInterceptor(adminAccessLogInterceptor).addPathPatterns("/admins/**");
// registry.addInterceptor(adminSecurityInterceptor).addPathPatterns("/admins/**");
}
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CorsFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}

View File

@ -1,36 +0,0 @@
package cn.iocoder.mall.search.application.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2 // TODO 生产环境时禁用掉
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.iocoder.mall.search.application.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("搜索子系统")
.description("搜索子系统")
.termsOfServiceUrl("http://www.iocoder.cn")
.version("1.0.0")
.build();
}
}

View File

@ -7,3 +7,9 @@ server:
port: 18086 port: 18086
servlet: servlet:
context-path: /search-api/ context-path: /search-api/
swagger:
title: 搜索子系统
description: 搜索子系统
version: 1.0.0
base-package: cn.iocoder.mall.search.application.controller

View File

@ -50,8 +50,9 @@
<artifactId>springfox-swagger2</artifactId> <artifactId>springfox-swagger2</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>com.github.xiaoymin</groupId>
<artifactId>springfox-swagger-ui</artifactId> <artifactId>swagger-bootstrap-ui</artifactId>
<optional>true</optional>
</dependency> </dependency>
<!-- <dependency>--> <!-- <dependency>-->

View File

@ -3,9 +3,10 @@ package cn.iocoder.mall.admin.application;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.admin"}) @SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.admin"})
//@EnableAdminServer @EnableAsync(proxyTargetClass = true)
public class AdminApplication { public class AdminApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -1,36 +0,0 @@
package cn.iocoder.mall.admin.application.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2 // TODO 生产环境时禁用掉
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.iocoder.mall.admin.application.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("管理员子系统")
.description("管理员子系统")
.termsOfServiceUrl("http://www.iocoder.cn")
.version("1.0.0")
.build();
}
}

View File

@ -18,7 +18,7 @@ import cn.iocoder.mall.admin.application.vo.AdminPageVO;
import cn.iocoder.mall.admin.application.vo.AdminRoleVO; import cn.iocoder.mall.admin.application.vo.AdminRoleVO;
import cn.iocoder.mall.admin.application.vo.AdminVO; import cn.iocoder.mall.admin.application.vo.AdminVO;
import cn.iocoder.mall.admin.sdk.context.AdminSecurityContextHolder; import cn.iocoder.mall.admin.sdk.context.AdminSecurityContextHolder;
import cn.iocoder.mall.spring.boot.constant.RootRequestPath; import cn.iocoder.common.framework.constant.MallConstants;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiImplicitParams;
@ -30,7 +30,7 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping(RootRequestPath.ADMIN + "/admin") @RequestMapping(MallConstants.ROOT_PATH_ADMIN + "/admin")
@Api("管理员模块") @Api("管理员模块")
public class AdminController { public class AdminController {

View File

@ -13,3 +13,6 @@ management:
include: "*" include: "*"
server: server:
port: 19083 # 配置独立端口。而该端口,不使用 nginx 对外暴露,从而不配置安全认证。也就是说,内网环境可访问,外网环境不可访问。当然,这么做的前提是,认为内网安全。 port: 19083 # 配置独立端口。而该端口,不使用 nginx 对外暴露,从而不配置安全认证。也就是说,内网环境可访问,外网环境不可访问。当然,这么做的前提是,认为内网安全。
swagger:
enable: true # 暂时不去掉

View File

@ -17,3 +17,9 @@ qiniu:
access-key: YldfyUC7OewoWM63TPYTairqnq8GMJvNek9EGoID access-key: YldfyUC7OewoWM63TPYTairqnq8GMJvNek9EGoID
secret-key: zZ7Q8wwZRyaklVvkyLmVydA4WygOBqtc_gTYzalS secret-key: zZ7Q8wwZRyaklVvkyLmVydA4WygOBqtc_gTYzalS
bucket: onemall bucket: onemall
swagger:
title: 管理员子系统
description: 管理员子系统
version: 1.0.0
base-package: cn.iocoder.mall.admin.application.controller

View File

@ -1,81 +0,0 @@
package cn.iocoder.mall.admin.sdk.interceptor;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.mall.admin.api.AdminAccessLogService;
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.dubbo.config.annotation.Reference;
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 AdminAccessLogInterceptor extends HandlerInterceptorAdapter {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 开始时间
*/
private static final ThreadLocal<Date> START_TIME = new ThreadLocal<>();
/**
* 管理员编号
*/
private static final ThreadLocal<Integer> ADMIN_ID = new ThreadLocal<>();
@Reference(validation = "true", version = "${dubbo.consumer.AdminAccessLogService.version:1.0.0}")
private AdminAccessLogService adminAccessLogService;
@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) {
if (adminAccessLogService == null) {
throw new IllegalStateException("AdminAccessLogService 服务未引入成功");
}
AdminAccessLogAddDTO accessLog = new AdminAccessLogAddDTO();
try {
accessLog.setAdminId(ADMIN_ID.get());
if (accessLog.getAdminId() == null) {
accessLog.setAdminId(AdminAccessLogAddDTO.ADMIN_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
adminAccessLogService.addAdminAccessLog(accessLog);
// TODO 提升暂时不考虑 ELK 的方案而是基于 MySQL 存储如果访问日志比较多需要定期归档
} catch (Throwable th) {
logger.error("[afterCompletion][插入管理员访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
} finally {
clear();
}
}
public static void setAdminId(Integer adminId) {
ADMIN_ID.set(adminId);
}
public static void clear() {
START_TIME.remove();
ADMIN_ID.remove();
}
}

View File

@ -1,7 +1,9 @@
package cn.iocoder.mall.admin.sdk.interceptor; package cn.iocoder.mall.admin.sdk.interceptor;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.exception.ServiceException; import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.util.HttpUtil; import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.MallUtil;
import cn.iocoder.common.framework.vo.CommonResult; import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.admin.api.OAuth2Service; import cn.iocoder.mall.admin.api.OAuth2Service;
import cn.iocoder.mall.admin.api.bo.OAuth2AuthenticationBO; import cn.iocoder.mall.admin.api.bo.OAuth2AuthenticationBO;
@ -39,8 +41,10 @@ public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 设置当前访问的用户类型注意即使未登陆我们也认为是管理员
MallUtil.setUserType(request, MallConstants.USER_TYPE_ADMIN);
// 校验访问令牌是否正确若正确返回授权信息 // 校验访问令牌是否正确若正确返回授权信息
String accessToken = HttpUtil.obtainAccess(request); String accessToken = HttpUtil.obtainAuthorization(request);
OAuth2AuthenticationBO authentication = null; OAuth2AuthenticationBO authentication = null;
if (accessToken != null) { if (accessToken != null) {
CommonResult<OAuth2AuthenticationBO> result = oauth2Service.checkToken(accessToken); CommonResult<OAuth2AuthenticationBO> result = oauth2Service.checkToken(accessToken);
@ -60,7 +64,7 @@ public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
// AdminSecurityInterceptor 执行后会移除 AdminSecurityContext 信息这就导致 AdminAccessLogInterceptor 无法获得管理员编号 // AdminSecurityInterceptor 执行后会移除 AdminSecurityContext 信息这就导致 AdminAccessLogInterceptor 无法获得管理员编号
// 因此这里需要进行记录 // 因此这里需要进行记录
if (authentication.getAdminId() != null) { if (authentication.getAdminId() != null) {
AdminAccessLogInterceptor.setAdminId(authentication.getAdminId()); MallUtil.setUserId(request, authentication.getAdminId());
} }
} else { } else {
String url = request.getRequestURI(); String url = request.getRequestURI();

View File

@ -1,13 +0,0 @@
package cn.iocoder.mall.admin.api;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO;
/**
* 管理员访问日志 Service 接口
*/
public interface AdminAccessLogService {
CommonResult<Boolean> addAdminAccessLog(AdminAccessLogAddDTO adminAccessLogAddDTO);
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.mall.admin.api;
import cn.iocoder.mall.admin.api.dto.AccessLogAddDTO;
/**
* 系统日志 Service 接口
*
* 例如说访问日志错误日志操作日志等等
*/
public interface SystemLogService {
void addAccessLog(AccessLogAddDTO accessLogAddDTO);
}

View File

@ -0,0 +1,98 @@
package cn.iocoder.mall.admin.api.dto;
import cn.iocoder.common.framework.vo.CommonResult;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
/**
* 访问日志添加 DTO
*/
@Data
@Accessors(chain = true)
public class AccessLogAddDTO implements Serializable {
/**
* 用户编号 -
*/
public static final Integer USER_ID_NULL = 0;
/**
* 链路追踪编号
*
* 一般来说通过链路追踪编号可以将访问日志错误日志链路追踪日志logger 打印日志等结合在一起从而进行排错
*/
@NotNull(message = "链路追踪编号不能为空")
private String traceId;
/**
* 用户编号.
*
* 当管理员为空时该值为 {@link #USER_ID_NULL}
*/
@NotNull(message = "用户编号不能为空")
private Integer userId;
/**
* 用户类型
*/
@NotNull(message = "用户类型不能为空")
private Integer userType;
/**
* 应用名
*
* 目前读取 spring.application.name
*/
@NotNull(message = "应用名不能为空")
private String applicationName;
/**
* 访问地址
*/
@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;
/**
* 错误码
*
* 目前的结果是使用 {@link CommonResult#getCode()} 属性
*/
@NotNull(message = "错误码不能为空")
private Integer errorCode;
/**
* 错误提示
*
* 目前的结果是使用 {@link CommonResult#getMessage()} 属性
*/
private String errorMessage;
}

View File

@ -1,66 +0,0 @@
package cn.iocoder.mall.admin.api.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
/**
* 管理员访问日志添加 DTO
*/
@Data
@Accessors(chain = true)
public class AdminAccessLogAddDTO implements Serializable {
/**
* 管理员编号 -
*/
public static final Integer ADMIN_ID_NULL = 0;
/**
* 管理员编号.
*
* 当管理员为空时该值为0
*/
@NotNull(message = "管理员编号不能为空")
private Integer adminId;
/**
* 访问地址
*/
@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;
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.admin.api.dto.AccessLogAddDTO;
import cn.iocoder.mall.admin.dataobject.AccessLogDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface AccessLogConvert {
AccessLogConvert INSTANCE = Mappers.getMapper(AccessLogConvert.class);
@Mappings({})
AccessLogDO convert(AccessLogAddDTO accessLogAddDTO);
}

View File

@ -1,17 +0,0 @@
package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO;
import cn.iocoder.mall.admin.dataobject.AdminAccessLogDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface AdminAccessLogConvert {
AdminAccessLogConvert INSTANCE = Mappers.getMapper(AdminAccessLogConvert.class);
@Mappings({})
AdminAccessLogDO convert(AdminAccessLogAddDTO adminAccessLogAddDTO);
}

View File

@ -0,0 +1,11 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.AccessLogDO;
import org.springframework.stereotype.Repository;
@Repository
public interface AccessLogMapper {
void insert(AccessLogDO entity);
}

View File

@ -1,11 +0,0 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.AdminAccessLogDO;
import org.springframework.stereotype.Repository;
@Repository
public interface AdminAccessLogMapper {
void insert(AdminAccessLogDO entity);
}

View File

@ -0,0 +1,84 @@
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import cn.iocoder.common.framework.vo.CommonResult;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 管理员访问日志 DO
*/
@Data
@Accessors(chain = true)
public class AccessLogDO extends DeletableDO {
/**
* 编号
*/
private Integer id;
/**
* 链路追踪编号
*
* 一般来说通过链路追踪编号可以将访问日志错误日志链路追踪日志logger 打印日志等结合在一起从而进行排错
*/
private String traceId;
/**
* 用户编号.
*
* 当管理员为空时该值为 {@link cn.iocoder.mall.admin.api.dto.AccessLogAddDTO#USER_ID_NULL}
*/
private Integer userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 应用名
*
* 目前读取 spring.application.name
*/
private String applicationName;
/**
* 访问地址
*/
private String uri;
/**
* 参数
*/
private String queryString;
/**
* http 方法
*/
private String method;
/**
* userAgent
*/
private String userAgent;
/**
* ip
*/
private String ip;
/**
* 请求时间
*/
private Date startTime;
/**
* 响应时长 -- 毫秒级
*/
private Integer responseTime;
/**
* 错误码
*
* 目前的结果是使用 {@link CommonResult#getCode()} 属性
*/
private Integer errorCode;
/**
* 错误提示
*
* 目前的结果是使用 {@link CommonResult#getMessage()} 属性
*/
private String errorMessage;
}

View File

@ -1,55 +0,0 @@
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 管理员访问日志 DO
*/
@Data
@Accessors(chain = true)
public class AdminAccessLogDO extends DeletableDO {
/**
* 编号
*/
private Integer id;
/**
* 管理员编号.
*
* 当管理员为空时该值为0
*/
private Integer adminId;
/**
* 访问地址
*/
private String uri;
/**
* 参数
*/
private String queryString;
/**
* http 方法
*/
private String method;
/**
* userAgent
*/
private String userAgent;
/**
* ip
*/
private String ip;
/**
* 请求时间
*/
private Date startTime;
/**
* 响应时长 -- 毫秒级
*/
private Integer responseTime;
}

View File

@ -1,12 +1,11 @@
package cn.iocoder.mall.admin.service; package cn.iocoder.mall.admin.service;
import cn.iocoder.common.framework.util.StringUtil; import cn.iocoder.common.framework.util.StringUtil;
import cn.iocoder.common.framework.vo.CommonResult; import cn.iocoder.mall.admin.api.SystemLogService;
import cn.iocoder.mall.admin.api.AdminAccessLogService; import cn.iocoder.mall.admin.api.dto.AccessLogAddDTO;
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO; import cn.iocoder.mall.admin.convert.AccessLogConvert;
import cn.iocoder.mall.admin.convert.AdminAccessLogConvert; import cn.iocoder.mall.admin.dao.AccessLogMapper;
import cn.iocoder.mall.admin.dao.AdminAccessLogMapper; import cn.iocoder.mall.admin.dataobject.AccessLogDO;
import cn.iocoder.mall.admin.dataobject.AdminAccessLogDO;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -14,7 +13,7 @@ import java.util.Date;
@Service @Service
@org.apache.dubbo.config.annotation.Service(validation = "true", version = "${dubbo.provider.AdminAccessLogService.version}") @org.apache.dubbo.config.annotation.Service(validation = "true", version = "${dubbo.provider.AdminAccessLogService.version}")
public class AdminAccessLogServiceImpl implements AdminAccessLogService { public class SystemLogServiceImpl implements SystemLogService {
/** /**
* 请求参数最大长度 * 请求参数最大长度
@ -30,12 +29,12 @@ public class AdminAccessLogServiceImpl implements AdminAccessLogService {
private static final Integer USER_AGENT_MAX_LENGTH = 1024; private static final Integer USER_AGENT_MAX_LENGTH = 1024;
@Autowired @Autowired
private AdminAccessLogMapper adminAccessLogMapper; private AccessLogMapper accessLogMapper;
@Override @Override
public CommonResult<Boolean> addAdminAccessLog(AdminAccessLogAddDTO adminAccessLogAddDTO) { public void addAccessLog(AccessLogAddDTO adminAccessLogAddDTO) {
// 创建 AdminAccessLogDO // 创建 AdminAccessLogDO
AdminAccessLogDO accessLog = AdminAccessLogConvert.INSTANCE.convert(adminAccessLogAddDTO); AccessLogDO accessLog = AccessLogConvert.INSTANCE.convert(adminAccessLogAddDTO);
accessLog.setCreateTime(new Date()); accessLog.setCreateTime(new Date());
// 截取最大长度 // 截取最大长度
if (accessLog.getUri().length() > URI_MAX_LENGTH) { if (accessLog.getUri().length() > URI_MAX_LENGTH) {
@ -48,9 +47,7 @@ public class AdminAccessLogServiceImpl implements AdminAccessLogService {
accessLog.setUserAgent(StringUtil.substring(accessLog.getUserAgent(), USER_AGENT_MAX_LENGTH)); accessLog.setUserAgent(StringUtil.substring(accessLog.getUserAgent(), USER_AGENT_MAX_LENGTH));
} }
// 插入 // 插入
adminAccessLogMapper.insert(accessLog); accessLogMapper.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.admin.dao.AccessLogMapper">
<!--<sql id="FIELDS">-->
<!--id, username, nickname, password, status,-->
<!--create_time-->
<!--</sql>-->
<insert id="insert" parameterType="AccessLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO access_log (
trace_id, user_id, user_type, uri, query_string, method, user_agent,
ip, start_time, response_time, error_code, error_message, create_time
) VALUES (
#{traceId}, #{userId}, #{userType}, #{uri}, #{queryString}, #{method}, #{userAgent},
#{ip}, #{startTime}, #{responseTime}, #{errorCode}, #{errorMessage}, #{createTime}
)
</insert>
</mapper>

View File

@ -1,20 +0,0 @@
<?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.admin.dao.AdminAccessLogMapper">
<!--<sql id="FIELDS">-->
<!--id, username, nickname, password, status,-->
<!--create_time-->
<!--</sql>-->
<insert id="insert" parameterType="AdminAccessLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO admin_access_log (
admin_id, uri, query_string, method, user_agent,
ip, start_time, response_time, create_time
) VALUES (
#{adminId}, #{uri}, #{queryString}, #{method}, #{userAgent},
#{ip}, #{startTime}, #{responseTime}, #{createTime}
)
</insert>
</mapper>

View File

@ -74,8 +74,8 @@
<artifactId>springfox-swagger2</artifactId> <artifactId>springfox-swagger2</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>com.github.xiaoymin</groupId>
<artifactId>springfox-swagger-ui</artifactId> <artifactId>swagger-bootstrap-ui</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -2,9 +2,10 @@ package cn.iocoder.mall.user.application;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.user"}) @SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.user"})
@EnableAsync(proxyTargetClass = true)
public class UserApplication { public class UserApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -1,36 +0,0 @@
package cn.iocoder.mall.user.application.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.iocoder.mall.user.application.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("用户子系统")
.description("用户子系统")
.termsOfServiceUrl("http://www.iocoder.cn")
.version("1.0.0")
.build();
}
}

View File

@ -7,3 +7,9 @@ server:
port: 18082 port: 18082
servlet: servlet:
context-path: /user-api/ context-path: /user-api/
swagger:
title: 用户子系统
description: 用户子系统
version: 1.0.0
base-package: cn.iocoder.mall.user.application.controller

View File

@ -1,84 +0,0 @@
package cn.iocoder.mall.user.sdk.interceptor;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.mall.user.api.UserAccessLogService;
import cn.iocoder.mall.user.api.dto.UserAccessLogAddDTO;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
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(validation = "true", version = "${dubbo.provider.UserAccessLogService.version:1.0.0}")
private UserAccessLogService userAccessLogService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// TODO 芋艿临时拿来处理 vue axios options 请求的问题
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
return false; // 通过这样的方式让前端知道允许的 header 等等
}
// 记录当前时间
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

@ -1,7 +1,9 @@
package cn.iocoder.mall.user.sdk.interceptor; package cn.iocoder.mall.user.sdk.interceptor;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.exception.ServiceException; import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.util.HttpUtil; import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.MallUtil;
import cn.iocoder.mall.user.api.OAuth2Service; import cn.iocoder.mall.user.api.OAuth2Service;
import cn.iocoder.mall.user.api.bo.OAuth2AuthenticationBO; import cn.iocoder.mall.user.api.bo.OAuth2AuthenticationBO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll; import cn.iocoder.mall.user.sdk.annotation.PermitAll;
@ -26,8 +28,10 @@ public class UserSecurityInterceptor extends HandlerInterceptorAdapter {
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 设置当前访问的用户类型注意即使未登陆我们也认为是用户
MallUtil.setUserType(request, MallConstants.USER_TYPE_USER);
// 校验访问令牌是否正确若正确返回授权信息 // 校验访问令牌是否正确若正确返回授权信息
String accessToken = HttpUtil.obtainAccess(request); String accessToken = HttpUtil.obtainAuthorization(request);
OAuth2AuthenticationBO authentication = null; OAuth2AuthenticationBO authentication = null;
if (accessToken != null) { if (accessToken != null) {
authentication = oauth2Service.checkToken(accessToken); // TODO 芋艿如果访问的地址无需登录这里也不用抛异常 authentication = oauth2Service.checkToken(accessToken); // TODO 芋艿如果访问的地址无需登录这里也不用抛异常
@ -39,7 +43,7 @@ public class UserSecurityInterceptor extends HandlerInterceptorAdapter {
// AdminSecurityInterceptor 执行后会移除 AdminSecurityContext 信息这就导致 AdminAccessLogInterceptor 无法获得管理员编号 // AdminSecurityInterceptor 执行后会移除 AdminSecurityContext 信息这就导致 AdminAccessLogInterceptor 无法获得管理员编号
// 因此这里需要进行记录 // 因此这里需要进行记录
if (authentication.getUserId() != null) { if (authentication.getUserId() != null) {
UserAccessLogInterceptor.setUserId(authentication.getUserId()); MallUtil.setUserId(request, authentication.getUserId());
} }
} }
// 校验是否需要已授权 // 校验是否需要已授权