feat:V3.1.1整合remi-comm到remi-cloud中
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user