feat:V3.1.1整合remi-comm到remi-cloud中

This commit is contained in:
dianjiu
2024-09-11 17:41:17 +08:00
parent e111c59a69
commit 28d62f6cf4
245 changed files with 20975 additions and 154 deletions

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>remi-comm-web</artifactId>
<name>remi-comm-web</name>
<description>remi-comm-web</description>
<parent>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm</artifactId>
<version>3.1.1</version>
</parent>
<!-- 打包上传到私服, 需要和配置文件中的server节点配合使用 -->
<distributionManagement>
<repository>
<id>maven-releases</id>
<name>release</name>
<url>https://repository.njydsz.com/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>maven-snapshots</id>
<name>snapshot</name>
<url>https://repository.njydsz.com/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<!-- common-core -->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-core</artifactId>
</dependency>
<!--common-jdbc-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-jdbc</artifactId>
</dependency>
<!-- common-util -->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-util</artifactId>
</dependency>
<!--remi-comm-exception-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-exception</artifactId>
</dependency>
<!--common-swagger-->
<dependency>
<groupId>ai.remi.top.cloud</groupId>
<artifactId>remi-comm-swagger</artifactId>
</dependency>
<!--spring-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--skywalking-trace-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,15 @@
package ai.remi.comm.web.auth;
import ai.remi.comm.util.auth.AuthInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc AuthHandler
*/
public interface AuthHandler {
AuthInfo getAuthInfo(HttpServletRequest request, HttpServletResponse response);
}

View File

@@ -0,0 +1,29 @@
package ai.remi.comm.web.auth;
import ai.remi.comm.core.enums.ServiceTypeEnum;
import ai.remi.comm.web.excel.SpringContextUtil;
import java.util.Map;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc AuthHandlerFactory
*/
public class AuthHandlerFactory {
public static AuthHandler getAuthHandler(ServiceTypeEnum serviceTypeEnum) {
//Map<String, AuthHandler> authHandlerMap = SpringUtil.getBeansOfType(AuthHandler.class);
Map<String, AuthHandler> authHandlerMap = SpringContextUtil.getApplicationContext().getBeansOfType(AuthHandler.class);
//AssertUtils.check(CollectionUtil.isNotEmpty(authHandlerMap), "AuthHandler 服务未注册");
switch (serviceTypeEnum) {
case WEB_SERVICE:
return authHandlerMap.get("webAuthHandler");
case APP_SERVICE:
return authHandlerMap.get("appAuthHandler");
default:
return authHandlerMap.get("defaultAuthHandler");
}
}
}

View File

@@ -0,0 +1,47 @@
package ai.remi.comm.web.auth;
import com.google.common.collect.Sets;
import ai.remi.comm.core.constant.HeaderConstant;
import ai.remi.comm.core.enums.PermissionScopeEnum;
import ai.remi.comm.util.auth.AuthInfo;
import ai.remi.comm.util.auth.CompanyAuthInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Set;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc B端集团公司验证类
*/
@Component
public class WebAuthHandler implements AuthHandler {
@Override
public AuthInfo getAuthInfo(HttpServletRequest request, HttpServletResponse response) {
String companyTenantId = request.getHeader(HeaderConstant.X_COMPANY_TENANT_ID);
String deptTenantId = request.getHeader(HeaderConstant.X_DEPT_TENANT_ID);
String authToken = request.getHeader(HeaderConstant.X_ACCESS_TOKEN);
String userId = request.getHeader(HeaderConstant.X_UNIQUE_ID);
String language = request.getHeader(HeaderConstant.X_USER_LANGUAGE);
String permissionScopeCode = request.getHeader(HeaderConstant.X_PERMISSION_SCOPE);
PermissionScopeEnum permissionScopeEnum = StringUtils.isBlank(permissionScopeCode)?null:
PermissionScopeEnum.codeOf(permissionScopeCode);
Set<String> companyIds = Sets.newHashSet(request.getHeaders(HeaderConstant.X_COMPANY_IDS).asIterator());
Set<String> deptIds = Sets.newHashSet(request.getHeaders(HeaderConstant.X_DEPT_IDS).asIterator());
CompanyAuthInfo companyAuthInfo = new CompanyAuthInfo();
companyAuthInfo.setCompanyTenantId(companyTenantId);
companyAuthInfo.setDeptTenantId(deptTenantId);
companyAuthInfo.setAccessToken(authToken);
companyAuthInfo.setUniqueId(userId);
companyAuthInfo.setUserLanguage(language);
companyAuthInfo.setPermissionScope(permissionScopeEnum);
companyAuthInfo.setHasPermissionCompanyIds(companyIds);
companyAuthInfo.setHasPermissionDeptIds(deptIds);
return companyAuthInfo;
}
}

View File

@@ -0,0 +1,27 @@
package ai.remi.comm.web.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 设置项目默认时区
*/
@Configuration
//@ComponentScan("ai.remi")
//@PropertySource("classpath:jdbc.properties")
//spring中开启事务保证事务的完整性
@EnableTransactionManagement
public class SpringConfig {
@Bean(name = "transactionManager")
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}

View File

@@ -0,0 +1,24 @@
package ai.remi.comm.web.config;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.TimeZone;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 设置项目默认时区
*/
@Configuration
public class TimezoneConfig {
@PostConstruct
public void defaultTimeZone() {
// 设置用户时区为 UTC
//TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
// 设置用户时区为 Asia/Shanghai
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
}
}

View File

@@ -0,0 +1,33 @@
package ai.remi.comm.web.config;
import io.github.mngsk.devicedetector.Detection;
import io.github.mngsk.devicedetector.DeviceDetector;
import io.github.mngsk.devicedetector.device.Device;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc user-agent解析器
*/
@Configuration
public class UserAgentConfig {
/**
* 初始化user-agent解析器
*/
@Bean
public DeviceDetector deviceDetector() {
return new DeviceDetector.DeviceDetectorBuilder().build();
}
public static void main(String[] args) {
DeviceDetector dd = new DeviceDetector.DeviceDetectorBuilder().build();
String str = "Mozilla/5.0 (Linux; U; Android 8.0.0; zh-CN; BAC-AL00 Build/HUAWEIBAC-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/11.9.4.974 UWS/2.13.1.48 Mobile Safari/537.36 AliApp(DingTalk/4.5.11) com.alibaba.android.rimet/10487439 Channel/227200 language/zh-CN";
Detection detect = dd.detect(str);
Device device = detect.getDevice().get();
System.out.println(device.getModel().get());
}
}

View File

@@ -0,0 +1,209 @@
package ai.remi.comm.web.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.DateSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import ai.remi.comm.core.constant.Constants;
import ai.remi.comm.exception.custom.BusinessException;
import ai.remi.comm.web.filter.AuthFilter;
import ai.remi.comm.web.interceptor.HttpInterceptor;
import ai.remi.comm.web.interceptor.TenantInterceptor;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc WebMvcConfig
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 防抖过滤器
*/
//@Autowired
//private ResubmitFilter resubmitFilter;
/**
* 租户拦截器
*/
@Resource
private TenantInterceptor tenantInterceptor;
/**
* Http拦截器
*/
@Resource
private HttpInterceptor httpInterceptor;
/**
* 防抖拦截器
*/
//@Autowired
//private ResubmitInterceptor resubmitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tenantInterceptor).addPathPatterns("/**").order(Integer.MIN_VALUE);
registry.addInterceptor(httpInterceptor).addPathPatterns("/**").order(Integer.MAX_VALUE);
//registry.addInterceptor(resubmitInterceptor).addPathPatterns("/**").order(Integer.MAX_VALUE);
}
/**
* 解决跨域请求 方式一:
*/
//@Override
//public void addCorsMappings(CorsRegistry registry) {
// //项目中的所有接口都支持跨域
// registry.addMapping("/**")
// //是否支持跨域Cookie
// .allowCredentials(true)
// //配置访问的地址,*代表所有的都可以访问
// //.allowedOrigins("http://localhost:8080")
// .allowedOriginPatterns(CorsConfiguration.ALL)
// //允许的请求头参数
// .allowedHeaders(CorsConfiguration.ALL)
// //允许的请求方式 "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"
// .allowedMethods(CorsConfiguration.ALL)
// // 跨域允许时间
// .maxAge(3600);
//
// WebMvcConfigurer.super.addCorsMappings(registry);
//}
/**
* 解决跨域请求 方式二:可指定拦截器执行顺序
*/
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
//是否支持跨域Cookie
corsConfig.setAllowCredentials(true);
//配置访问的地址,*代表所有的都可以访问
//corsConfig.addAllowedOrigin("http://localhost:8080");
corsConfig.addAllowedOriginPattern(CorsConfiguration.ALL);
//允许的请求头参数
corsConfig.addAllowedHeader(CorsConfiguration.ALL);
//允许的请求头参数
corsConfig.addAllowedMethod(CorsConfiguration.ALL);
//默认可不设置这个暴露的头。这个为了安全问题,不能使用*。设置成*,后面会报错:throw new IllegalArgumentException("'*' is not a valid exposed header value");
//corsConfig.addExposedHeader("");
corsConfig.setMaxAge(3600L);
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", corsConfig);
FilterRegistrationBean<CorsFilter> corsBean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(configSource));
corsBean.setName("crossOriginFilter");
//这个顺序也有可能会有影响,尽量设置在拦截器前面
corsBean.setOrder(0);
return corsBean;
}
/**
* 自定义全局序列化方式
*/
@Bean
public HttpMessageConverters httpMessageConverters() {
MappingJackson2HttpMessageConverter httpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = httpMessageConverter.getObjectMapper();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleModule simpleModule = new SimpleModule();
//序列化日期格式
simpleModule.addSerializer(Date.class, new DateSerializer(false, simpleDateFormat));
//Long序列化为String
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
//字符串转String
simpleModule.addDeserializer(Date.class, new JsonDeserializer<Date>() {
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String date = jsonParser.getText();
try {
return simpleDateFormat.parse(date);
} catch (ParseException e) {
throw new BusinessException(Constants.ERROR, e.getMessage());
}
}
});
//反序列化日期格式
objectMapper.registerModule(simpleModule);
//序列化多余字段不报错
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//序列化地区
objectMapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
//如果值为null时字段key还是否输出:ALWAYS(总是输出),NON_NULL(为null的不输出)
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object o, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
String fieldName = gen.getOutputContext().getCurrentName();
try {
//反射获取字段类型
Field field = gen.getCurrentValue().getClass().getDeclaredField(fieldName);
if (Objects.equals(field.getType(), String.class)) {
//字符串型空值""
gen.writeString("");
return;
} else if (Objects.equals(field.getType(), List.class)) {
//列表型空值返回[]
gen.writeStartArray();
gen.writeEndArray();
return;
} else if (Objects.equals(field.getType(), Map.class)) {
//map型空值返回{}
gen.writeStartObject();
gen.writeEndObject();
return;
}
} catch (NoSuchFieldException e) {
}
//默认返回""
gen.writeString("");
}
});
return new HttpMessageConverters(httpMessageConverter);
}
/*@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(resubmitFilter);
//过滤所有路径
registrationBean.addUrlPatterns("/*");
registrationBean.setName("resubmitFilter");
registrationBean.setOrder(1);
return registrationBean;
}*/
@Bean
public FilterRegistrationBean<AuthFilter> authFilter() {
AuthFilter authFilter = new AuthFilter();
FilterRegistrationBean<AuthFilter> authFilterBean = new FilterRegistrationBean<>(authFilter);
//过滤所有路径
authFilterBean.addUrlPatterns("/*");
authFilterBean.setName("authFilter");
authFilterBean.setOrder(3);
return authFilterBean;
}
}

View File

@@ -0,0 +1,67 @@
package ai.remi.comm.web.excel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.WriteSheet;
import ai.remi.comm.core.excel.ExcelSelected;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class EasyExcelUtil {
/**
* 创建即将导出的sheet页(sheet页中含有带下拉框的列)
* @param head 导出的表头信息和配置
* @param sheetNo sheet索引
* @param sheetName sheet名称
* @param <T> 泛型
* @return sheet页
*/
public static <T> WriteSheet writeSelectedSheet(Class<T> head, Integer sheetNo, String sheetName) {
Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(head);
return EasyExcel.writerSheet(sheetNo, sheetName)
.head(head)
.registerWriteHandler(new SelectedSheetWriteHandler(selectedMap))
.build();
}
/**
* 解析表头类中的下拉注解
* @param head 表头类
* @param <T> 泛型
* @return Map<下拉框列索引, 下拉框内容> map
*/
private static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head) {
Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();
// getDeclaredFields(): 返回全部声明的属性;getFields(): 返回public类型的属性
Field[] fields = head.getDeclaredFields();
for (int i = 0; i < fields.length; i++){
Field field = fields[i];
// 解析注解信息
ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
if (selected != null) {
ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();
String[] source = excelSelectedResolve.resolveSelectedSource(selected);
if (source != null && source.length > 0){
excelSelectedResolve.setSource(source);
excelSelectedResolve.setFirstRow(selected.firstRow());
excelSelectedResolve.setLastRow(selected.lastRow());
if (property != null && property.index() >= 0){
selectedMap.put(property.index(), excelSelectedResolve);
} else {
selectedMap.put(i, excelSelectedResolve);
}
}
}
}
return selectedMap;
}
}

View File

@@ -0,0 +1,55 @@
package ai.remi.comm.web.excel;
import ai.remi.comm.core.excel.ExcelDynamicSelect;
import ai.remi.comm.core.excel.ExcelSelected;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
//自定义注解解析
@Data
@Slf4j
public class ExcelSelectedResolve {
/**
* 下拉内容
*/
private String[] source;
/**
* 设置下拉框的起始行,默认为第二行
*/
private int firstRow;
/**
* 设置下拉框的结束行,默认为最后一行
*/
private int lastRow;
public String[] resolveSelectedSource(ExcelSelected excelSelected) {
if (excelSelected == null) {
return null;
}
// 获取固定下拉框的内容
String[] source = excelSelected.source();
if (source.length > 0) {
return source;
}
// 获取动态下拉框的内容
Class<? extends ExcelDynamicSelect>[] classes = excelSelected.sourceClass();
if (classes.length > 0) {
try {
ExcelDynamicSelect excelDynamicSelect = classes[0].newInstance();
String[] dynamicSelectSource = excelDynamicSelect.getSource();
if (dynamicSelectSource != null && dynamicSelectSource.length > 0) {
return dynamicSelectSource;
}
} catch (InstantiationException | IllegalAccessException e) {
log.error("解析动态下拉框数据异常", e);
}
}
return null;
}
}

View File

@@ -0,0 +1,53 @@
package ai.remi.comm.web.excel;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.util.Map;
@Data
@AllArgsConstructor
public class SelectedSheetWriteHandler implements SheetWriteHandler {
private final Map<Integer, ExcelSelectedResolve> selectedMap;
/**
* Called before create the sheet
*/
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
/**
* Called after the sheet is created
*/
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 这里可以对cell进行任何操作
Sheet sheet = writeSheetHolder.getSheet();
DataValidationHelper helper = sheet.getDataValidationHelper();
selectedMap.forEach((k, v) -> {
// 设置下拉列表的行: 首行,末行,首列,末列
CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);
// 设置下拉列表的值
DataValidationConstraint constraint = helper.createExplicitListConstraint(v.getSource());
// 设置约束
DataValidation validation = helper.createValidation(constraint, rangeList);
// 阻止输入非下拉选项的值
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.setShowErrorBox(true);
validation.setSuppressDropDownArrow(true);
validation.createErrorBox("提示", "请输入下拉选项中的内容");
sheet.addValidationData(validation);
});
}
}

View File

@@ -0,0 +1,33 @@
package ai.remi.comm.web.excel;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
// 获取ApplicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
// 通过class获取Bean
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
// 通过name以及class获取Bean
public static <T> T getBean(String name, Class<T> clazz) {
return applicationContext.getBean(name, clazz);
}
}

View File

@@ -0,0 +1,168 @@
package ai.remi.comm.web.exception;
import ai.remi.comm.core.result.ResultBean;
import ai.remi.comm.exception.custom.BusinessException;
import ai.remi.comm.exception.enums.CommExceptionCode;
import ai.remi.comm.exception.util.MessageUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestHeaderException;
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 org.springframework.web.multipart.MaxUploadSizeExceededException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc GlobalExceptionHandler
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 全局异常
*
* @return R
*/
@ResponseBody
@ExceptionHandler({Exception.class, Throwable.class})
public ResultBean<?> exception(Exception e) {
log.error("全局异常信息 ==> {}", e);
return ResultBean.error(MessageUtils.getMessage("system.error"));
}
/**
* 系统异常
*
* @return R
*/
@ResponseBody
@ExceptionHandler(RuntimeException.class)
public ResultBean<?> runtimeException(Exception e) {
log.error("全局系统异常信息 ==> {}", e);
return ResultBean.error(e.getMessage());
}
/**
* 业务异常
*/
@ResponseBody
@ExceptionHandler(BusinessException.class)
public ResultBean<?> businessException(BusinessException e) {
log.error("全局业务异常信息 ==>{}:{}", e.getCode(), e.getMessage());
return ResultBean.error(e.getCode(), e.getMessage());
}
/**
* 上传异常
*/
@ResponseBody
@ExceptionHandler(MaxUploadSizeExceededException.class)
//@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResultBean handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.error("文件上传太大异常 ==> {}", e.getMessage(), e);
return ResultBean.error("Upload file too large");
}
/**
* 非法参数异常
*/
@ResponseBody
@ExceptionHandler(IllegalArgumentException.class)
//@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResultBean<?> illegalArgumentException(IllegalArgumentException e) {
log.error("非法参数异常 ==> {}", e.getMessage(), e);
return ResultBean.error(CommExceptionCode.ILLEGAL_ARGUMENT.getCode(), MessageUtils.getMessage(CommExceptionCode.ILLEGAL_ARGUMENT.getKey()), null);
}
/**
* 处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常,详情继续往下看代码
*/
@ResponseBody
@ExceptionHandler(BindException.class)
//@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public ResultBean<?> bindExceptionHandler(BindException e) {
String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(", "));
log.error("绑定异常 ==> {}", message, e);
return ResultBean.error(CommExceptionCode.ILLEGAL_ARGUMENT.getCode(), MessageUtils.getMessage(CommExceptionCode.ILLEGAL_ARGUMENT.getKey()), null);
}
/**
* 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
*/
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
//@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public ResultBean<?> constraintViolationExceptionHandler(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(", "));
log.error("请求参数格式错误 ==> {}", message, e);
return ResultBean.error(CommExceptionCode.ILLEGAL_ARGUMENT.getCode(), MessageUtils.getMessage(CommExceptionCode.ILLEGAL_ARGUMENT.getKey()), null);
}
/**
* 处理请求参数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常。
*/
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
//@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public ResultBean<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(", "));
log.error("请求参数格式错误 ==> {}", message, e);
return ResultBean.error(CommExceptionCode.ILLEGAL_ARGUMENT.getCode(), MessageUtils.getMessage(CommExceptionCode.ILLEGAL_ARGUMENT.getKey()), null);
}
/**
* 处理请求数据格式异常,比如JSON格式错误。
*/
@ResponseBody
@ExceptionHandler(HttpMessageNotReadableException.class)
//@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResultBean<?> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException e) {
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
log.error("请求数据格式错误 ==> {}", message, e);
return ResultBean.error(CommExceptionCode.ILLEGAL_ARGUMENT.getCode(), MessageUtils.getMessage(CommExceptionCode.ILLEGAL_ARGUMENT.getKey()), null);
}
/**
* 缺少请求头参数
*/
@ResponseBody
@ExceptionHandler(MissingRequestHeaderException.class)
//@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResultBean<?> missingRequestHeaderException(MissingRequestHeaderException e) {
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
log.error("缺少请求头参数 ==> {}", message, e);
return ResultBean.error(CommExceptionCode.ILLEGAL_ARGUMENT.getCode(), MessageUtils.getMessage(CommExceptionCode.ILLEGAL_ARGUMENT.getKey()), null);
}
@ResponseBody
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
//@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public ResultBean<?> httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
String message = e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
log.error("请求方法类型错误 ==> {}", message, e);
return ResultBean.error(CommExceptionCode.ILLEGAL_ARGUMENT.getCode(), MessageUtils.getMessage(CommExceptionCode.ILLEGAL_ARGUMENT.getKey()), null);
}
@ResponseBody
@ExceptionHandler(MissingServletRequestParameterException.class)
//@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResultBean<?> missingServletRequestParameterException(MissingServletRequestParameterException e) {
String message = String.format("参数 %s 不存在", e.getParameterName());
log.error("请求参数不存在 ==> {}", message, e);
return ResultBean.error(CommExceptionCode.ILLEGAL_ARGUMENT.getCode(), MessageUtils.getMessage(CommExceptionCode.ILLEGAL_ARGUMENT.getKey()), null);
}
}

View File

@@ -0,0 +1,80 @@
package ai.remi.comm.web.filter;
import com.alibaba.fastjson.JSON;
import ai.remi.comm.core.config.AuthFilterConfig;
import ai.remi.comm.core.constant.FilterIgnoreConstant;
import ai.remi.comm.core.constant.HeaderConstant;
import ai.remi.comm.core.enums.ServiceTypeEnum;
import ai.remi.comm.util.auth.AuthInfo;
import ai.remi.comm.util.auth.RequestHolder;
import ai.remi.comm.util.url.UrlIgnoreUtils;
import ai.remi.comm.web.auth.AuthHandler;
import ai.remi.comm.web.auth.AuthHandlerFactory;
import ai.remi.comm.web.excel.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 全局验证过滤器
*/
@Slf4j
public class AuthFilter implements Filter {
/**
* 获取验证信息 并存储到 ThreadLocal 中,可通过 {@link RequestHolder#getAuthInfo()} } 获取验证信息
*
* @param servletRequest 请求数据
* @param servletResponse 响应数据
* @param filterChain 过滤器链
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String servletPath = request.getServletPath();
// String applicationName = SpringUtil.getApplicationName();
String applicationName = SpringContextUtil.getApplicationContext().getApplicationName();
boolean ignoreService = FilterIgnoreConstant.AUTH_FILTER_IGNORE_SERVICE_NAME.contains(applicationName);
if (ignoreService) {
filterChain.doFilter(request, servletResponse);
return;
}
AuthFilterConfig authFilterConfig = SpringContextUtil.getBean(AuthFilterConfig.class);
// AuthFilterConfig authFilterConfig = SpringUtil.getBean(AuthFilterConfig.class);
Set<String> ignoreUrl = FilterIgnoreConstant.FILTER_COMMON_IGNORE_URL;
List<String> commonIgnoreUrl = authFilterConfig.getCommonIgnoreUrl();
List<String> customIgnoreUrl = authFilterConfig.getCustomIgnoreUrl();
ignoreUrl.addAll(commonIgnoreUrl);
ignoreUrl.addAll(customIgnoreUrl);
boolean ignoredUrl = UrlIgnoreUtils.isIgnoreUrl(ignoreUrl, servletPath);
if (ignoredUrl) {
filterChain.doFilter(request, servletResponse);
return;
}
HttpServletResponse response = (HttpServletResponse) servletResponse;
String serviceType = request.getHeader(HeaderConstant.X_SERVICE_TYPE);
log.info("X_SERVICE_TYPE:{}", serviceType);
if (StringUtils.isEmpty(serviceType)){
serviceType = ServiceTypeEnum.WEB_SERVICE.getCode();
}
AuthHandler authHandler = AuthHandlerFactory.getAuthHandler(ServiceTypeEnum.codeOf(serviceType));
AuthInfo authInfo = authHandler.getAuthInfo(request, response);
log.debug("当前请求路径:{},当前请求认证信息:{}", servletPath, JSON.toJSONString(authInfo));
RequestHolder.add(authInfo);
RequestHolder.add(request);
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,31 @@
package ai.remi.comm.web.interceptor;
import ai.remi.comm.util.auth.RequestHolder;
import ai.remi.comm.util.auth.TenantHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc 主要用于请求结束 清除当前线程相关数据 清除所有 ThreadLocal
*/
@Component
public class HttpInterceptor implements HandlerInterceptor {
/**
* This implementation is empty.
*
* @param request
* @param response
* @param handler
* @param ex
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
RequestHolder.remove();
TenantHolder.remove();
}
}

View File

@@ -0,0 +1,61 @@
package ai.remi.comm.web.interceptor;
import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import ai.remi.comm.core.constant.FilterIgnoreConstant;
import ai.remi.comm.util.auth.TenantHolder;
import ai.remi.comm.util.url.UrlIgnoreUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Set;
/**
* @author dianjiu【公众号 点九开源】
* @email dianjiuxyz@gmail.com
* @desc TenantInterceptor
*/
@Slf4j
@Component
public class TenantInterceptor implements HandlerInterceptor {
/**
* 不拦截的url
*/
private Set<String> ignoreUrlSet = Sets.newConcurrentHashSet();
@Value("${sql-intercept.tenant.anonUrlSet:''}")
private String anonUrlSet;
@PostConstruct
public void init() {
ignoreUrlSet.addAll(FilterIgnoreConstant.FILTER_COMMON_IGNORE_URL);
List<String> exclusionUrlList =
Splitter.on(",")
.trimResults()
.omitEmptyStrings()
.splitToList(anonUrlSet);
ignoreUrlSet.addAll(exclusionUrlList);
log.info("多租户拦截器忽略得请求路径有:{}",ignoreUrlSet);
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String servletPath = request.getServletPath();
boolean isIgnoreRequest = UrlIgnoreUtils.isIgnoreUrl(ignoreUrlSet, servletPath);
log.info("本次请求多租户拦截器是否拦截:{},请求路径:{}",!isIgnoreRequest,servletPath);
TenantHolder.setIgnoredRequest(isIgnoreRequest);
return true;
}
}