提交 53fff39a authored 作者: YunaiV's avatar YunaiV

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

- 后端:增加 swagger AutoConfiguration 配置类 - 后端:统一访问日志的记录
上级 3ff9f1b3
...@@ -90,6 +90,12 @@ ...@@ -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>
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";
}
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;
......
...@@ -5,6 +5,11 @@ import javax.servlet.http.HttpServletRequest; ...@@ -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
...@@ -32,4 +37,4 @@ public class CorsFilter implements Filter { ...@@ -32,4 +37,4 @@ public class CorsFilter implements Filter {
public void destroy() { public void destroy() {
} }
} }
\ No newline at end of file
...@@ -44,7 +44,7 @@ public class HttpUtil { ...@@ -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;
...@@ -316,4 +316,4 @@ public class HttpUtil { ...@@ -316,4 +316,4 @@ public class HttpUtil {
return enc; return enc;
} }
} }
\ No newline at end of file
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();
}
}
...@@ -5,7 +5,7 @@ import org.springframework.util.Assert; ...@@ -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;
......
...@@ -47,6 +47,17 @@ ...@@ -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>
package cn.iocoder.mall.spring.boot.constant;
/**
* 全局请求路径枚举类,用于定义不同用户类型的根请求路径
*/
public interface RootRequestPath {
/**
* 管理员
*/
String ADMIN = "/admins";
/**
* 用户
*/
String USER = "/users";
}
package cn.iocoder.mall.user.application.config; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ApiInfoBuilder;
...@@ -10,27 +15,43 @@ import springfox.documentation.spi.DocumentationType; ...@@ -10,27 +15,43 @@ import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2; import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* 简单的 Swagger2 自动配置类
*
* 较为完善的,可以了解 https://mvnrepository.com/artifact/com.spring4all/spring-boot-starter-swagger
*/
@Configuration @Configuration
@EnableSwagger2 @EnableSwagger2
public class SwaggerConfiguration { @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 @Bean
public Docket createRestApi() { public Docket createRestApi() {
SwaggerProperties properties = swaggerProperties();
// 创建 Docket 对象
return new Docket(DocumentationType.SWAGGER_2) return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo()) .apiInfo(apiInfo(properties))
.select() .select()
.apis(RequestHandlerSelectors.basePackage("cn.iocoder.mall.user.application.controller")) .apis(RequestHandlerSelectors.basePackage(properties.getBasePackage()))
.paths(PathSelectors.any()) .paths(PathSelectors.any())
.build(); .build();
} }
private ApiInfo apiInfo() { private ApiInfo apiInfo(SwaggerProperties properties) {
return new ApiInfoBuilder() return new ApiInfoBuilder()
.title("用户子系统") .title(properties.getTitle())
.description("用户子系统") .description(properties.getDescription())
.termsOfServiceUrl("http://www.iocoder.cn") .version(properties.getVersion())
.version("1.0.0")
.build(); .build();
} }
} }
\ No newline at end of file
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;
}
package cn.iocoder.mall.spring.boot.web; package cn.iocoder.mall.spring.boot.web;
import cn.iocoder.mall.admin.sdk.interceptor.AdminAccessLogInterceptor; 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.admin.sdk.interceptor.AdminSecurityInterceptor;
import cn.iocoder.mall.spring.boot.constant.RootRequestPath; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet; 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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿,未来可能考虑 REACTIVE @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿,未来可能考虑 REACTIVE
@ConditionalOnClass({DispatcherServlet.class, WebMvcConfigurer.class, // 有 Spring MVC 容器 @ConditionalOnClass({DispatcherServlet.class, WebMvcConfigurer.class, // 有 Spring MVC 容器
AdminSecurityInterceptor.class, AdminAccessLogInterceptor.class}) // 有引入 system-sdk AdminSecurityInterceptor.class, AccessLogInterceptor.class}) // 有引入 system-sdk
public class AdminMVCConfiguration implements WebMvcConfigurer { public class AdminMVCAutoConfiguration implements WebMvcConfigurer {
@Bean
// @ConditionalOnMissingBean(AccessLogInterceptor.class)
public AccessLogInterceptor adminAccessLogInterceptor() {
return new AccessLogInterceptor();
}
@Bean @Bean
@ConditionalOnMissingBean(AdminSecurityInterceptor.class) @ConditionalOnMissingBean(AdminSecurityInterceptor.class)
...@@ -26,24 +35,30 @@ public class AdminMVCConfiguration implements WebMvcConfigurer { ...@@ -26,24 +35,30 @@ public class AdminMVCConfiguration implements WebMvcConfigurer {
} }
@Bean @Bean
@ConditionalOnMissingBean(AdminAccessLogInterceptor.class) @ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
public AdminAccessLogInterceptor adminAccessLogInterceptor() { public GlobalResponseBodyHandler globalReturnValueHandler() {
return new AdminAccessLogInterceptor(); return new GlobalResponseBodyHandler();
}
@Bean
@ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
} }
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminAccessLogInterceptor()).addPathPatterns(RootRequestPath.ADMIN + "/**"); registry.addInterceptor(adminAccessLogInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
registry.addInterceptor(adminSecurityInterceptor()).addPathPatterns(RootRequestPath.ADMIN + "/**"); registry.addInterceptor(adminSecurityInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
} }
@Override @Bean
public void addCorsMappings(CorsRegistry registry) { @ConditionalOnMissingBean
registry.addMapping(RootRequestPath.USER + "/**") public FilterRegistrationBean<CorsFilter> corsFilter() {
.allowedOrigins("*") FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
.allowedMethods("*") registrationBean.setFilter(new CorsFilter());
.allowedHeaders("*") registrationBean.addUrlPatterns("/*");
.allowCredentials(true).maxAge(1800); return registrationBean;
} }
} }
package cn.iocoder.mall.spring.boot.web; package cn.iocoder.mall.spring.boot.web;
import cn.iocoder.mall.spring.boot.constant.RootRequestPath; import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.mall.user.sdk.interceptor.UserAccessLogInterceptor; 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 cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet; 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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿,未来可能考虑 REACTIVE @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿,未来可能考虑 REACTIVE
@ConditionalOnClass({DispatcherServlet.class, WebMvcConfigurer.class, // 有 Spring MVC 容器 @ConditionalOnClass({DispatcherServlet.class, WebMvcConfigurer.class, // 有 Spring MVC 容器
UserSecurityInterceptor.class, UserAccessLogInterceptor.class}) // 有引入 system-sdk UserSecurityInterceptor.class, // 有引入 user-sdk
public class UserMVCConfiguration implements WebMvcConfigurer { AccessLogInterceptor.class}) // 有引入 system-sdk
public class UserMVCAutoConfiguration implements WebMvcConfigurer {
@Bean @Bean
@ConditionalOnMissingBean(UserAccessLogInterceptor.class) // @ConditionalOnMissingBean(AccessLogInterceptor.class)
public UserAccessLogInterceptor userAccessLogInterceptor() { public AccessLogInterceptor userAccessLogInterceptor() {
return new UserAccessLogInterceptor(); return new AccessLogInterceptor();
} }
@Bean @Bean
...@@ -31,19 +35,31 @@ public class UserMVCConfiguration implements WebMvcConfigurer { ...@@ -31,19 +35,31 @@ public class UserMVCConfiguration implements WebMvcConfigurer {
return new 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 @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userAccessLogInterceptor()).addPathPatterns(RootRequestPath.USER + "/**"); registry.addInterceptor(userAccessLogInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_USER + "/**");
registry.addInterceptor(userSecurityInterceptor()).addPathPatterns(RootRequestPath.USER + "/**"); registry.addInterceptor(userSecurityInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_USER + "/**");
} }
@Override @Bean
public void addCorsMappings(CorsRegistry registry) { @ConditionalOnMissingBean
registry.addMapping(RootRequestPath.USER + "/**") public FilterRegistrationBean<CorsFilter> corsFilter() {
.allowedOrigins("*") FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
.allowedMethods("*") registrationBean.setFilter(new CorsFilter());
.allowedHeaders("*") registrationBean.addUrlPatterns("/*");
.allowCredentials(true).maxAge(1800); return registrationBean;
} }
} }
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() {
}
}
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;
}
}
package cn.iocoder.mall.admin.sdk.interceptor; package cn.iocoder.mall.spring.boot.web.interceptor;
import cn.iocoder.common.framework.util.HttpUtil; import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.mall.admin.api.AdminAccessLogService; import cn.iocoder.common.framework.util.MallUtil;
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO; 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 com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.dubbo.config.annotation.Reference; import org.apache.dubbo.config.annotation.Reference;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
...@@ -19,7 +24,7 @@ import java.util.Date; ...@@ -19,7 +24,7 @@ import java.util.Date;
* 访问日志拦截器 * 访问日志拦截器
*/ */
@Component @Component
public class AdminAccessLogInterceptor extends HandlerInterceptorAdapter { public class AccessLogInterceptor extends HandlerInterceptorAdapter {
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
...@@ -27,13 +32,12 @@ public class AdminAccessLogInterceptor extends HandlerInterceptorAdapter { ...@@ -27,13 +32,12 @@ public class AdminAccessLogInterceptor extends HandlerInterceptorAdapter {
* 开始时间 * 开始时间
*/ */
private static final ThreadLocal<Date> START_TIME = new ThreadLocal<>(); 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}") @Reference(validation = "true", version = "${dubbo.consumer.AdminAccessLogService.version:1.0.0}")
private AdminAccessLogService adminAccessLogService; private SystemLogService adminAccessLogService;
@Value("${spring.application.name}")
private String applicationName;
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
...@@ -44,38 +48,50 @@ public class AdminAccessLogInterceptor extends HandlerInterceptorAdapter { ...@@ -44,38 +48,50 @@ public class AdminAccessLogInterceptor extends HandlerInterceptorAdapter {
@Override @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (adminAccessLogService == null) { AccessLogAddDTO accessLog = new AccessLogAddDTO();
throw new IllegalStateException("AdminAccessLogService 服务未引入成功");
}
AdminAccessLogAddDTO accessLog = new AdminAccessLogAddDTO();
try { try {
accessLog.setAdminId(ADMIN_ID.get()); // 设置用户编号
if (accessLog.getAdminId() == null) { accessLog.setUserId(MallUtil.getUserId(request));
accessLog.setAdminId(AdminAccessLogAddDTO.ADMIN_ID_NULL); if (accessLog.getUserId() == null) {
accessLog.setUserId(AccessLogAddDTO.USER_ID_NULL);
} }
accessLog.setUri(request.getRequestURI()); // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。 accessLog.setUserType(MallUtil.getUserType(request));
accessLog.setQueryString(HttpUtil.buildQueryString(request)); // 设置访问结果
accessLog.setMethod(request.getMethod()); CommonResult result = MallUtil.getCommonResult(request);
accessLog.setUserAgent(HttpUtil.getUserAgent(request)); Assert.isTrue(result != null, "result 必须非空");
accessLog.setIp(HttpUtil.getIp(request)); accessLog.setErrorCode(result.getCode())
accessLog.setStartTime(START_TIME.get()); .setErrorMessage(result.getMessage());
accessLog.setResponseTime((int) (System.currentTimeMillis() - accessLog.getStartTime().getTime()));// 默认响应时间设为0 // 设置其它字段
adminAccessLogService.addAdminAccessLog(accessLog); 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 存储。如果访问日志比较多,需要定期归档。 // TODO 提升:暂时不考虑 ELK 的方案。而是基于 MySQL 存储。如果访问日志比较多,需要定期归档。
} catch (Throwable th) { } catch (Throwable th) {
logger.error("[afterCompletion][插入管理员访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th)); logger.error("[afterCompletion][插入访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
} finally { } finally {
clear(); clear();
} }
} }
public static void setAdminId(Integer adminId) { @Async // 异步入库
ADMIN_ID.set(adminId); public void addAccessLog(AccessLogAddDTO accessLog) {
try {
adminAccessLogService.addAccessLog(accessLog);
} catch (Throwable th) {
logger.error("[addAccessLog][插入访问日志({}) 发生异常({})", JSON.toJSONString(accessLog), ExceptionUtils.getRootCauseMessage(th));
}
} }
public static void clear() { private static void clear() {
START_TIME.remove(); START_TIME.remove();
ADMIN_ID.remove();
} }
} }
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
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 { ...@@ -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("*");
}
} }
...@@ -17,6 +17,11 @@ ...@@ -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 @@ ...@@ -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>
......
...@@ -2,12 +2,14 @@ package cn.iocoder.mall.pay.application; ...@@ -2,12 +2,14 @@ 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) {
SpringApplication.run(PayApplication.class, args); SpringApplication.run(PayApplication.class, args);
} }
} }
\ No newline at end of file
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("*");
}
}
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();
}
}
\ No newline at end of file
...@@ -6,4 +6,10 @@ spring: ...@@ -6,4 +6,10 @@ spring:
server: server:
port: 18084 port: 18084
servlet: servlet:
context-path: /pay-api/ context-path: /pay-api/
\ No newline at end of file
swagger:
title: 支付子系统
description: 支付子系统
version: 1.0.0
base-package: cn.iocoder.mall.pay.application.controller
...@@ -37,6 +37,7 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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>
......
...@@ -17,6 +17,11 @@ ...@@ -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 @@ ...@@ -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>
......
...@@ -2,12 +2,14 @@ package cn.iocoder.mall.product.application; ...@@ -2,12 +2,14 @@ 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) {
SpringApplication.run(ProductApplication.class, args); SpringApplication.run(ProductApplication.class, args);
} }
} }
\ No newline at end of file
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;
}
}
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();
}
}
\ No newline at end of file
...@@ -6,4 +6,10 @@ spring: ...@@ -6,4 +6,10 @@ spring:
server: server:
port: 18081 port: 18081
servlet: servlet:
context-path: /product-api/ context-path: /product-api/
\ No newline at end of file
swagger:
title: 商品子系统
description: 商品子系统
version: 1.0.0
base-package: cn.iocoder.mall.product.application.controller
...@@ -31,6 +31,8 @@ dubbo: ...@@ -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:
......
...@@ -18,6 +18,11 @@ ...@@ -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 @@ ...@@ -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>
......
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;
}
}
...@@ -6,4 +6,10 @@ spring: ...@@ -6,4 +6,10 @@ spring:
server: server:
port: 18085 port: 18085
servlet: servlet:
context-path: /promotion-api/ context-path: /promotion-api/
\ No newline at end of file
swagger:
title: 营销子系统
description: 营销子系统
version: 1.0.0
base-package: cn.iocoder.mall.promotion.application.controller
...@@ -17,6 +17,11 @@ ...@@ -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 @@ ...@@ -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>
......
...@@ -2,8 +2,10 @@ package cn.iocoder.mall.search.application; ...@@ -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) {
......
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;
}
}
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();
}
}
...@@ -7,3 +7,9 @@ server: ...@@ -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
...@@ -50,8 +50,9 @@ ...@@ -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>-->
......
...@@ -3,9 +3,10 @@ package cn.iocoder.mall.admin.application; ...@@ -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) {
...@@ -19,4 +20,4 @@ public class AdminApplication { ...@@ -19,4 +20,4 @@ public class AdminApplication {
// System.out.println(); // TODO 后面去掉,这里是临时的 // System.out.println(); // TODO 后面去掉,这里是临时的
} }
} }
\ No newline at end of file
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();
}
}
\ No newline at end of file
...@@ -18,7 +18,7 @@ import cn.iocoder.mall.admin.application.vo.AdminPageVO; ...@@ -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.*; ...@@ -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 {
......
...@@ -13,3 +13,6 @@ management: ...@@ -13,3 +13,6 @@ management:
include: "*" include: "*"
server: server:
port: 19083 # 配置独立端口。而该端口,不使用 nginx 对外暴露,从而不配置安全认证。也就是说,内网环境可访问,外网环境不可访问。当然,这么做的前提是,认为内网安全。 port: 19083 # 配置独立端口。而该端口,不使用 nginx 对外暴露,从而不配置安全认证。也就是说,内网环境可访问,外网环境不可访问。当然,这么做的前提是,认为内网安全。
swagger:
enable: true # 暂时不去掉
...@@ -17,3 +17,9 @@ qiniu: ...@@ -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
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 { ...@@ -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 { ...@@ -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();
......
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);
}
package cn.iocoder.mall.admin.api;
import cn.iocoder.mall.admin.api.dto.AccessLogAddDTO;
/**
* 系统日志 Service 接口
*
* 例如说,访问日志、错误日志、操作日志等等
*/
public interface SystemLogService {
void addAccessLog(AccessLogAddDTO accessLogAddDTO);
}
package cn.iocoder.mall.admin.api.dto; package cn.iocoder.mall.admin.api.dto;
import cn.iocoder.common.framework.vo.CommonResult;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
...@@ -9,24 +10,42 @@ import java.io.Serializable; ...@@ -9,24 +10,42 @@ import java.io.Serializable;
import java.util.Date; import java.util.Date;
/** /**
* 管理员访问日志添加 DTO * 访问日志添加 DTO
*/ */
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
public class AdminAccessLogAddDTO implements Serializable { public class AccessLogAddDTO implements Serializable {
/** /**
* 管理员编号 - 空 * 用户编号 - 空
*/ */
public static final Integer ADMIN_ID_NULL = 0; public static final Integer USER_ID_NULL = 0;
/**
* 链路追踪编号
*
* 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
*/
@NotNull(message = "链路追踪编号不能为空")
private String traceId;
/** /**
* 管理员编号. * 用户编号.
* *
* 当管理员为空时,该值为0 * 当管理员为空时,该值为 {@link #USER_ID_NULL}
*/ */
@NotNull(message = "管理员编号不能为空") @NotNull(message = "用户编号不能为空")
private Integer adminId; private Integer userId;
/**
* 用户类型
*/
@NotNull(message = "用户类型不能为空")
private Integer userType;
/**
* 应用名
*
* 目前读取 spring.application.name
*/
@NotNull(message = "应用名不能为空")
private String applicationName;
/** /**
* 访问地址 * 访问地址
*/ */
...@@ -62,5 +81,18 @@ public class AdminAccessLogAddDTO implements Serializable { ...@@ -62,5 +81,18 @@ public class AdminAccessLogAddDTO implements Serializable {
*/ */
@NotNull(message = "响应时长不能为空") @NotNull(message = "响应时长不能为空")
private Integer responseTime; private Integer responseTime;
/**
* 错误码
*
* 目前的结果,是使用 {@link CommonResult#getCode()} 属性
*/
@NotNull(message = "错误码不能为空")
private Integer errorCode;
/**
* 错误提示
*
* 目前的结果,是使用 {@link CommonResult#getMessage()} 属性
*/
private String errorMessage;
} }
package cn.iocoder.mall.admin.convert; package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.admin.api.dto.AdminAccessLogAddDTO; import cn.iocoder.mall.admin.api.dto.AccessLogAddDTO;
import cn.iocoder.mall.admin.dataobject.AdminAccessLogDO; import cn.iocoder.mall.admin.dataobject.AccessLogDO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mappings; import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@Mapper @Mapper
public interface AdminAccessLogConvert { public interface AccessLogConvert {
AdminAccessLogConvert INSTANCE = Mappers.getMapper(AdminAccessLogConvert.class); AccessLogConvert INSTANCE = Mappers.getMapper(AccessLogConvert.class);
@Mappings({}) @Mappings({})
AdminAccessLogDO convert(AdminAccessLogAddDTO adminAccessLogAddDTO); AccessLogDO convert(AccessLogAddDTO accessLogAddDTO);
} }
\ No newline at end of file
package cn.iocoder.mall.admin.dao; package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.AdminAccessLogDO; import cn.iocoder.mall.admin.dataobject.AccessLogDO;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
public interface AdminAccessLogMapper { public interface AccessLogMapper {
void insert(AdminAccessLogDO entity); void insert(AccessLogDO entity);
} }
package cn.iocoder.mall.admin.dataobject; package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO; import cn.iocoder.common.framework.dataobject.DeletableDO;
import cn.iocoder.common.framework.vo.CommonResult;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
...@@ -11,18 +12,34 @@ import java.util.Date; ...@@ -11,18 +12,34 @@ import java.util.Date;
*/ */
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
public class AdminAccessLogDO extends DeletableDO { public class AccessLogDO extends DeletableDO {
/** /**
* 编号 * 编号
*/ */
private Integer id; private Integer id;
/** /**
* 管理员编号. * 链路追踪编号
* *
* 当管理员为空时,该值为0 * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
*/ */
private Integer adminId; 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;
/** /**
* 访问地址 * 访问地址
*/ */
...@@ -51,5 +68,17 @@ public class AdminAccessLogDO extends DeletableDO { ...@@ -51,5 +68,17 @@ public class AdminAccessLogDO extends DeletableDO {
* 响应时长 -- 毫秒级 * 响应时长 -- 毫秒级
*/ */
private Integer responseTime; private Integer responseTime;
/**
* 错误码
*
* 目前的结果,是使用 {@link CommonResult#getCode()} 属性
*/
private Integer errorCode;
/**
* 错误提示
*
* 目前的结果,是使用 {@link CommonResult#getMessage()} 属性
*/
private String errorMessage;
} }
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; ...@@ -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 { ...@@ -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 { ...@@ -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);
} }
} }
<?xml version="1.0" encoding="UTF-8"?> <?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"> <!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"> <mapper namespace="cn.iocoder.mall.admin.dao.AccessLogMapper">
<!--<sql id="FIELDS">--> <!--<sql id="FIELDS">-->
<!--id, username, nickname, password, status,--> <!--id, username, nickname, password, status,-->
<!--create_time--> <!--create_time-->
<!--</sql>--> <!--</sql>-->
<insert id="insert" parameterType="AdminAccessLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id"> <insert id="insert" parameterType="AccessLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO admin_access_log ( INSERT INTO access_log (
admin_id, uri, query_string, method, user_agent, trace_id, user_id, user_type, uri, query_string, method, user_agent,
ip, start_time, response_time, create_time ip, start_time, response_time, error_code, error_message, create_time
) VALUES ( ) VALUES (
#{adminId}, #{uri}, #{queryString}, #{method}, #{userAgent}, #{traceId}, #{userId}, #{userType}, #{uri}, #{queryString}, #{method}, #{userAgent},
#{ip}, #{startTime}, #{responseTime}, #{createTime} #{ip}, #{startTime}, #{responseTime}, #{errorCode}, #{errorMessage}, #{createTime}
) )
</insert> </insert>
</mapper> </mapper>
\ No newline at end of file
...@@ -74,8 +74,8 @@ ...@@ -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>
......
...@@ -2,9 +2,10 @@ package cn.iocoder.mall.user.application; ...@@ -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) {
......
...@@ -6,4 +6,10 @@ spring: ...@@ -6,4 +6,10 @@ spring:
server: server:
port: 18082 port: 18082
servlet: servlet:
context-path: /user-api/ context-path: /user-api/
\ No newline at end of file
swagger:
title: 用户子系统
description: 用户子系统
version: 1.0.0
base-package: cn.iocoder.mall.user.application.controller
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();
}
}
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 { ...@@ -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 { ...@@ -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());
} }
} }
// 校验是否需要已授权 // 校验是否需要已授权
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论