增加服务端核心类与公共类

This commit is contained in:
huxin02 2025-02-28 17:04:00 +08:00
parent 33d8018999
commit f804b0eea5
158 changed files with 11489 additions and 0 deletions

View File

@ -0,0 +1,33 @@
package com.aisino.iles;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.retry.annotation.EnableRetry;
import java.security.Security;
import java.util.Arrays;
@SpringBootApplication(scanBasePackages = {
"com.aisino",
"com.smartlx"
})
@EnableCaching
@EnableRetry // 启用重试功能
@Slf4j
public class Application {
public static void main(String[] args) {
// 1. 检查并添加 BC 提供商
Security.removeProvider("BC");
Security.addProvider(new BouncyCastleProvider());
log.info("BC 提供商版本:{} ", Security.getProvider("BC").getVersionStr());
log.info("已注册的加密提供商:");
Arrays.stream(Security.getProviders()).forEach(p ->
log.info(" - " + p.getName() + " (" + p.getInfo() + ")")
);
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,13 @@
package com.aisino.iles.core.annotation;
import java.lang.annotation.*;
/**
* 当前用户标记,用于帮助标记参数,方便用户属性注入
*/
@Documented
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}

View File

@ -0,0 +1,37 @@
package com.aisino.iles.core.annotation;
import com.aisino.iles.core.model.enums.OperateType;
import java.lang.annotation.*;
/**
* 操作日志注解标记
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 日志内容
* @return 日志内容描述
*/
String value();
/**
* 操作类型
* @return 操作日志类型分类
*/
OperateType type();
/**
* 格外的描述信息
* @return 格外的描述信息
*/
String description() default "";
/**
* 忽略参数
* @return 忽略参数
*/
boolean ignoreArgs() default false;
}

View File

@ -0,0 +1,90 @@
package com.aisino.iles.core.annotation.handlers;
import cn.hutool.core.util.StrUtil;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.annotation.CurrentUser;
import com.aisino.iles.core.exception.TokenError;
import com.aisino.iles.core.model.User;
import com.aisino.iles.core.util.RedisUtil;
import com.smartlx.sso.client.model.AccessToken;
import com.smartlx.sso.client.model.RemoteUserInfo;
import com.smartlx.sso.client.service.SsoClientService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* 当前登录用户方法参数注入处理器工具
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class CurrentUserMethodArgResolver implements HandlerMethodArgumentResolver {
private final SsoClientService ssoClientService;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
if (methodParameter.getParameterType().isAssignableFrom(User.class) && methodParameter.hasParameterAnnotation(CurrentUser.class)) {
return true;
}
return methodParameter.getParameterType().isAssignableFrom(RemoteUserInfo.class) && methodParameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
if (methodParameter.getParameterType().isAssignableFrom(RemoteUserInfo.class)) {
// 如果请求属性中有用户ID则从redisutil中返回
if (nativeWebRequest.getAttribute(Constants.CURRENT_USER_ID, RequestAttributes.SCOPE_REQUEST) != null) {
String userId = (String) nativeWebRequest.getAttribute(Constants.CURRENT_USER_ID, RequestAttributes.SCOPE_REQUEST);
// 构建Redis中存储的用户信息的key
String userInfoKey = StrUtil.format(Constants.RedisKeyPrefix.userInfo.getValue(), userId);
// 从Redis中获取用户信息
RemoteUserInfo userInfo = RedisUtil.get(userInfoKey, RemoteUserInfo.class);
if (userInfo != null) {
return userInfo;
}
}
// 如果请求属性中没有用户ID则从header中的Authorization信息获取
String authorization = nativeWebRequest.getHeader("Authorization");
if (StringUtils.hasText(authorization) && authorization.startsWith("Bearer ")) {
try {
// 提取access_token
String token = authorization.substring(7);
// 创建AccessToken对象
AccessToken accessToken = new AccessToken();
accessToken.setAccess_token(token);
// 使用SsoClientService获取用户信息
RemoteUserInfo userInfo = ssoClientService.getRemoteUserInfo(accessToken);
if (userInfo != null && StringUtils.hasText(userInfo.getYhwybs())) {
return userInfo;
} else {
log.warn("无效的access_token或用户信息不完整");
throw new TokenError("无效的access_token或用户信息不完整");
}
} catch (Exception e) {
log.error("获取用户信息时发生错误", e);
throw new TokenError("获取用户信息时发生错误");
}
}
// 如果没有Authorization头或格式不正确抛出异常
log.warn("请求中缺少有效的Authorization头");
throw new TokenError("请求中缺少有效的Authorization头");
}
throw new TokenError("请求中缺少有效的Authorization头");
}
}

View File

@ -0,0 +1,152 @@
package com.aisino.iles.core.aop;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.common.util.Constants.GlobalParams.OperateLogSwitch;
import com.aisino.iles.common.util.InetAddressUtil;
import com.aisino.iles.common.util.StringUtils;
import com.aisino.iles.core.annotation.CurrentUser;
import com.aisino.iles.core.annotation.Log;
import com.aisino.iles.core.model.OperateLog;
import com.aisino.iles.core.model.OperateLogText;
import com.aisino.iles.core.model.enums.IpType;
import com.aisino.iles.core.model.enums.OperateLogTextType;
import com.aisino.iles.core.model.enums.OperateStatus;
import com.aisino.iles.core.repository.OperateLogRepo;
import com.aisino.iles.core.service.GlobalParamService;
import com.aisino.iles.core.util.TokenUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Aspect
@Component
@ConditionalOnProperty(prefix = "operate-log", name = "enable", havingValue = "true", matchIfMissing = true)
@Slf4j
public class LogAspect {
private final HttpServletRequest request;
private final ObjectMapper http2JacksonObjectMapper;
private final OperateLogRepo operateLogRepo;
private final TransactionTemplate transactionTemplate;
private final GlobalParamService globalParamService;
@Autowired
public LogAspect(HttpServletRequest request,
ObjectMapper http2JacksonObjectMapper,
OperateLogRepo operateLogRepo,
TransactionTemplate transactionTemplate,
GlobalParamService globalParamService) {
this.request = request;
this.http2JacksonObjectMapper = http2JacksonObjectMapper;
this.operateLogRepo = operateLogRepo;
this.transactionTemplate = transactionTemplate;
this.globalParamService = globalParamService;
}
@Pointcut("@annotation(com.aisino.iles.core.annotations.Log)")
private void pointcut() {
}
/**
* 记录日志
*
* @param point 接入点
*/
@Around("pointcut()")
public Object saveLog(ProceedingJoinPoint point) throws Throwable {
// 获取内外网标记参数
OperateLogSwitch logSwitch = globalParamService.findByCode(Constants.GlobalParams.OperateLogSwitch.code)
.map(g->Constants.GlobalParams.OperateLogSwitch.fromValue(g.getGlobalParamValue()))
.orElse(Constants.GlobalParams.OperateLogSwitch.file); // 默认为文件记录
// 标记方法调用
Object proceed;
Log logAnnotation = ((MethodSignature) point.getSignature()).getMethod().getAnnotation(Log.class);
String userIpAddr = InetAddressUtil.getIpAddress(request);
// 序列化排除用户信息用户信息的链式反应太多了去掉性能应该会好一点
Set<Object> args = IntStream.range(0, point.getArgs().length)
.filter(i -> {
Annotation[] parameterAnnotation = ((MethodSignature) point.getSignature()).getMethod().getParameterAnnotations()[i];
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
boolean annotationResult = Arrays.stream(parameterAnnotation).noneMatch(a -> (a.annotationType().isAssignableFrom(CurrentUser.class)));
boolean parTypeResult = Arrays.stream(parameterTypes).noneMatch(p -> p.equals(HttpServletRequest.class) || p.equals(HttpServletResponse.class));
return annotationResult && parTypeResult;
})
.mapToObj(i -> point.getArgs()[i])
.collect(Collectors.toSet());
OperateLog.OperateLogBuilder builder = OperateLog.builder()
.executionBeginTime(LocalDateTime.now())
.method(Optional.ofNullable(point.getSignature().getDeclaringTypeName()).map(n -> n + ".").orElse("") + point.getSignature().getName())
.args(!logAnnotation.ignoreArgs() ?
OperateLogText.builder()
.content(http2JacksonObjectMapper.writeValueAsString(args))
.type(OperateLogTextType.args).build() : null)
.ip(userIpAddr)
.ipType(InetAddressUtil.isIPv4Address(userIpAddr) ? IpType.v4 : IpType.v6)
.name(logAnnotation.value())
.description(logAnnotation.description())
.operateType(logAnnotation.type());
try {
String token = ""; // todo 这里需要重新实现
Optional.ofNullable(TokenUtil.parseToken(token))
.ifPresent(tk -> {
try {
Optional.ofNullable(http2JacksonObjectMapper.readTree(tk.getJsonProfile())
.path("user")
.path("idNum").asText())
.filter(StringUtils::isNotEmpty)
.ifPresent(builder::idNum);
} catch (JsonProcessingException e) {
log.error("解析令牌错误:" + e.getMessage(), e);
}
});
} catch (RuntimeException ignored) {
}
try {
proceed = point.proceed();
} catch (Throwable throwable) {
StringWriter sw = new StringWriter();
throwable.printStackTrace(new PrintWriter(sw));
String fullStackError = sw.toString();
sw.close();
builder.status(OperateStatus.FAILURE)
.executionEndTime(LocalDateTime.now())
.error(OperateLogText.builder().type(OperateLogTextType.errors).content(throwable.getLocalizedMessage() + "\n" + fullStackError).build());
throw throwable;
} finally {
OperateLog olog = builder.status(OperateStatus.SUCCESS)
.executionEndTime(LocalDateTime.now()).build();
if (logSwitch == OperateLogSwitch.db) {
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.executeWithoutResult(status-> operateLogRepo.save(olog));
}
else if (logSwitch == OperateLogSwitch.file) {
log.info("operate_name|operate_type|status|execution_begin_time|execution_end_time|ip_type|ip|user_id|method_name|args|error\n\n"
+ String.format("%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s", olog.getName(), olog.getOperateType(), olog.getStatus(), olog.getExecutionBeginTime(), olog.getExecutionEndTime(), olog.getIpType(), olog.getIp(), Optional.ofNullable(request.getHeader(Constants.CURRENT_USER_ID)).orElse(""), olog.getMethod(), Optional.ofNullable(olog.getArgs()).map(OperateLogText::getContent).orElse(""), Optional.ofNullable(olog.getError()).map(OperateLogText::getContent).orElse("")));
}
}
return proceed;
}
}

View File

@ -0,0 +1,73 @@
package com.aisino.iles.core.config;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.Token;
import com.aisino.iles.core.util.TokenUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Optional;
@Configuration
public class AuditConfig {
@EnableJpaAuditing(dateTimeProviderRef = "auditDateTimeProvider")
public static class JpaAuditConfig {
}
/**
* 获取当前用户信息接口
* 字符串主键实现
*
* @author huxin
* @since 2020-10-12
*/
@Component
@ConditionalOnProperty(prefix = "data-audit", name = "enable", havingValue = "true", matchIfMissing = true)
static class AuditorAwareStringImpl implements AuditorAware<String> {
private final HttpServletRequest request;
AuditorAwareStringImpl(HttpServletRequest request) {
this.request = request;
}
@Override
public Optional<String> getCurrentAuditor() {
return Optional.ofNullable(request).map(req -> req.getHeader(Constants.Headers.AUTH_TOKEN))
.map(TokenUtil::parseToken)
.map(Token::getUid);
}
}
/**
* 审计功能用的日期时间提供者
*
* @author hx
* @since 20210818
*/
static class AuditDateTimeProvider implements DateTimeProvider {
@Override
public Optional<TemporalAccessor> getNow() {
return Optional.of(LocalDateTime.parse(LocalDateTime.now().format(DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss"))));
}
}
/**
* 审计功能用的日期时间提供者(实例)
*
* @author hx
* @since 20210818
*/
@Bean
public AuditDateTimeProvider auditDateTimeProvider() {
return new AuditDateTimeProvider();
}
}

View File

@ -0,0 +1,37 @@
package com.aisino.iles.core.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
public class CoreCacheConfig {
@Deprecated
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
ObjectMapper mapper = objectMapper.copy();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
mapper.addMixIn(Object.class, JacksonConfig.JsonMixinForDataPackage.class);
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(mapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setHashKeySerializer(stringRedisSerializer);
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

View File

@ -0,0 +1,52 @@
package com.aisino.iles.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
@ConfigurationProperties(prefix = "data-package")
@Data
public class DataPackageConfig {
/**
* 需要打包的实体名称列表
*/
private ClassPattern processEntityNames;
/**
* 写入目标redis失败时重试次数
*/
private int retryTimes;
/**
* 写入目标redis失败时重试间隔时间毫秒
*/
private int retryInterval;
/**
* 打包标志
*/
private boolean packagingFlag = false;
/**
* 打包接口地址
*/
private String packagingApi;
private List<String> dataPackProcessors = new ArrayList<>();
/**
* 类名匹配
*/
@Data
public static class ClassPattern {
/**
* 排除列表
*/
private List<String> exclude = new ArrayList<>();
/**
* 包含列表
*/
private List<String> include = new ArrayList<>();
}
}

View File

@ -0,0 +1,57 @@
package com.aisino.iles.core.config;
import com.aisino.iles.common.repository.impl.CommonRepositoryImpl;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import javax.sql.DataSource;
import java.util.Map;
@Configuration
@EnableJpaRepositories(
basePackages = {
"com.aisino.iles.*.repository"
},
repositoryBaseClass = CommonRepositoryImpl.class,
entityManagerFactoryRef = "entityManagerFactoryIles")
public class DataSourceConfig {
private final JpaProperties jpaProperties;
private final HibernateProperties hibernateProperties;
private final HibernateSettings hibernateSettings;
public DataSourceConfig(JpaProperties jpaProperties, HibernateProperties hibernateProperties, HibernateSettings hibernateSettings) {
this.jpaProperties = jpaProperties;
this.hibernateProperties = hibernateProperties;
this.hibernateSettings = hibernateSettings;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource() {
return new HikariDataSource();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryIles(EntityManagerFactoryBuilder builder, DataSource dataSource) {
return builder.dataSource(dataSource)
.packages("com.aisino.iles.*.model")
.persistenceUnit("persistenceIles")
.properties(getVendorProperties())
.build();
}
private Map<String, Object> getVendorProperties() {
return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), hibernateSettings);
}
}

View File

@ -0,0 +1,25 @@
package com.aisino.iles.core.config;
import com.aisino.iles.core.hibernate.DataPackageInterceptor;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Set;
@Configuration
public class HibernateConfig {
@Bean
public HibernateSettings hibernateSettings(Set<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers) {
HibernateSettings hibernateSettings = new HibernateSettings();
hibernateSettings.hibernatePropertiesCustomizers(hibernatePropertiesCustomizers);
return hibernateSettings;
}
@Bean
public HibernatePropertiesCustomizer dataPackageInterceptorCustomizer(DataPackageInterceptor dataPackageInterceptor) {
return hp -> hp.put("hibernate.session_factory.interceptor", dataPackageInterceptor);
}
}

View File

@ -0,0 +1,129 @@
package com.aisino.iles.core.config;
import com.aisino.iles.core.converter.json.*;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import jakarta.persistence.Transient;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Configuration
public class JacksonConfig {
@Bean
@Primary
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.registerModule(new Jdk8Module());
objectMapper.registerModule(new ParameterNamesModule());
// 注册自定义模块
SimpleModule customModule = new SimpleModule();
// 自己带时区转换的LOCAL_DATE_TIME
customModule.addDeserializer(LocalDateTime.class, new HttpJackson2LocalDateTimeConverter());
customModule.addDeserializer(LocalDate.class, new HttpJackson2LocalDateConverter());
// 处理字符串参数内容两端包含空格的数据自动转换去掉
customModule.addDeserializer(String.class, new StringTrimDeserializer());
objectMapper.registerModule(customModule);
// 处理JPA延迟加载类型未加载时序列化问题
Hibernate6Module hibernateModule = new Hibernate6Module();
// 序列化时候只序列化延迟加载未载入的manytoone对象的主键信息
hibernateModule.enable(Hibernate6Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
// 序列化使用了临时标记的属性
hibernateModule.disable(Hibernate6Module.Feature.USE_TRANSIENT_ANNOTATION);
// 序列化时使用基本集合类型hashset,arraylist,hashmap等替换hibernate的持久化集合类型
// hibernateModule.enable(Hibernate5Module.Feature.REPLACE_PERSISTENT_COLLECTIONS);
// 序列化不存在的实体为空值
hibernateModule.enable(Hibernate6Module.Feature.WRITE_MISSING_ENTITIES_AS_NULL);
objectMapper.registerModule(hibernateModule);
// 关闭标记了忽略的属性出现报错的功能
objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES,false);
// 关闭遇到未知属性报错的功能
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 把数组,集合之类的为null的,转换为空数组 (注意不包含延迟加载的属性)
objectMapper.setSerializerFactory(objectMapper.getSerializerFactory()
.withSerializerModifier(new SerializerModifier()));
return objectMapper;
}
@Bean
public ObjectMapper dataPackageObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.registerModule(new Jdk8Module());
objectMapper.registerModule(new ParameterNamesModule());
// 自己带时区转换的LOCAL_DATE_TIME
SimpleModule javaTimeModule = new SimpleModule();
javaTimeModule.addDeserializer(LocalDateTime.class, new HttpJackson2LocalDateTimeConverter());
javaTimeModule.addDeserializer(LocalDate.class, new HttpJackson2LocalDateConverter());
objectMapper.registerModule(javaTimeModule);
// 预加载延迟集合属性
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 激活默认类型方式
// objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
// ObjectMapper.DefaultTyping.NON_FINAL);
// 忽略标记了不序列化的属性保证数据完整(这个会照成延迟属性失效)
objectMapper.setAnnotationIntrospector(new DataPackageJacksonAnnotationIntrospector());
// 忽略数据为空的属性即可以提高序列化速度也可以缩短序列化长度
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setSerializerFactory(objectMapper.getSerializerFactory()
// 不序列化需要延迟加载的集合序列化空集合或者数组为空数组
.withSerializerModifier(new DataPackageHibernateDontLazySerializerModifier())
// 把数组,集合之类的为null的,转换为空数组 (注意不包含延迟加载的属性)
.withSerializerModifier(new SerializerModifier()));
// 打包数据id编号以及忽略不存在的属性.
objectMapper.addMixIn(Object.class, JsonMixinForDataPackage.class);
return objectMapper;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class JsonMixinForDataPackage {
}
/**
* 打包用json注解处理器
*/
public static class DataPackageJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
/**
* 这个方法的作用是处理设置了 {@link JsonIgnoreProperties} 的注解内容
* <p>
* 这里忽略所有标记了json忽略属性的注解{@link JsonIgnoreProperties}
*
* @param a 被注解的属性
* @return 返回不序列化注解属性的值这里默认返回一个空的忽略属性也就是让{@link JsonIgnoreProperties}注解失效
*/
@Override
public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated a) {
return JsonIgnoreProperties.Value.empty();
}
/**
* 这个方法的作用是处理所有设置或者标记为不参与序列化的业务
* <p>
* 这里所有标记了属性JSON的{@link JsonIgnore}注解以及其他有着类似性质的注解属性都无效都强制参与序列化 {@link Transient}注解除外
* 设置了{@link Transient}注解的属性不参与序列化在同步业务上这些属性不会写入数据库所以不需要序列化出来
*
* @param m 被注解的成员
* @return false 忽略所有标记
*/
@Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
// 标记了@transient注解的在打包是需要忽略掉
return m.hasAnnotation(Transient.class);
}
}
}

View File

@ -0,0 +1,26 @@
package com.aisino.iles.core.config;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.Locale;
import java.util.TimeZone;
public class LocaleTimezoneConfig implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
// 获取默认locale
String locale = context.getEnvironment().getProperty("spring.jackson.locale", Locale.getDefault().toString());
String timeZone = context.getEnvironment().getProperty("spring.jackson.time-zone", TimeZone.getDefault().toZoneId().toString());
// 设置默认时区
TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
// 设置默认区域
Locale.Builder builder = new Locale.Builder();
String[] localeParts = locale.split("_|-");
builder.setLanguage(localeParts[0]);
if (localeParts.length > 1)
builder.setRegion(localeParts[1]);
Locale.setDefault(builder.build());
}
}

View File

@ -0,0 +1,50 @@
package com.aisino.iles.core.config;
import com.aisino.iles.core.hibernate.RedissonRegionFactoryPlus;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.cfg.AvailableSettings;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.CompositeCodec;
import org.redisson.codec.Kryo5Codec;
import org.redisson.codec.LZ4Codec;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(@Value("${redisson.config}") String redissonClientConfig) throws IOException {
Config config = Config.fromYAML(redissonClientConfig);
// 设置锁超时时间
config.setLockWatchdogTimeout(10000);
// 使用Kryo编码器+Lz4编码器减少体积提高性能
config.setCodec(new CompositeCodec(new Kryo5Codec(), new LZ4Codec()));
return Redisson.create(config);
}
@AutoConfigureBefore(JpaRepositoriesAutoConfiguration.class)
@ConditionalOnProperty(name = "spring.jpa.properties.hibernate.cache.region.factory_class",havingValue = "redisson", matchIfMissing = true)
@Slf4j
public static class RedissonRegionFactoryAutoConfiguration {
@Bean
public RedissonRegionFactoryPlus redissonRegionFactoryPlus(RedissonClient redisson) {
return new RedissonRegionFactoryPlus(redisson);
}
@Bean
public HibernatePropertiesCustomizer redissonRegionFactoryCustomizer(RedissonRegionFactoryPlus redissonRegionFactoryPlus) {
// 注入自定义缓存实现
return (properties) -> properties.put(AvailableSettings.CACHE_REGION_FACTORY, redissonRegionFactoryPlus);
}
}
}

View File

@ -0,0 +1,252 @@
package com.aisino.iles.core.config;
import com.aisino.iles.common.util.StringUtils;
import com.aisino.iles.core.annotation.handlers.CurrentUserMethodArgResolver;
import com.aisino.iles.core.controller.GlobalExceptionController;
import com.aisino.iles.core.converter.DateConverter;
import com.aisino.iles.core.converter.ISO_DATE_TIME2LocalDateConverter;
import com.aisino.iles.core.converter.ISO_DATE_TIME2LocalDateTimeConverter;
import com.aisino.iles.core.converter.ValueEnumConverterFactory;
import com.aisino.iles.core.interceptor.AccessTokenInterceptor;
import com.aisino.iles.core.interceptor.CorsInterceptor;
import com.aisino.iles.core.interceptor.EncryptRequestResponseFilter;
import com.aisino.iles.core.interceptor.JsonTokenValidatorInterceptor;
import com.aisino.iles.core.repository.ResourceRepo;
import com.aisino.iles.core.repository.UserRepo;
import com.aisino.iles.core.service.DynamicEncryptService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.smartlx.sso.client.service.SsoClientService;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* WEB配置
*/
@Configuration
@Slf4j
@SuppressWarnings("unused")
public class WebConfig implements WebMvcConfigurer {
private final CurrentUserMethodArgResolver currentUserMethodArgResolver;
private final ResourceRepo resourceRepo;
private final UserRepo userRepo;
private final DynamicEncryptService dynamicEncryptService;
private final ObjectMapper objectMapper;
private final GlobalExceptionController globalExceptionController;
private final RedisTemplate<String, String> redisTemplate;
private final SsoClientService ssoClientService;
@Autowired
public WebConfig(CurrentUserMethodArgResolver currentUserMethodArgResolver,
ResourceRepo resourceRepo,
UserRepo userRepo,
DynamicEncryptService dynamicEncryptService,
ObjectMapper objectMapper,
GlobalExceptionController globalExceptionController, RedisTemplate<String, String> redisTemplate, SsoClientService ssoClientService) {
this.currentUserMethodArgResolver = currentUserMethodArgResolver;
this.resourceRepo = resourceRepo;
this.userRepo = userRepo;
this.dynamicEncryptService = dynamicEncryptService;
this.objectMapper = objectMapper;
this.globalExceptionController = globalExceptionController;
this.redisTemplate = redisTemplate;
this.ssoClientService = ssoClientService;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(currentUserMethodArgResolver);
}
/**
* 拦截器
*
* @param registry InterceptorRegistry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
/*
跨域拦截器
*/
registry.addInterceptor(corsInterceptor())
.addPathPatterns("/**");
registry.addInterceptor(accessTokenInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(
"/lawenforcement/v1/auth/login",
"/lawenforcement/v1/auth/getLoginUrl",
"/lawenforcement/v1/auth/logout",
"/lawenforcement/v1/auth/refreshToken",
"/lawenforcement/v1/auth/backend-token",
"/api/lawenforcement/enterprises/warn",
"/lawenforcement/v1/auth/refreshToken",
"/api/lawenforcement/deliveryRecords/smsStatusCallback",
"/api/lawenforcement/big-screen-system/**");
/*
用户令牌验证
*/
// registry.addInterceptor(jsonTokenInterceptor())
// .addPathPatterns("/logout")
// .addPathPatterns("/api/**");
}
/**
* 消息转换器配置
*
* @param converters 转换器列表
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 调整一下消息转换器顺序将String转换器放在最后
converters.removeIf(converter -> converter instanceof org.springframework.http.converter.StringHttpMessageConverter);
converters.add(new org.springframework.http.converter.StringHttpMessageConverter());
}
@Override
public void addFormatters(FormatterRegistry registry) {
// JSON的通用单个参数枚举类型转换器用于转换控制器类参数中的枚举类型
registry.addConverterFactory(new ValueEnumConverterFactory());
registry.addConverter(new DateConverter());
// 基于ISO_DATE_TIME格式的JAVA TIME类型转换器
registry.addConverter(new ISO_DATE_TIME2LocalDateTimeConverter());
registry.addConverter(new ISO_DATE_TIME2LocalDateConverter());
}
@Bean
@ConfigurationProperties("server.cors")
public HandlerInterceptor corsInterceptor() {
return new CorsInterceptor();
}
@Bean
public HandlerInterceptor jsonTokenInterceptor() {
return new JsonTokenValidatorInterceptor(resourceRepo, userRepo);
}
@Bean
public HandlerInterceptor accessTokenInterceptor() {
return new AccessTokenInterceptor(ssoClientService);
}
@Configuration
@ConditionalOnProperty(name = "server.http-port")
static class SslConfig {
/**
* 嵌入式容器配置 默认https 添加一个http的连接器
*
* @return 服务工厂定制配置器
*/
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(
Connector httpTomcatConnector,
@Value("${server.ssl.force-ssl-patterns}") String forceSslPatterns) {
return factory -> {
if (factory instanceof TomcatServletWebServerFactory) {
TomcatServletWebServerFactory fac = (TomcatServletWebServerFactory) factory;
// 支持http协议
fac.addAdditionalTomcatConnectors(httpTomcatConnector);
// 设置新的这个连接器的属性为主连接器的配置
fac.getTomcatConnectorCustomizers().forEach(tomcatConnectorCustomizer -> {
tomcatConnectorCustomizer.customize(httpTomcatConnector);
});
// ssl上下文设置
TomcatContextCustomizer sslContextCustomizer = context -> {
SecurityConstraint constraint = new SecurityConstraint();
constraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.setName("useSslURICollection");
// 需要强制使用ssl的路径匹配
Optional.ofNullable(forceSslPatterns).filter(StringUtils::isNotEmpty)
.map(fsp -> Arrays.asList(fsp.split(",")))
.ifPresent(patterns -> patterns.forEach(collection::addPattern));
constraint.addCollection(collection);
context.addConstraint(constraint);
};
fac.addContextCustomizers(sslContextCustomizer);
}
};
}
/**
* 创建http的连接器
*
* @return tomcat http连接器
*/
@Bean
public Connector createHttpConnector(ServerProperties serverProperties,
@Value("${server.http-port}") int httpPort) {
Http11NioProtocol http11NioProtocol = new Http11NioProtocol();
Connector connector = new Connector(http11NioProtocol);
connector.setPort(httpPort);
connector.setRedirectPort(serverProperties.getPort());
connector.setSecure(false);
return connector;
}
}
/**
* 自定义字符串属性处理工具注册器
* 处理去掉字符串两端空格
* 处理空字符串""转换为null
*
* @author hx
* @since 2021-03-16
*/
@ControllerAdvice
public static class StringTrimmerEditorRegister {
@InitBinder
public void initBinder(WebDataBinder binder) {
// 处理提交字符串参数两端包含空格还有空字符串""
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
}
/**
* 通用加密解密请求响应过滤器
*
* @return 过滤器注册器
*/
@Bean
public FilterRegistrationBean<EncryptRequestResponseFilter> encryptRequestResponseFilter() {
FilterRegistrationBean<EncryptRequestResponseFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new EncryptRequestResponseFilter(dynamicEncryptService, globalExceptionController, objectMapper));
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(0);
return filterRegistrationBean;
}
}

View File

@ -0,0 +1,73 @@
package com.aisino.iles.core.controller;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.common.model.Ok;
import com.aisino.iles.common.model.PageResult;
import com.aisino.iles.common.model.Result;
import com.aisino.iles.core.model.AddrInfo;
import com.aisino.iles.core.model.query.AddrQuery;
import com.aisino.iles.core.repository.DictItemRepo;
import com.aisino.iles.core.service.AddrInfoService;
import com.aisino.iles.core.service.StreetInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping(Constants.API_PREFIX + Constants.ApiIndustryCategoryPrefixes.SYSTEM)
public class AddrInfoController {
private static final String ADDR_PAGE_URI = "/addrs";
private static final String STREET_ADDR_URI = ADDR_PAGE_URI + "/streetaddr";
private final AddrInfoService service;
private final StreetInfoService streetInfoService;
private final DictItemRepo dictItemRepo;
@Autowired
public AddrInfoController(AddrInfoService service,
StreetInfoService streetInfoService,
DictItemRepo dictItemRepo) {
this.service = service;
this.streetInfoService = streetInfoService;
this.dictItemRepo = dictItemRepo;
}
/**
* 获取地址信息分页
*
* @param query 查询条件
* @return json结果
*/
@GetMapping(ADDR_PAGE_URI)
//@Log(type = OperateType.QUERY, value = "地址信息")
public PageResult<AddrInfo> pageAddrs(AddrQuery query) {
Page<AddrInfo> addrs = service.pageAddrs(query);
return PageResult.of(addrs);
}
@GetMapping(STREET_ADDR_URI)
public Result streetaddr(AddrQuery query) {
Map<String, String> map = new HashMap<>();
Map<String, String> result = new HashMap<>();
service.pageAddrs(query).get().findFirst().ifPresent(o -> {
map.put("xzqh", o.getXzqh());
map.put("mlph", o.getXxdz());
map.put("fwbm", o.getFwbm());
query.setDm(o.getJlh());
result.put("jingdu",o.getY());
result.put("weidu",o.getX());
result.put("zfsq",o.getZfsq());
});
streetInfoService.pageStreets(query).get().findFirst().ifPresent(o -> map.put("jd", o.getMc()));
dictItemRepo.findByDictDictCodeAndValue("dm_xzqh", map.get("xzqh")).ifPresent(o -> map.put("value", o.getDisplay()));
result.put("id", map.get("fwbm"));
result.put("value", map.get("value") + map.get("jd") + map.get("mlph"));
return Ok.of(result);
}
}

View File

@ -0,0 +1,148 @@
package com.aisino.iles.core.controller;
import com.aisino.iles.common.model.Fail;
import com.aisino.iles.common.model.Ok;
import com.aisino.iles.common.model.Result;
import com.aisino.iles.core.annotation.Log;
import com.aisino.iles.core.hibernate.RedissonLocalStorage;
import com.aisino.iles.core.model.enums.OperateType;
import com.aisino.iles.core.service.CacheAdminService;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/cache")
public class CacheAdminController {
private final CacheAdminService cacheAdminService;
public CacheAdminController(CacheAdminService cacheAdminService) {
this.cacheAdminService = cacheAdminService;
}
private static final String CACHE_REGIONS_URI = "/regions";
private static final String CACHE_REGION_URI = CACHE_REGIONS_URI + "/{region}";
private static final String CACHE_ENTRY_URI = "/{region}/{key}";
private static final String CACHE_ENTRY_CLASS_ID_URI = "/class-id/{className}/{id}";
private static final String CACHE_STATS_URI = "/stats/{region}";
private static final String CACHE_TTL_URI = "/ttl/{region}/{key}";
/**
* 获取所有缓存区域信息
* @return 缓存区域统计信息
*/
@GetMapping(CACHE_REGIONS_URI)
@Log(type = OperateType.QUERY, value = "缓存区域查询")
public Result<Map<String, Object>> getAllRegions() {
Map<String, RedissonLocalStorage> regions = cacheAdminService.getAllRegions();
Map<String, Object> result = new LinkedHashMap<>();
regions.forEach((name, storage) -> {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("size", storage.getSize());
dataMap.put("ttl", storage.getTtl());
dataMap.put("maxIdle", storage.getMaxIdle());
dataMap.put("liveTime", storage.getCacheTtl());
dataMap.put("liveSize", storage.getCacheSize());
// 最后一次更新时间
// dataMap.put("lastUpdate", storage.);
result.put(name, dataMap);
});
return Ok.of(result);
}
/**
* 删除缓存区域
* @param region 缓存区域名称
* @return 操作结果
*/
@DeleteMapping(CACHE_REGION_URI)
@Log(type = OperateType.REMOVE, value = "缓存区域删除")
public Result<?> removeRegion(@PathVariable String region) {
boolean success = cacheAdminService.removeRegion(region);
return success ? Ok.of() : Fail.of("操作失败");
}
/**
* 获取指定缓存条目
* @param region 缓存区域名称
* @param key 缓存键
* @return 缓存值或404
*/
@GetMapping(CACHE_ENTRY_URI)
@Log(type = OperateType.QUERY, value = "缓存条目查询")
public Result<?> getValue(
@PathVariable String region,
@PathVariable String key) {
return Ok.of(cacheAdminService.getCacheValue(region, key));
}
/**
* 根据实体类名和ID删除缓存条目
* @param className 实体类名
* @param id 实体ID
* @return 操作结果
*/
@DeleteMapping(CACHE_ENTRY_CLASS_ID_URI)
@Log(type = OperateType.REMOVE, value = "缓存条目删除")
public Result<?> removeValueByClassName(
@PathVariable String className,
@PathVariable Object id) {
boolean success = cacheAdminService.removeCacheValueByEntityManager(className,id);
return success ?
Ok.of() :
Fail.of("操作失败");
}
/**
* 刷新本地缓存
* @param region 缓存区域名称
* @param key 缓存键
* @return 操作结果
*/
@PostMapping(CACHE_ENTRY_URI + "/refresh")
@Log(type = OperateType.MODIFY, value = "本地缓存刷新")
public Result<?> refreshLocalCache(
@PathVariable String region,
@PathVariable String key) {
cacheAdminService.refreshLocalCache(region, key);
return Ok.of();
}
// /**
// * 获取缓存统计信息
// * @param region 缓存区域名称
// * @return 统计信息或404
// */
// @GetMapping(CACHE_STATS_URI)
// @Log(type = OperateType.QUERY, value = "缓存统计查询")
// public Result<LocalCachedMapStats> getStats(
// @PathVariable String region) {
// return cacheAdminService.getCacheStats(region)
// .map(Ok::of)
// .orElse(Ok.of());
// }
// /**
// * 设置缓存过期时间
// * @param region 缓存区域名称
// * @param key 缓存键
// * @param ttl 过期时间数值
// * @param unit 时间单位
// * @return 操作结果
// */
// @PostMapping(CACHE_TTL_URI)
// @Log(type = OperateType.MODIFY, value = "缓存TTL设置")
// public Result<?> setTTL(
// @PathVariable String region,
// @PathVariable String key,
// @RequestParam long ttl,
// @RequestParam TimeUnit unit) {
// boolean success = cacheAdminService.putCacheValue(region, key, ttl, unit);
// return success ?
// Ok.of() :
// Result.error("操作失败");
// }
}

View File

@ -0,0 +1,193 @@
package com.aisino.iles.core.controller;
import com.aisino.iles.common.model.Ok;
import com.aisino.iles.common.model.PageResult;
import com.aisino.iles.common.model.Result;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.annotation.Log;
import com.aisino.iles.core.model.Dict;
import com.aisino.iles.core.model.DictItem;
import com.aisino.iles.core.model.dto.DictDTO;
import com.aisino.iles.core.model.dto.DictItemDTO;
import com.aisino.iles.core.model.enums.OperateType;
import com.aisino.iles.core.model.query.DictItemQuery;
import com.aisino.iles.core.model.query.DictQuery;
import com.aisino.iles.core.service.DictService;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Set;
/**
* 字典控制器
*/
@RestController
@RequestMapping(Constants.API_PREFIX + Constants.ApiIndustryCategoryPrefixes.SYSTEM)
public class DictController {
private static final String DICT_URI = "/dicts";
private static final String DICT_SINGLE_URI = DICT_URI + "/{dictId}";
private static final String DICT_ITEM_URI = "/dictItems";
private static final String DICT_ITEM_SINGLE_URI = DICT_ITEM_URI + "/{dictItemId}";
private static final String DICT_ITEM_LIST_URI = DICT_ITEM_URI + "/list";
private final DictService dictService;
@Autowired
public DictController(DictService dictService) {
this.dictService = dictService;
}
/**
* 获取字典列表 分页
*
* @param page 页码
* @param pagesize 每页数
* @param sort 排序列
* @param dir 升降序
* @return 分页的字典信息
*/
@GetMapping(DICT_URI)
//@Log(type = OperateType.QUERY, value = "列表查询分页")
public PageResult<DictDTO> pageDicts(
@RequestParam(required = false) Integer page,
@RequestParam(required = false) Integer pagesize,
@RequestParam(required = false) String sort,
@RequestParam(required = false) String dir,
DictQuery dictQuery
) {
return PageResult.of(dictService.pageDicts(page,pagesize,sort,dir,dictQuery));
}
/**
* 新增字典
* @param dict 字典
* @return JSON结果
*/
@PostMapping(value = DICT_URI)
@Log(type = OperateType.ADD, value = "字典新增")
public Result<DictDTO> addDict(@NotNull @RequestBody Dict dict) {
return Ok.of(dictService.addDict(dict));
}
/**
* 修改字典
* @param dict 字典
* @param dictId 字典ID
* @return JSON结果
*/
@PutMapping(DICT_SINGLE_URI)
@Log(type = OperateType.MODIFY, value = "字典修改")
public Result<Dict> modifyDict(@NotNull @RequestBody Dict dict, @PathVariable String dictId) {
dict.setDictId(dictId);
dictService.modifyDict(dict);
return Ok.of();
}
/**
* 删除字典
* @param dictId 字典ID
* @return json结果
*/
@DeleteMapping(DICT_SINGLE_URI)
@Log(type = OperateType.REMOVE, value = "字典删除")
public Result<Dict> removeDict(@PathVariable String dictId) {
dictService.removeDict(dictId);
return Ok.of();
}
/**
* 批量删除字典信息
* @param dictIds 要删除的字典ID集合
* @return json结果
*/
@DeleteMapping(DICT_URI)
@Log(type = OperateType.REMOVE, value = "字典批量删除")
public Result<Dict> removeDicts(@RequestBody Set<String> dictIds) {
dictService.removeDicts(dictIds);
return Ok.of();
}
/**
* 获取字典项信息分页
* @param query 查询条件
* @return json结果
*/
@GetMapping(DICT_ITEM_URI)
//@Log(type = OperateType.QUERY, value = "字典项查询")
public PageResult<DictItem> pageDictItems(DictItemQuery query) {
Page<DictItem> dictItems = dictService.pageDictItems(query);
return PageResult.of(dictItems);
}
/**
* 获取字典项信息不分页
*
* @param query 查询条件
* @return json结果
*/
@GetMapping(DICT_ITEM_LIST_URI)
//@Log(type = OperateType.QUERY, value = "字典项列表查询(不分页)")
public Result<List<DictItemDTO>> listDictItems(DictItemQuery query) {
return Ok.of(dictService.listDictItems(query));
}
/**
* 添加字典项
* @param dictItem 字典项
* @return json结果
*/
@PostMapping(DICT_ITEM_URI)
@Log(type = OperateType.ADD, value = "字典项新增")
public Result<DictItem> addDictItem(@NotNull @RequestBody DictItem dictItem) {
return Ok.of(dictService.addDictItem(dictItem));
}
/**
* 修改字典项
* @param dictItem 字典项
* @param dictItemId 字典项id
* @return json结果
*/
@PutMapping(DICT_ITEM_SINGLE_URI)
@Log(type = OperateType.MODIFY, value = "字典项修改")
public Result<DictItem> modifyDictItem(@NotNull @RequestBody DictItem dictItem,@NotNull @PathVariable String dictItemId) {
dictItem.setDictItemId(dictItemId);
dictService.modifyDictItems(dictItem);
return Ok.of();
}
/**
* 删除字典项
* @param dictItemId 字典项
* @return json结果
*/
@DeleteMapping(DICT_ITEM_SINGLE_URI)
@Log(type = OperateType.REMOVE, value = "字典项删除")
public Result<DictItem> removeDictItem(@PathVariable String dictItemId) {
dictService.removeDictItem(dictItemId);
return Ok.of();
}
/**
* 查询单个字典数据
* @param dictId 字典ID
* @return json结果
*/
@GetMapping(DICT_SINGLE_URI)
//@Log(type = OperateType.QUERY, value = "字典详情")
public Result<DictDTO> findOneDict(@PathVariable String dictId) {
return dictService.findDictById(dictId).map(Ok::of).orElseGet(Ok::of);
}
/**
* 查询单个字典项
* @param dictItemId 字典项ID
* @return json结果
*/
@GetMapping(DICT_ITEM_SINGLE_URI)
//@Log(type = OperateType.QUERY, value = "字典项详情")
public Result<DictItem> findOneDictItem(@PathVariable String dictItemId) {
return dictService.findDictItemById(dictItemId).map(Ok::of)
.orElseGet(Ok::of);
}
}

View File

@ -0,0 +1,118 @@
package com.aisino.iles.core.controller;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.common.model.Result;
import com.aisino.iles.core.exception.ArgError;
import com.aisino.iles.core.exception.BusinessError;
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.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.stream.Collectors;
/**
* 全局异常处理控制器
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionController {
/**
* 验证消息(针对 path)
*
* @param e 验证异常
* @return 响应信息
*/
@ExceptionHandler(BindException.class)
public Result<Object> validatePath(BindException e) {
String msg = e.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(","));
ArgError error = new ArgError(msg);
return businessErrorHandler(error);
}
/**
* 验证消息(针对 param)
*
* @param error 参数验证异常
* @return 响应信息
*/
@ExceptionHandler(value = {ConstraintViolationException.class})
public Result<Object> ConstraintViolationExceptionHandler(ConstraintViolationException error) {
String msg = error.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(","));
return businessErrorHandler(new ArgError(msg));
}
/**
* 验证消息 (针对 body)
*
* @param e 验证异常
* @return 响应信息
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Object> validateBody(MethodArgumentNotValidException e) {
String msg = e.getBindingResult()
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(","));
ArgError error = new ArgError(msg);
return businessErrorHandler(error);
}
/**
* 反序列化错误
*
* @param e 错误对象
* @return 响应信息
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result<Object> httpMessageHandler(HttpMessageNotReadableException e) throws IOException {
String message = e.getMessage();
message = StrUtil.subAfter(message, ":", false);
message = StrUtil.subPre(message, 200);
// 对于 JSON 格式错误在控制台应该输出完整异常信息
log.error("反序列化错误:", e);
return businessErrorHandler(new BusinessError(message));
}
/**
* 业务错误处理器
*
* @return 信息
*/
@ExceptionHandler(value = {BusinessError.class})
public Result<Object> businessErrorHandler(BusinessError error) {
log.info("业务错误:", error);
return Result.of(error.getCode(), false, ExceptionUtil.getMessage(error), null);
}
/**
* 服务器异常处理
*
* @param error 服务器异常
* @return 信息
*/
@ExceptionHandler(value = {Exception.class})
public Result<Object> serverErrorHandler(Exception error) {
log.info("服务器异常:", error);
return Result.of(Constants.Exceptions.server_error,
false,
"服务器发生了错误,请联系管理员" + ExceptionUtil.getRootCauseMessage(error),
null);
}
}

View File

@ -0,0 +1,119 @@
package com.aisino.iles.core.controller;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.common.iface.Logger;
import com.aisino.iles.common.model.Ok;
import com.aisino.iles.common.model.PageResult;
import com.aisino.iles.common.model.Result;
import com.aisino.iles.core.annotation.Log;
import com.aisino.iles.core.model.GlobalParam;
import com.aisino.iles.core.model.enums.OperateType;
import com.aisino.iles.core.service.GlobalParamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.constraints.NotNull;
import java.util.Set;
/**
* 公共/系统管理/全局参数管理
* @author hx
* @since 2023-08-31
*/
@RestController
@RequestMapping(Constants.API_PREFIX + Constants.ApiIndustryCategoryPrefixes.SYSTEM)
public class GlobalParamController implements Logger {
private static final String GLOBAL_PARAM_URI = "/globalParams";
private static final String GLOBAL_PARAM_SINGLE_URI = GLOBAL_PARAM_URI + "/{globalParamCode}";
private final GlobalParamService globalParamService;
@Autowired
public GlobalParamController(GlobalParamService globalParamService) {
this.globalParamService = globalParamService;
}
/**
* 获取全局参数列表分页
* @param page 页码
* @param pagesize 每页
* @param sort 排序列名称
* @param dir 升降序
* @param globalParamCode 全局参数代码
* @param globalParamName 全局参数名称
* @param globalParamValue 全局参数值
* @return json数据
*/
@GetMapping(GLOBAL_PARAM_URI)
@Log(type = OperateType.QUERY, value = "全局参数查询")
public PageResult<GlobalParam> list(
@RequestParam(required = false,defaultValue = "1") Integer page,
@RequestParam(required = false,defaultValue = "20") Integer pagesize,
@RequestParam(required = false,defaultValue = "globalParamCode") String sort,
@RequestParam(required = false,defaultValue = "desc") String dir,
@RequestParam(required = false) String globalParamCode,
@RequestParam(required = false) String globalParamName,
@RequestParam(required = false) String globalParamValue
) {
return PageResult.of(globalParamService.listGlobalParams(page, pagesize, sort, dir, globalParamCode, globalParamName, globalParamValue));
}
/**
* 新增全局参数
* @param globalParam 全局参数
* @return 结果
*/
@PostMapping(GLOBAL_PARAM_URI)
@Log(type = OperateType.ADD, value = "全局参数新增")
public Result<GlobalParam> add(@NotNull @RequestBody GlobalParam globalParam) {
return Ok.of(globalParamService.saveGlobalParam(globalParam));
}
/**
* 修改全局参数
* @param globalParam 全局参数
* @param globalParamCode 全局参数代码
* @return 结果
*/
@PutMapping(GLOBAL_PARAM_SINGLE_URI)
@Log(type = OperateType.MODIFY, value = "全局参数修改")
public Result<GlobalParam> modify(@NotNull @RequestBody GlobalParam globalParam, @PathVariable String globalParamCode) {
globalParam.setGlobalParamCode(globalParamCode);
globalParamService.modifyGlobalParam(globalParam);
return Ok.of();
}
/**
* 删除全局参数
* @param globalParamCode 全局参数代码
* @return 结果
*/
@DeleteMapping(GLOBAL_PARAM_SINGLE_URI)
@Log(type = OperateType.REMOVE, value = "全局参数删除")
public Result<Object> remove(@NotNull @PathVariable String globalParamCode) {
globalParamService.removeGlobalParam(globalParamCode);
return Ok.of();
}
/**
* 批量删除全局参数
* @param globalParamCodes 全局参数代码
* @return 结果
*/
@DeleteMapping(GLOBAL_PARAM_URI)
@Log(type = OperateType.REMOVE, value = "全局参数批量删除")
public Result<Object> removeAll(@NotNull @RequestBody Set<String> globalParamCodes) {
globalParamService.removeGlobalParams(globalParamCodes);
return Ok.of();
}
/**
* 通过代码查询单个全局参数
* @param globalParamCode 全局参数代码
* @return 全局参数
*/
@GetMapping(GLOBAL_PARAM_SINGLE_URI)
@Log(type = OperateType.QUERY, value = "查询单个全局参数")
public Result<GlobalParam> get(@NotNull @PathVariable String globalParamCode) {
return globalParamService.findByCode(globalParamCode).map(Ok::of).orElseGet(Ok::of);
}
}

View File

@ -0,0 +1,79 @@
package com.aisino.iles.core.controller;
import cn.hutool.json.JSONObject;
import com.aisino.iles.common.model.Fail;
import com.aisino.iles.common.model.Ok;
import com.aisino.iles.common.model.PageResult;
import com.aisino.iles.common.model.Result;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* 通用rest接口返回统一处理
*
* @author hx
* @since 2022-03-15
*/
@RestControllerAdvice
public class GlobalResponseController implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
!returnType.getMethod().getReturnType().getName().equals("com.aisino.socialcollect.uploadinterface.models.UploadInterfaceResult");
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (body == null)
return Ok.of();
if (Result.class.isAssignableFrom(returnType.getParameterType()))
return body;
if (Collection.class.isAssignableFrom(returnType.getParameterType())) {
return Ok.of((Collection)body);
}
if (Map.class.isAssignableFrom(returnType.getParameterType())) {
Map map = (Map) body;
if (map.containsKey("success") && map.containsKey("code"))
return body;
}
if (String.class.isAssignableFrom(returnType.getParameterType())
&& MediaType.APPLICATION_JSON == selectedContentType)
return Ok.of(body);
if (Page.class.isAssignableFrom(returnType.getParameterType())) {
return PageResult.of((Page) body);
}
if (Exception.class.isAssignableFrom(body.getClass())) {
Exception ex = (Exception) body;
return Fail.of(ex.getMessage(), 1);
}
if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())
&& body instanceof Map) {
Map<String, Object> map = (Map<String, Object>) body;
if (map.keySet().containsAll(Arrays.asList("path", "error", "status", "timestamp", "message"))) {
JSONObject json = new JSONObject(map);
Fail<Object> of = Fail.of(json.getStr("error") + json.getStr("message"), json.getInt("status"));
of.setData(Collections.singletonList(json));
return of;
}
}
// 默认为所有json类型的结果包裹成Result结构
return Ok.of(body);
}
}

View File

@ -0,0 +1,38 @@
package com.aisino.iles.core.controller;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.GlobalParam;
import com.aisino.iles.core.service.GlobalParamService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 公共/系统管理/全局参数管理
* 不需要登录的全局参数接口
* @author hx
* @since 2023-08-31
*/
@RestController
@RequestMapping(Constants.NO_AUTH_API_PREFIX + Constants.ApiIndustryCategoryPrefixes.SYSTEM)
@Slf4j
public class NoAuthGlobalParamController {
private static final String GLOBAL_PARAM_URI = "/globalParams";
private final GlobalParamService globalParamService;
public NoAuthGlobalParamController(GlobalParamService globalParamService) {
this.globalParamService = globalParamService;
}
/**
* 获取不需要登录的全局参数信息
* @return 全局参数信息
*/
@GetMapping(GLOBAL_PARAM_URI)
public List<GlobalParam> list() {
return globalParamService.listNoAuth();
}
}

View File

@ -0,0 +1,55 @@
package com.aisino.iles.core.controller;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.common.model.Ok;
import com.aisino.iles.common.model.PageResult;
import com.aisino.iles.common.model.Result;
import com.aisino.iles.core.model.OperateLog;
import com.aisino.iles.core.model.query.OperateLogQuery;
import com.aisino.iles.core.service.OperateLogService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 操作日志接口
*
* @author huxin
* @since 2020-10-13
*/
@RestController
@RequestMapping(Constants.API_PREFIX + Constants.ApiIndustryCategoryPrefixes.SYSTEM + "/operate-logs")
public class OperateLogController {
/**
* 单个操作日志数据URI
*/
private static final String OPERATE_LOG_SINGLE_URI = "/{operateLogId}";
private final OperateLogService operateLogService;
public OperateLogController(OperateLogService operateLogService) {
this.operateLogService = operateLogService;
}
/**
* 分页查询操作日志接口
*
* @param query 操作日志查询条件
* @return 分页的操作日志json响应数据
*/
@GetMapping
public PageResult<OperateLog> findOperateLogForPage(OperateLogQuery query) {
return PageResult.of(operateLogService.findOperateLogForPage(query));
}
/**
* 操作日志详细信息查询接口
*
* @param operateLogId 操作日志主键
* @return 操作日志详细信息JSON响应数据
*/
@GetMapping(OPERATE_LOG_SINGLE_URI)
public Result<OperateLog> findOperateLogDetail(@PathVariable String operateLogId) {
return operateLogService.findOperateLogDetailById(operateLogId).map(Ok::of).orElseGet(Ok::of);
}
}

View File

@ -0,0 +1,40 @@
package com.aisino.iles.core.controller;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.common.model.PageResult;
import com.aisino.iles.core.model.StreetInfo;
import com.aisino.iles.core.model.query.AddrQuery;
import com.aisino.iles.core.service.StreetInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(Constants.API_PREFIX + Constants.ApiIndustryCategoryPrefixes.SYSTEM)
public class StreetInfoController {
private final StreetInfoService service;
private static final String STREET_PAGE_URI = "/streets";
@Autowired
public StreetInfoController(StreetInfoService service) {
this.service = service;
}
/**
* 获取街道信息分页
* @param query 查询条件
* @return json结果
*/
@GetMapping(STREET_PAGE_URI)
//@Log(type = OperateType.QUERY, value = "街道信息")
public PageResult<StreetInfo> pageAddrs(AddrQuery query) {
Page<StreetInfo> streets = service.pageStreets(query);
return PageResult.of(streets);
}
}

View File

@ -0,0 +1,31 @@
package com.aisino.iles.core.converter;
import com.aisino.iles.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import java.text.SimpleDateFormat;
import java.util.Date;
@Slf4j
public class DateConverter implements Converter<String, Date> {
private SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
private SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public Date convert(String s) {
if(StringUtils.isEmpty(s)){
return null;
}
try {
if(s.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}$")){//yyyy-MM-dd HH:mm:ss
return sdf2.parse(s);
}else if(s.matches("^\\d{4}-\\d{2}-\\d{2}$")){//yyyy-MM-dd
return sdf1.parse(s);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,17 @@
package com.aisino.iles.core.converter;
import com.aisino.iles.common.util.StringUtils;
import org.springframework.core.convert.converter.Converter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
public class ISO_DATE2LocalDateConverter implements Converter<String, LocalDate> {
@Override
public LocalDate convert(String s) {
return Optional.ofNullable(s).filter(StringUtils::isNotEmpty)
.map(ds->LocalDate.parse(ds, DateTimeFormatter.ISO_DATE))
.orElse(null);
}
}

View File

@ -0,0 +1,30 @@
package com.aisino.iles.core.converter;
import com.aisino.iles.common.util.StringUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.TimeZone;
@Component
public class ISO_DATE_TIME2LocalDateConverter implements Converter<String, LocalDate> {
@Override
public LocalDate convert(String dateStr) {
return Optional.ofNullable(dateStr).filter(StringUtils::isNotEmpty)
.map(ds-> {
if (ds.contains("T") && ds.contains("Z")) {
return Instant.parse(ds).atZone(TimeZone.getDefault().toZoneId()).toLocalDate();
} else if (ds.contains("T")) {
return LocalDateTime.parse(ds, DateTimeFormatter.ISO_DATE_TIME).toLocalDate();
} else {
return LocalDate.parse(ds,DateTimeFormatter.ISO_DATE);
}
})
.orElse(null);
}
}

View File

@ -0,0 +1,48 @@
package com.aisino.iles.core.converter;
import com.aisino.iles.common.util.StringUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.TimeZone;
/**
* 时间转换器 时间字符串类型转为本地时间
* 支持格式yyyy-MM-dd,yyyy-MM-dd HH:mm:ss,yyyy-MM-ddTHH:mm:ss,yyyy-MM-ddTHH:mm:ss.zzzZ,yyyy-MM-ddTHH:mm:ss.zzzzzzZ
*
* @author huxin
* @since 2020-06
*/
@Component
public class ISO_DATE_TIME2LocalDateTimeConverter implements Converter<String, LocalDateTime> {
private final DateTimeFormatter yyyyMMddHHmmss = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
private final DateTimeFormatter yyyyMMddHHmmss_no_sign = DateTimeFormatter.ofPattern("uuuuMMddHHmmss");
@Override
public LocalDateTime convert(String dtStr) {
return Optional.of(dtStr).filter(StringUtils::isNotEmpty)
.map(dts -> {
if (dts.contains("T") && dts.contains("Z")) {
return Instant.parse(dts).atZone(TimeZone.getDefault().toZoneId()).toLocalDateTime();
} else if (dts.contains("T")) {
return LocalDateTime.parse(dts, DateTimeFormatter.ISO_DATE_TIME);
} else {
// 根据长度判断时间类型, 长度=10
if (dts.length() <= 10)
return LocalDate.parse(dts, DateTimeFormatter.ISO_DATE).atTime(0, 0);
else
// 不带T的时间类型转换
try {
return LocalDateTime.parse(dts, yyyyMMddHHmmss);
} catch (Exception e) {
return LocalDateTime.parse(dts,yyyyMMddHHmmss_no_sign);
}
}
})
.orElse(null);
}
}

View File

@ -0,0 +1,48 @@
package com.aisino.iles.core.converter;
import com.aisino.iles.common.iface.Logger;
import com.aisino.iles.core.model.ValueEnum;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
/**
* 字符串转换枚举类型的转换器工厂
*/
public class ValueEnumConverterFactory implements ConverterFactory<String, ValueEnum<String>> {
@Override
public <T extends ValueEnum<String>> Converter<String, T> getConverter(Class<T> aClass) {
// 主要用于获得被转换对象的类信息.
return new ValueEnumConverter<>(aClass);
}
/**
* 通用枚举转换器
* @param <T>
* @param <C>
*/
public static class ValueEnumConverter<T extends ValueEnum<C>,C> implements Converter<C,T>, Logger {
private final Class<T> tClass;
public ValueEnumConverter(Class<T> tClass) {
this.tClass = tClass;
}
@Override
@SuppressWarnings("unchecked")
public T convert(C c) {
try {
T[] values = (T[]) tClass.getMethod("values").invoke(tClass);
return Arrays.stream(values).filter(v -> v.getValue().equals(c)).findFirst()
.orElse(null);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
logger().error(e.getMessage(),e);
}
return null;
}
}
}

View File

@ -0,0 +1,132 @@
package com.aisino.iles.core.converter.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.datatype.jdk8.Jdk8BeanSerializerModifier;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.hibernate.collection.spi.PersistentCollection;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 不序列化需要延迟加载的集合和对象
*/
@Slf4j
public class DataPackageHibernateDontLazySerializerModifier extends Jdk8BeanSerializerModifier {
/**
* 一对多序列化方法
*/
private final JsonSerializer<Object> o2mSerializer = new JsonSerializer<Object>() {
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNull();
}
};
/**
* 多对多序列化方法
*/
private final JsonSerializer<Object> m2mSerializer = new JsonSerializer<Object>() {
public void writeCollection(Collection<Object> collection, JsonGenerator gen) throws IOException {
gen.writeStartArray();
collection.forEach(c -> {
try {
if (c == null)
gen.writeNull();
else {
Class<?> cClass = c.getClass();
Map<String, Object> primaryObject = new HashMap<>();
Map<String, Object> cps = PropertyUtils.describe(c);
FieldUtils.getFieldsListWithAnnotation(cClass, Id.class).forEach(idf -> {
primaryObject.put(idf.getName(), cps.get(idf.getName()));
});
if (!primaryObject.isEmpty()) {
gen.writeObject(primaryObject);
}
}
} catch (IOException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
log.error(e.getMessage(), e);
}
});
gen.writeEndArray();
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (PersistentCollection.class.isAssignableFrom(value.getClass())) {
PersistentCollection persistentCollection = (PersistentCollection) value;
// 没有懒加载的情况下用自定义
if (!persistentCollection.wasInitialized()) {
gen.writeNull();
} else {
if (Collection.class.isAssignableFrom(persistentCollection.getClass())) {
// 处理集合类型
writeCollection(((Collection<Object>) persistentCollection), gen);
} else {
// 处理集合类型以外的情况
serializers.defaultSerializeValue(value, gen);
}
}
} else {
// 处理集合类型以外的情况
writeCollection(((Collection<Object>) value), gen);
}
}
};
/**
* 多对一序列化方法
*/
private final JsonSerializer<Object> m2oSerializer = new JsonSerializer<Object>() {
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
Class<?> vClass = value.getClass();
Map<String, Object> primaryObject = new HashMap<>();
FieldUtils.getFieldsListWithAnnotation(vClass, Id.class)
.stream().map(Field::getName)
.forEach(idf -> {
try {
primaryObject.put(idf, PropertyUtils.getProperty(value, idf));
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
log.error(e.getMessage(), e);
}
});
if (!primaryObject.isEmpty()) {
gen.writeObject(primaryObject);
} else
gen.writeNull();
}
};
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
List<BeanPropertyWriter> beanPropertyWriters = super.changeProperties(config, beanDesc, beanProperties);
beanPropertyWriters.forEach(bpw -> {
if (bpw.getAnnotation(OneToMany.class) != null) {
bpw.assignSerializer(o2mSerializer);
} else if (bpw.getAnnotation(ManyToMany.class) != null) {
bpw.assignSerializer(m2mSerializer);
} else if (bpw.getAnnotation(ManyToOne.class) != null) {
bpw.assignSerializer(m2oSerializer);
}
});
return beanPropertyWriters;
}
}

View File

@ -0,0 +1,29 @@
package com.aisino.iles.core.converter.json;
import com.aisino.iles.core.converter.ISO_DATE_TIME2LocalDateConverter;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.LocalDate;
public class HttpJackson2LocalDateConverter extends JsonDeserializer<LocalDate> {
private final ISO_DATE_TIME2LocalDateConverter iso_date_time2LocalDateConverter;
public HttpJackson2LocalDateConverter() {
this.iso_date_time2LocalDateConverter = new ISO_DATE_TIME2LocalDateConverter();
}
@Override
public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return iso_date_time2LocalDateConverter.convert(jsonParser.getText());
}
@Override
public Class<?> handledType() {
return LocalDate.class;
}
}

View File

@ -0,0 +1,38 @@
package com.aisino.iles.core.converter.json;
import com.aisino.iles.core.converter.ISO_DATE_TIME2LocalDateTimeConverter;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.time.LocalDateTime;
/**
* 按理说客户端不应该左右时间的值格式通过Date.toJson方法可以把本地时间转换为UTC时间
* 然后使用ISO_DATE 或者 ISO_DATE_TIME的格式风格通过默认的这个格式把时间转换为服务器本地时间
* 这样来操作时间比较准确和谨慎即便出现了跨越时区的情况也可以应对
* 但是这里为了兼容之前已经存在的设置了本地时间的value-format的时间格式使用value-formate指定yyyy-MM-dd HH:mm:ss的时间属性
* 按照本地时间的方式来进行转换
*/
@Component
public class HttpJackson2LocalDateTimeConverter extends JsonDeserializer<LocalDateTime> {
private final ISO_DATE_TIME2LocalDateTimeConverter iso_date_time2LocaleDateTimeConverter;
@Autowired
public HttpJackson2LocalDateTimeConverter() {
this.iso_date_time2LocaleDateTimeConverter = new ISO_DATE_TIME2LocalDateTimeConverter();
}
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return iso_date_time2LocaleDateTimeConverter.convert(jsonParser.getText());
}
@Override
public Class<?> handledType() {
return LocalDateTime.class;
}
}

View File

@ -0,0 +1,81 @@
package com.aisino.iles.core.converter.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer;
import com.fasterxml.jackson.datatype.jdk8.Jdk8BeanSerializerModifier;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@Slf4j
public class ManyToOneSerializerModifier extends Jdk8BeanSerializerModifier {
private final JsonSerializer<Object> manyToOneSerializer = new JsonSerializer<Object>() {
@SneakyThrows
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
Object idObj = value.getClass().newInstance();
Arrays.stream(value.getClass().getDeclaredFields())
.filter(f ->
f.getType().isArray() || Map.class.isAssignableFrom(f.getType())
|| Collection.class.isAssignableFrom(f.getType())
).map(Field::getName)
.forEach(n -> {
try {
PropertyUtils.setProperty(idObj, n, null);
} catch (Exception e) {
log.error("json 错误:{}", e);
}
});
Arrays.stream(value.getClass().getDeclaredFields())
.filter(f -> f.isAnnotationPresent(Id.class))
.map(Field::getName)
.forEach(n -> {
try {
Object v = PropertyUtils.getProperty(value, n);
log.debug("field[{}]:{}", n, v);
PropertyUtils.setProperty(idObj, n, v);
} catch (Exception e) {
log.error("json 错误:{}", e);
}
});
serializers.defaultSerializeValue(idObj, gen);
}
@Override
public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
serialize(value, gen, serializers);
}
@Override
public Class<Object> handledType() {
return Object.class;
}
};
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
List<BeanPropertyWriter> bpws = super.changeProperties(config, beanDesc, beanProperties);
bpws.forEach(bpw -> {
if (bpw.getAnnotation(ManyToOne.class) != null) {
//bpw.assignTypeSerializer(new Ser(bpw.getTypeSerializer().getTypeIdResolver(),bpw));
bpw.assignSerializer(new TypeWrappedSerializer(bpw.getTypeSerializer(), manyToOneSerializer));
}
});
return bpws;
}
}

View File

@ -0,0 +1,41 @@
package com.aisino.iles.core.converter.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.datatype.jdk8.Jdk8BeanSerializerModifier;
import java.io.IOException;
import java.util.List;
/**
* 用于转换默认为null的数组或者集合类型的数据,到默认空数组,而不是 null
*/
public class SerializerModifier extends Jdk8BeanSerializerModifier {
/**
* 空数组序列化方法
*/
private final JsonSerializer<Object> nullArraySerializer = new JsonSerializer<Object>() {
@Override
public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
// 数组开括号
jsonGenerator.writeStartArray();
// 数组闭括号
jsonGenerator.writeEndArray();
}
};
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
List<BeanPropertyWriter> beanPropertyWriters = super.changeProperties(config, beanDesc, beanProperties);
beanPropertyWriters.forEach(bpw -> {
if (bpw.getType().isArrayType() || bpw.getType().isCollectionLikeType()) {
bpw.assignNullSerializer(nullArraySerializer);
}
});
return beanPropertyWriters;
}
}

View File

@ -0,0 +1,22 @@
package com.aisino.iles.core.converter.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
/**
* 两端包含空格的字符串处理反序列化器
*
* @author hx
* @since 2021-03-16
*/
public class StringTrimDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return p.getText().trim();
}
}

View File

@ -0,0 +1,27 @@
package com.aisino.iles.core.converter.persistence;
import jakarta.persistence.AttributeConverter;
import java.util.Optional;
import java.util.stream.Stream;
/**
* 菜单图标类型 字符串数组转varchar的转换器
*/
public class IconClsStringArray2VarcharConverter implements AttributeConverter<String[], String> {
@Override
public String convertToDatabaseColumn(String[] attribute) {
return Optional.ofNullable(attribute).map(attr -> String.join(",", attr)).orElse(null);
}
@Override
public String[] convertToEntityAttribute(String dbData) {
return Optional.ofNullable(dbData).map(db -> {
String[] dbs = db.split(",");
if (dbs.length == 1) {
return Stream.of("fas", dbs[0]).toArray(String[]::new);
} else {
return dbs;
}
}).orElse(null);
}
}

View File

@ -0,0 +1,27 @@
package com.aisino.iles.core.converter.persistence;
import jakarta.persistence.AttributeConverter;
import java.util.Optional;
import java.util.stream.Stream;
/**
* 字符串数组转换器
*/
public class StringArray2VarcharConverter implements AttributeConverter<String[], String> {
@Override
public String convertToDatabaseColumn(String[] attribute) {
return Optional.ofNullable(attribute).map(attr -> String.join(",", attr)).orElse(null);
}
@Override
public String[] convertToEntityAttribute(String dbData) {
return Optional.ofNullable(dbData).map(db -> {
String[] dbs = db.split(",");
if (dbs.length == 1) {
return Stream.of(dbs[0]).toArray(String[]::new);
} else {
return dbs;
}
}).orElse(null);
}
}

View File

@ -0,0 +1,46 @@
package com.aisino.iles.core.converter.persistence;
import com.aisino.iles.common.iface.Logger;
import com.aisino.iles.common.util.StringUtils;
import com.aisino.iles.core.exception.BusinessError;
import com.aisino.iles.core.model.ValueEnum;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
@Converter(autoApply = true)
@SuppressWarnings("unchecked")
public abstract class ValueEnumConverter<X, T extends ValueEnum<X>> implements AttributeConverter<T, X>, Logger {
private final Class<T> tClass;
public ValueEnumConverter() {
this.tClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}
@Override
public X convertToDatabaseColumn(T t) {
if (t == null)
return null;
return t.getValue();
}
@Override
public T convertToEntityAttribute(X x) {
if (x == null)
return null;
if (x instanceof String && StringUtils.isEmpty(x.toString()))
return null;
try {
return Arrays.stream(((T[]) tClass.getMethod("values").invoke(tClass)))
.filter(v -> v.getValue().equals(x)).findFirst().orElseThrow(() -> new BusinessError("这个值[" + x + "], 无法通过值在[" + tClass.getName() + "]里找到对应的枚举类型!"));
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
logger().error(e.getMessage(), e);
}
return null;
}
}

View File

@ -0,0 +1,22 @@
package com.aisino.iles.core.exception;
import com.aisino.iles.common.util.Constants;
/**
* 参数错误,用于方法的参数验证抛出
*/
public class ArgError extends BusinessError {
private static String msg = "参数错误";
public ArgError() {
super(msg, Constants.Exceptions.arg_error);
}
public ArgError(String msg, Throwable throwable) {
super(ArgError.msg+":"+msg, Constants.Exceptions.arg_error, throwable);
}
public ArgError(String msg) {
super(ArgError.msg+":"+msg, Constants.Exceptions.arg_error);
}
}

View File

@ -0,0 +1,38 @@
package com.aisino.iles.core.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 业务异常
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class BusinessError extends RuntimeException {
Integer code = 1;
public BusinessError(Throwable throwable) {
super(throwable);
}
public BusinessError(String msg, Throwable throwable) {
super(msg, throwable);
}
public BusinessError() {
super();
}
public BusinessError(String msg, Integer code, Throwable throwable) {
super(msg, throwable);
this.code = code;
}
public BusinessError(String msg, Integer code) {
super(msg);
this.code = code;
}
public BusinessError(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,29 @@
package com.aisino.iles.core.exception;
import com.aisino.iles.common.util.Constants;
public class JitdLoginError extends BusinessError {
private final static String msg = "吉大正元证书登录错误";
private String jitdReturnErrorCode = null;
public JitdLoginError() {
super(msg, Constants.Exceptions.jitd_sign_login);
}
public JitdLoginError(String msg) {
super(JitdLoginError.msg+": "+msg, Constants.Exceptions.jitd_sign_login);
}
public JitdLoginError(String msg, Throwable throwable) {
super(JitdLoginError.msg+": "+msg, Constants.Exceptions.jitd_sign_login, throwable);
}
public JitdLoginError(String msg, String errCode) {
super(JitdLoginError.msg+": "+msg, Constants.Exceptions.jitd_sign_login);
jitdReturnErrorCode = errCode;
}
public String getJitdReturnErrorCode() {
return jitdReturnErrorCode;
}
}

View File

@ -0,0 +1,32 @@
package com.aisino.iles.core.exception;
import com.aisino.iles.common.util.Constants;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 机构信息未找到错误
*
* @author huxin
* @since 2021-01-19
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class JurisdictionNotFoundError extends BusinessError {
private static final String msg = "机构信息不存在";
private static final String detailMsg = "机构ID为[{jurisdictionId}]的机构信息在系统里不存在";
private static final Integer errorCode = Constants.Exceptions.jurisdiction_not_found_error;
public JurisdictionNotFoundError(String jurisdictionId) {
super(detailMsg.replaceAll("\\{jurisdictionId\\}", jurisdictionId), errorCode);
}
public JurisdictionNotFoundError(String msg, Throwable throwable) {
super(msg, errorCode, throwable);
}
public JurisdictionNotFoundError() {
super(msg, errorCode);
}
}

View File

@ -0,0 +1,29 @@
package com.aisino.iles.core.exception;
import com.aisino.iles.common.util.Constants;
/**
* 登录错误
*/
public class LoginError extends BusinessError {
private static final String msg = "登录错误";
public LoginError() {
super(LoginError.msg, Constants.Exceptions.login_error);
}
public LoginError(String msg) {
super(LoginError.msg + ":" + msg, Constants.Exceptions.login_error);
}
public LoginError(String msg,Throwable throwable){
super(LoginError.msg + ":" + msg, Constants.Exceptions.login_error, throwable);
}
public LoginError(String msg, Integer code, Throwable throwable) {
super(LoginError.msg + ":" +msg, code, throwable);
}
public LoginError(String msg, Integer code) {
super(LoginError.msg + ":" +msg, code);
}
}

View File

@ -0,0 +1,8 @@
package com.aisino.iles.core.exception;
public class LoginFiveTimesWrongError extends LoginError {
private static final String msg = "登录错误";
public LoginFiveTimesWrongError(String msg, Integer code) {
super(LoginFiveTimesWrongError.msg + ":" +msg, code);
}
}

View File

@ -0,0 +1,27 @@
package com.aisino.iles.core.exception;
import com.aisino.iles.common.util.Constants;
/**
* 令牌错误
*/
public class TokenError extends BusinessError {
private static final String msg = "令牌错误";
public TokenError() {
super(TokenError.msg, Constants.Exceptions.token_error);
}
public TokenError(Throwable throwable) {
super(TokenError.msg, Constants.Exceptions.token_error, throwable);
}
public TokenError(String msg,Throwable throwable){
super(TokenError.msg + ": " + msg, Constants.Exceptions.token_error, throwable);
}
public TokenError(String msg) {
super(TokenError.msg + ":" + msg, Constants.Exceptions.token_error);
}
public TokenError(int code, String msg) {
super(TokenError.msg + ":" + msg, code);
}
}

View File

@ -0,0 +1,33 @@
package com.aisino.iles.core.exception;
import com.aisino.iles.common.util.Constants;
/**
* 文件上传错误
*
* @author huxin
* @since 2020-10-08
*/
public class UploadFileError extends BusinessError {
private static final String msg = "文件上传错误";
public UploadFileError() {
super(UploadFileError.msg, Constants.Exceptions.upload_file_error);
}
public UploadFileError(String msg) {
super(UploadFileError.msg + ":" + msg, Constants.Exceptions.upload_file_error);
}
public UploadFileError(String msg, Throwable throwable) {
super(UploadFileError.msg + ":" + msg, Constants.Exceptions.upload_file_error, throwable);
}
public UploadFileError(String msg, Integer code) {
super(UploadFileError.msg + ":" + msg, code);
}
public UploadFileError(String msg, Integer code, Throwable throwable) {
super(UploadFileError.msg + ":" + msg, code, throwable);
}
}

View File

@ -0,0 +1,16 @@
package com.aisino.iles.core.exception;
import com.aisino.iles.common.util.Constants;
/**
* 用户没有关联机构异常
*/
public class UserNoRelateJurisdictionError extends BusinessError {
private static final String msg = "该用户没有关联机构";
public UserNoRelateJurisdictionError() {
super(msg, Constants.Exceptions.user_no_relate_jurisdiciton_error);
}
}

View File

@ -0,0 +1,142 @@
package com.aisino.iles.core.hibernate;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.common.model.Message;
import com.aisino.iles.common.model.MessageProperties;
import com.aisino.iles.common.model.Result;
import com.aisino.iles.core.model.Token;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import jakarta.annotation.Resource;
import java.net.URI;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 抽象数据包发送kafka处理业务
*
* @author hx
* @since 20241029
*/
@Slf4j
public abstract class AbstractDataPackSendToKafkaProcessor implements DataPackSingleRecordProcessor {
@Resource
private RestTemplate restTemplate;
@Resource
private RedissonClient redisson;
@Resource
private MessageProperties messageProperties;
@Override
public void invoke(PackData data) {
buildMessage(data).forEach(this::sendMessageToKafka);
}
@Override
public boolean support(PackData data) {
return false;
}
/**
* 构建消息
*
* @param data 数据包
* @return 发送信息
*/
public abstract List<Message> buildMessage(PackData data);
/**
* 发送消息到kafka
*
* @param message 消息内容
*/
void sendMessageToKafka(Message message) {
String token = Optional.ofNullable(redisson.<String>getBucket(Constants.RedisKeyPrefix.messageToken.getValue()).get())
.orElseGet(() -> {
RLock lock = redisson.getLock(Constants.RedisKeyPrefix.messageGenerateToken.getValue());
AtomicReference<String> mtk = new AtomicReference<>();
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 登录消息服务用户,拿到token
Map<String, Object> params = new HashMap<>();
params.put("username", messageProperties.getUsername());
params.put("password", messageProperties.getPassword());
RequestEntity<Map<String, Object>> requestEntity = RequestEntity.post(URI.create(messageProperties.getLoginUrl()))
.body(params);
log.debug("登录获取消息服务token请求参数:{}", params);
ResponseEntity<Result<Token>> response = restTemplate.exchange(requestEntity, new ParameterizedTypeReference<Result<Token>>() {});
log.debug("登录获取消息服务token结果:{}", response);
if (response.getStatusCode() == HttpStatus.OK) {
Result<Token> result = response.getBody();
assert result != null;
if (result.getSuccess()) {
Token tk = result.getData();
String messageTokenKey = tk.getToken();
mtk.set(messageTokenKey);
redisson.getBucket(Constants.RedisKeyPrefix.messageToken.getValue()).set(messageTokenKey);
} else {
log.error("登录获取消息服务token发生错误: 错误代码: {}, {}, 错误详情: {}", result.getCode(), result.getMsg(), result.getException());
}
} else {
log.error("登录获取消息服务token发生错误:错误代码 {}, 错误内容: {}", response.getStatusCode(), response.getBody());
}
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
log.error("在获取消息服务用户token时,发生了中断错误", e);
}
RBucket<String> tokenBucket = redisson.getBucket(Constants.RedisKeyPrefix.messageToken.getValue());
long loopTimeout = 3000; // 循环的超时时间
long loopTime = 0;
while (Objects.isNull(mtk.get()) && loopTime <= loopTimeout) {
try {
Thread.sleep(100);
loopTime += 100;
mtk.set(tokenBucket.get());
} catch (InterruptedException e) {
log.error("在循环获取消息服务用户token时,发生了中断错误: " + e.getMessage(), e);
}
}
return Objects.requireNonNull(mtk.get()).toString();
});
for (String topic : message.getTopics()) {
try {
ResponseEntity<Result<Object>> response = restTemplate.exchange(RequestEntity.post(URI.create(messageProperties.getSendUrl() + "/" + topic))
.header(Constants.Headers.AUTH_TOKEN, token)
.body(message), new ParameterizedTypeReference<Result<Object>>() {});
if (response.getStatusCode() != HttpStatus.OK) {
log.error("发送消息到kafka发生错误:错误代码 {}, 错误内容: {}", response.getStatusCode(), response.getBody());
}
if (response.getBody() != null && !response.getBody().getSuccess()) {
log.error("发送消息到kafka发生错误: 错误代码: {}, 错误消息:{}, 错误详情: {}", response.getBody().getCode(), response.getBody().getMsg(), response.getBody().getException());
}
if (response.getBody() != null && (response.getBody().getCode() == Constants.Exceptions.token_expired_error
|| response.getBody().getCode() == Constants.Exceptions.token_login_in_other_device_error)){
redisson.getBucket(Constants.RedisKeyPrefix.messageToken.getValue()).delete();
sendMessageToKafka(message);
}
} catch (RestClientException e) {
log.error("发送消息到kafka发生错误", e);
}
}
}
}

View File

@ -0,0 +1,30 @@
package com.aisino.iles.core.hibernate;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 注解信息描述可以用于建表的时候生成注释
*
* @author huxin
* @since 2020-09-04
*/
@Retention(RUNTIME)
@Target({ElementType.FIELD})
public @interface ColumnComment {
/**
* 字段名称默认就是标记类属性的名称
* @return 字段
*/
String name() default "";
/**
* 注释信息
* @return 注释信息
*/
String value();
}

View File

@ -0,0 +1,19 @@
//package com.aisino.iles.core.hibernate;
//
//
//import org.hibernate.dialect.DmDialect;
//
///**
// * 达梦数据库JPA方言支持注释
// *
// * @author huxin
// * @since 2020-09-08
// */
//
//public class DMDialectPlus extends DmDialect {
// @Override
// public boolean supportsCommentOn() {
// // 适配达梦数据库支持注解oracle模式
// return true;
// }
//}

View File

@ -0,0 +1,78 @@
package com.aisino.iles.core.hibernate;
import com.aisino.iles.common.model.Message;
import com.aisino.iles.common.model.MessageProperties;
import com.aisino.iles.core.model.BaseModel;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 公安网报警信息发送kafka处理业务
* @author hx
* @since 20241029
*/
@Slf4j
@Component
public class DataPackAlarmInfoSendToKafkaProcessor extends AbstractDataPackSendToKafkaProcessor{
private final ObjectMapper mapper;
private final MessageProperties messageProperties;
public DataPackAlarmInfoSendToKafkaProcessor(ObjectMapper mapper, MessageProperties messageProperties) {
this.mapper = mapper;
this.messageProperties = messageProperties;
}
@Override
public List<Message> buildMessage(PackData data) {
List<Message> messages = new ArrayList<>();
Message message = new Message();
try {
message.setValue(mapper.writeValueAsString(data));
message.setTopics(new String[]{messageProperties.getTopic().getAlarmInfo()});
messages.add(message);
} catch (JsonProcessingException e) {
log.error("发送数据到kafka比对报警数据序列化数据包发生错误", e);
}
return messages;
}
public boolean support(PackData data) {
boolean acceptOperation = data.getOperation().equals(DataPackageInterceptor.PackDataOperations.SAVE) || data.getOperation().equals(DataPackageInterceptor.PackDataOperations.MODIFY);
boolean acceptEntityType = false;
if (acceptOperation) {
try {
log.debug("发送kafka解析前数据包信息{}", data);
Object object = mapper.readValue(data.getDataJson(), Class.forName(data.getClzName()));
log.debug("发送kafka数据实体{}", object);
acceptEntityType = Objects.nonNull(object) && object instanceof BaseModel && ((BaseModel) object).isNeedAlarm();
} catch (JsonProcessingException e) {
log.error("json反序列化错误:{}", e.getMessage(), e);
} catch (ClassNotFoundException e) {
log.error("定位类型的时候没有找到这个类型:" + e.getMessage(), e);
}
}
return acceptOperation && acceptEntityType;
}
@Override
public void invoke(List<PackData> data) {
log.debug("发送数据到kafka比对报警");
super.invoke(data);
log.debug("发送数据完成");
}
@Override
public String getName() {
return "sendAlarmInfoToKafka";
}
}

View File

@ -0,0 +1,23 @@
package com.aisino.iles.core.hibernate;
/**
* 数据包处理器用于将数据包根据业务需求进行其他行为比如发送邮件发送短信发送信息等
* @author hx
* @since 20241028
*/
public interface DataPackProcessor<T> {
/**
* 处理数据包
*/
void invoke(T data);
/**
* 是否支持当前数据包进入业务处理流程
* @param data 数据包
* @return true 可以进入业务 false 不可以
*/
boolean support(T data);
String getName();
}

View File

@ -0,0 +1,69 @@
package com.aisino.iles.core.hibernate;
import com.aisino.iles.common.util.StringUtils;
import com.aisino.iles.core.config.DataPackageConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* 数据包发送到打包服务进行打包
* @author hx
* @since 20241028
*/
@Slf4j
@Service
public class DataPackSendToDttPackProcessor implements DataPackProcessor<List<PackData>> {
private final DataPackageConfig dataPackageConfig;
private final RestTemplate restTemplate;
public DataPackSendToDttPackProcessor(DataPackageConfig dataPackageConfig, RestTemplate restTemplate) {
this.dataPackageConfig = dataPackageConfig;
this.restTemplate = restTemplate;
}
@Override
public void invoke(List<PackData> data) {
for (int i = 0; i < dataPackageConfig.getRetryTimes(); i++) {
try {
// rest 数据给接口
HttpEntity<List<PackData>> requestEntity = new HttpEntity<>(data);
ResponseEntity<Void> resp = restTemplate.exchange(dataPackageConfig.getPackagingApi(), HttpMethod.POST, requestEntity, Void.class);
if (resp.getStatusCode() == HttpStatus.OK) {
break;
}
} catch (Exception e) {
log.error("写入保存打包数据发生错误:" + e.getMessage());
log.debug(e.getMessage(), e);
if (i == dataPackageConfig.getRetryTimes() - 1) {
// todo rest接口调用错误超过重试次数之后处理
} else {
try {
Thread.sleep(dataPackageConfig.getRetryInterval());
} catch (InterruptedException interruptedException) {
log.error(interruptedException.getLocalizedMessage(), interruptedException);
}
}
}
}
}
@Override
public boolean support(List<PackData> data) {
// 是否需要打包存在打包标记打包api
return dataPackageConfig.isPackagingFlag() && StringUtils.isNotBlank(dataPackageConfig.getPackagingApi());
}
@Override
public String getName() {
return "sendToDttPack";
}
}

View File

@ -0,0 +1,22 @@
package com.aisino.iles.core.hibernate;
import java.util.List;
import java.util.Objects;
/**
* 数据包单记录处理
* @author hx
* @since 20241028
*/
public interface DataPackSingleRecordProcessor extends DataPackProcessor<List<PackData>> {
void invoke(PackData data);
boolean support(PackData data);
default boolean support(List<PackData> data) {
return data.stream().anyMatch(this::support);
}
default void invoke(List<PackData> data) {
data.stream().filter(this::support).filter(Objects::nonNull).forEach(this::invoke);
}
}

View File

@ -0,0 +1,314 @@
package com.aisino.iles.core.hibernate;
import com.aisino.iles.common.util.StringUtils;
import com.aisino.iles.core.config.DataPackageConfig;
import com.aisino.iles.core.exception.BusinessError;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Interceptor;
import org.hibernate.Transaction;
import org.hibernate.collection.spi.AbstractPersistentCollection;
import org.hibernate.collection.spi.PersistentList;
import org.hibernate.collection.spi.PersistentSet;
import org.hibernate.type.Type;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
/**
* 数据打包拦截器
*
* @author huxin
* @since 2020-08-31
*/
@Component
@Slf4j
public class DataPackageInterceptor implements Interceptor {
/**
* 用于新增修改删除的数据实体
*/
private final ThreadLocal<Map<Serializable, PackData>> saveEntities = new ThreadLocal<>();
/**
* 是否已经提交过了
*/
private final ThreadLocal<Boolean> wasCommitted = new ThreadLocal<>();
private final ObjectMapper mapper;
/**
* 路径匹配工具
*/
private final AntPathMatcher pathMatcher;
private final DataPackageConfig dataPackageConfig;
private final ObjectProvider<DataPackProcessor<List<PackData>>> dataPackProcessorProviders;
public DataPackageInterceptor(DataPackageConfig dataPackageConfig,
@Qualifier("dataPackageObjectMapper") ObjectMapper mapper,
ObjectProvider<DataPackProcessor<List<PackData>>> dataPackProcessors) {
this.mapper = mapper;
this.dataPackageConfig = dataPackageConfig;
this.dataPackProcessorProviders = dataPackProcessors;
pathMatcher = new AntPathMatcher();
}
/**
* 生成打包数据并加入到集合里面
*
* @param entity 实体
* @param operation 操作标志
* @param id 主键
*/
private void processPackData(Object entity, String operation, Serializable id) {
if (saveEntities.get() == null) {
saveEntities.set(new LinkedHashMap<>());
}
String className = entity.getClass().getName();
if (dataPackageConfig.getProcessEntityNames().getInclude().stream().anyMatch(enp -> pathMatcher.matchStart(enp, className))
&& dataPackageConfig.getProcessEntityNames().getExclude().stream().noneMatch(enp -> pathMatcher.matchStart(enp, className))) {
log.debug("配置包含当前实体加入打包列表。");
String packDataId = className + "#" + id;
Optional<PackData> oPackData = Optional.ofNullable(saveEntities.get().get(packDataId))
.map(pd -> {
String oper = PackDataOperations.PRIORITY_LIST.indexOf(operation) < PackDataOperations.PRIORITY_LIST.indexOf(saveEntities.get().get(packDataId).getOperation()) ?
operation : saveEntities.get().get(packDataId).getOperation();
if (operation.equals(PackDataOperations.REMOVE)) {
saveEntities.get().remove(packDataId);
return Optional.<PackData>empty();
}
return Optional.of(new PackData()
.setClzName(className)
.setEntity(entity)
.setOperation(oper)
.setSystemName("SYSTEM")
.setWaring(false));
})
.orElse(Optional.of(new PackData()
.setClzName(className)
.setEntity(entity)
.setOperation(operation)
.setSystemName("SYSTEM")
.setWaring(false)));
oPackData.ifPresent(packData -> {
try {
log.debug("entityJson:{}", mapper.writeValueAsString(packData));
saveEntities.get().put(packDataId, packData);
} catch (JsonProcessingException e) {
log.error("打包写入新增实体JSON数据发生错误" + e.getMessage(), e);
}
});
}
}
/**
* 触发删除
*
* @param entity 实体
* @param id 主键
* @param state 状态也就是属性的值
* @param propertyNames 属性名称
* @param types 类型数组
*/
@Override
public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
log.debug("进入删除");
log.debug("当前线程ID{}", Thread.currentThread().getId());
log.debug("当前实体:{}", entity.getClass().getName());
processPackData(entity, PackDataOperations.REMOVE, id);
}
/**
* 触发修改
*
* @param entity 实体
* @param id 主键
* @param currentState 当前状态
* @param previousState 之前的状态
* @param propertyNames 属性名称
* @param types 类型
* @return 如果在这个事件回调里的修改需要生效持久化 返回true 否则false 默认false
*/
@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
log.debug("进入修改");
log.debug("当前线程ID" + Thread.currentThread().getId());
log.debug("当前实体:" + entity.getClass().getName());
// 先生成打包数据
processPackData(entity, PackDataOperations.MODIFY, id);
return false;
}
/**
* 触发新增
*
* @param entity 实体
* @param id 主键
* @param state 当前状态
* @param propertyNames 属性名称
* @param types 类型
* @return 如果在这个事件回调里的修改需要生效持久化 返回true 否则false 默认false
*/
@Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
log.debug("进入新增");
log.debug("当前线程ID" + Thread.currentThread().getId());
log.debug("当前实体:" + entity.getClass().getName());
// 先生成打包数据
processPackData(entity, PackDataOperations.SAVE, id);
return false;
}
@Override
public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
// log.debug("进入载入实体(查询)");
// log.debug("当前线程ID" + Thread.currentThread().getId());
// log.debug("当前实体:" + entity.getClass().getName());
return false;
}
@Override
public String getEntityName(Object object) {
return object.getClass().getName();
}
/**
* 当集合类型调用remove时候
*
* @param collection 集合
* @param key 主键
*/
@Override
public void onCollectionRemove(Object collection, Serializable key) {
collectionPackage(collection, key);
}
/**
* 当集合类型变量被重庆实例化时候
*
* @param collection 集合
* @param key 主键
*/
@Override
public void onCollectionRecreate(Object collection, Serializable key) {
collectionPackage(collection, key);
}
/**
* 当集合类型变量发生修改的时候
*
* @param collection 集合
* @param key 主键
*/
@Override
public void onCollectionUpdate(Object collection, Serializable key) {
collectionPackage(collection, key);
}
private void collectionPackage(Object collection, Serializable key) {
if (saveEntities.get() == null) {
saveEntities.set(new LinkedHashMap<>());
}
if (PersistentSet.class.isAssignableFrom(collection.getClass()) || PersistentList.class.isAssignableFrom(collection.getClass())) {
processPackData(((AbstractPersistentCollection) collection).getOwner(), PackDataOperations.MODIFY, key);
}
}
/**
* 事务完成前提交了事务在这里标记一提交状态
*
* @param tx jpa事务
*/
@Override
public void beforeTransactionCompletion(Transaction tx) {
// 设置提交标记
wasCommitted.set(Boolean.TRUE);
}
/**
* 事务完成的时候触发写入打包的数据信息
*
* @param tx JPA/HIBERNATE事务
*/
@Override
public void afterTransactionCompletion(Transaction tx) {
try {
if (wasCommitted.get() == null || !wasCommitted.get()) {
return;
}
// 事务完成的时候触发写入打包的数据信息
Optional.ofNullable(saveEntities.get()).filter(l -> !l.isEmpty())
.ifPresent(sel -> {
log.debug("事务完成写入打包数据 ================ ");
log.debug("当前线程ID{}", Thread.currentThread().getId());
//log.debug(sel.toString());
List<PackData> packDataList = saveEntities.get().values().stream()
.peek(pd -> {
try {
pd.setDataJson(mapper.writeValueAsString(pd.getEntity()));
} catch (JsonProcessingException e) {
log.error("打包写入新增实体JSON数据发生错误" + e.getMessage(), e);
}
})
.collect(Collectors.toList());
// 链式调用数据包处理器
List<DataPackProcessor<List<PackData>>> dataPackProcessors = dataPackProcessorProviders.stream()
.filter(dp -> dataPackageConfig.getDataPackProcessors().contains(dp.getName()))
.collect(Collectors.toList());
dataPackProcessors.stream()
.filter(dpp -> dpp.support(packDataList))
.forEach(dpp -> {
try {
dpp.invoke(packDataList);
} catch (Exception e) {
log.error("数据包业务处理发生异常", e);
}
});
});
} finally {
wasCommitted.remove();
saveEntities.remove();
}
}
public static final class PackDataOperations {
public static final String SAVE = "insert";
public static final String MODIFY = "update";
public static final String REMOVE = "delete";
public static final List<String> PRIORITY_LIST = Arrays.asList(SAVE, MODIFY);
}
/**
* 打包实体操作类型(这个区别于{@link PackDataOperations}只用来标记saveEntities里面类型.
* 现在不再根据每一个操作insert等,打包操作轨迹. 而只打包每个事物的每个实体的最后状态,这样比较高效.
*/
public enum SaveEntityType {
save, remove;
public SaveEntityType fromPackDataOperation(String packDataOperation) {
if (StringUtils.isEmpty(packDataOperation)) {
throw new BusinessError("数据包操作类型为空");
}
if (!PackDataOperations.PRIORITY_LIST.contains(packDataOperation)) {
throw new BusinessError("数据包操作类型错误");
}
if (packDataOperation.equals(PackDataOperations.SAVE) || packDataOperation.equals(PackDataOperations.MODIFY)) {
return save;
} else {
return remove;
}
}
}
}

View File

@ -0,0 +1,74 @@
package com.aisino.iles.core.hibernate;
import cn.hutool.core.util.StrUtil;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.MySQL8Dialect;
import org.hibernate.dialect.MySQLDialect;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* mysql8方言扩展
* @author hx
* @since 2023-07-26
*/
public class MysqlDialectPlus extends MySQLDialect {
public MysqlDialectPlus() {
super(DatabaseVersion.make(8,0,34));
}
/**
* 支持数据库提示注解(目前主要还是部分支持不同数据库实现hint的方式
* 是不同的有的使用select /*+ something *\/ 有的使用 from table using something
* 这里为mysql提供select 注释+something的实现)
* @param query 生成的sql
* @param hints 数据库提示注解
* @return 加入数据库提示注解后的sql语句
*/
@Override
public String getQueryHintString(String query, String hints) {
// 在query语句是select语句才使用hints
if(!query.startsWith("select"))
return query;
// hints 里面有字符串模板替换
// 表名模板使用上有一个局限他只能用于表示出现一次的表关联语句如果出现了自连接之类的例如
// from t_jurisdiction j join t_jurisdiction pj on j.parent_juris_id = pj.jurisdiction_id
//<b> 这样的语句他就不能很好的使用{t_jurisdiction}获取到准确的别名</b>
String newHints = StrUtil.format(hints,findHintsTemplateMap(query, hints));
// 把hints插入到query的select 关键字后面使用固定格式/*+ */ 包裹住hints
StringBuilder newSql = new StringBuilder();
newSql.append(query, 0, "select".length());
newSql.append(" /*+ ");
newSql.append(newHints);
newSql.append(" */ ");
newSql.append(query, "select".length(), query.length());
return newSql.toString();
}
private Map<String,String> findHintsTemplateMap(String sql, String hints) {
// 获取hints中的{}中间的内容返回列表
List<String> tempVars = new ArrayList<>();
Pattern pattern = Pattern.compile("\\{(.*?)}");
Matcher matcher = pattern.matcher(hints);
while(matcher.find()){
tempVars.add(matcher.group(1));
}
return tempVars.stream().reduce(new HashMap<>(), (a, b) -> {
Pattern aliasPattern = Pattern.compile("\\b"+b +"\\b" + "\\s+(.*?)(,|$|\\s+)+");
Matcher aliasMatcher = aliasPattern.matcher(sql);
while (aliasMatcher.find()) {
a.put(b, aliasMatcher.group(1));
}
return a;
}, (a, b) -> a);
}
}

View File

@ -0,0 +1,37 @@
package com.aisino.iles.core.hibernate;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class PackData {
/**
* 全类名
*/
private String clzName;
/**
* 同步的业务数据
*/
private String dataJson;
/**
* 系统名称 例如 HOTEL 酒店
*/
private String systemName;
/**
* 数据操作 insert, update, delete
*/
private String operation;
/**
* 是否为预警数据
*/
private boolean isWaring;
/** 实体对象 不加入序列化和反序列化 */
@JsonIgnore
private Object entity;
}

View File

@ -0,0 +1,21 @@
package com.aisino.iles.core.hibernate;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.dialect.PostgreSQLDialect;
/**
* PostgreSQL方言扩展(beiyong)
* 添加对json_value函数的支持统一JSON函数调用
*
* @author hx
* @since 2025-08-08
*/
public class PostgreSQLDialectPlus extends PostgreSQLDialect {
@Override
public void initializeFunctionRegistry(FunctionContributions functionContributions) {
super.initializeFunctionRegistry(functionContributions);
}
}

View File

@ -0,0 +1,237 @@
package com.aisino.iles.core.hibernate;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.support.DomainDataStorageAccess;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.redisson.api.RFuture;
import org.redisson.api.RLocalCachedMap;
import org.redisson.connection.ConnectionManager;
import org.redisson.hibernate.RedissonRegionFactory;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Slf4j
public class RedissonLocalStorage implements DomainDataStorageAccess {
private final RLocalCachedMap<Object, Object> mapCache;
private final ConnectionManager connectionManager;
@Getter
int ttl;
@Getter
int maxIdle;
@Getter
int size;
boolean fallback;
volatile boolean fallbackMode;
public RedissonLocalStorage(RLocalCachedMap<Object, Object> mapCache,
ConnectionManager connectionManager,
Map<String, Object> properties,
String defaultKey) {
if (mapCache == null || connectionManager == null) {
throw new IllegalArgumentException("mapCache和connectionManager不能为空");
}
this.mapCache = mapCache;
this.connectionManager = connectionManager;
String maxEntries = getProperty(properties, mapCache.getName(), defaultKey, RedissonRegionFactory.MAX_ENTRIES_SUFFIX);
if (maxEntries != null) {
size = Integer.parseInt(maxEntries);
}
String timeToLive = getProperty(properties, mapCache.getName(), defaultKey, RedissonRegionFactory.TTL_SUFFIX);
if (timeToLive != null) {
ttl = Integer.parseInt(timeToLive);
}
String maxIdleTime = getProperty(properties, mapCache.getName(), defaultKey, RedissonRegionFactory.MAX_IDLE_SUFFIX);
if (maxIdleTime != null) {
maxIdle = Integer.parseInt(maxIdleTime);
}
String fallbackValue = (String) properties.getOrDefault(RedissonRegionFactory.FALLBACK, "false");
fallback = Boolean.parseBoolean(fallbackValue);
}
private String getProperty(Map<String, Object> properties, String name, String defaultKey, String suffix) {
String maxEntries = (String) properties.get(RedissonRegionFactory.CONFIG_PREFIX + name + suffix);
if (maxEntries != null) {
return maxEntries;
}
String defValue = (String) properties.get(RedissonRegionFactory.CONFIG_PREFIX + defaultKey + suffix);
if (defValue != null) {
return defValue;
}
return null;
}
private void ping() {
fallbackMode = true;
connectionManager.getServiceManager().newTimeout(t -> {
if (!fallbackMode) {
return;
}
RFuture<Boolean> future = mapCache.isExistsAsync();
future.onComplete((r, ex) -> {
if (ex == null) {
fallbackMode = false;
log.info("Redis连接恢复正常退出降级模式");
} else {
log.warn("Redis连接检查失败: {}", ex.getMessage());
ping();
}
});
}, 5, TimeUnit.SECONDS); // 调整为5秒重试间隔
}
@Override
public Object getFromCache(Object key, SharedSessionContractImplementor session) {
if (fallbackMode) {
return null;
}
try {
Object value = mapCache.get(key);
if (log.isDebugEnabled()) {
log.debug("读取缓存[{}]键[{}][{}]", mapCache.getName(), key, Objects.isNull(value) ? "未命中": "命中");
}
return value;
} catch (Exception e) {
if (fallback) {
ping(); // 在降级模式下调用 ping
return null; // 返回默认值
}
throw new CacheException("缓存读取失败", e);
}
}
@Override
public void putIntoCache(Object key, Object value, SharedSessionContractImplementor session) {
if (fallbackMode) {
return;
}
try {
if (log.isDebugEnabled()) {
log.debug("写入缓存[{}]键[{}], TTL:{}ms, maxIdle:{}ms",
mapCache.getName(), key, ttl, maxIdle);
}
RFuture<Boolean> future = mapCache.fastPutAsync(key, value);
future.whenComplete((res, ex) -> {
if (ex != null) {
log.error("异步写入缓存[{}]键[{}]失败: {}", mapCache.getName(), key, ex.getMessage());
} else if (log.isDebugEnabled()) {
log.debug("异步写入缓存[{}]键[{}]成功", mapCache.getName(), key);
}
});
} catch (Exception e) {
if (fallback) {
ping(); // 在降级模式下调用 ping
return; // 返回默认值
}
throw new CacheException("缓存写入失败", e);
}
}
@Override
public boolean contains(Object key) {
if (fallbackMode) {
return false;
}
try {
boolean exists = mapCache.containsKey(key);
if (log.isDebugEnabled()) {
log.debug("检查缓存[{}]键[{}]存在: {}", mapCache.getName(), key, exists);
}
return exists;
} catch (Exception e) {
if (fallback) {
ping(); // 在降级模式下调用 ping
return false; // 返回默认值
}
throw new CacheException("检查缓存失败", e);
}
}
@Override
public void evictData() {
if (fallbackMode) {
return;
}
try {
mapCache.clear();
if (log.isDebugEnabled()) {
log.debug("清空缓存[{}]", mapCache.getName());
}
} catch (Exception e) {
if (fallback) {
ping(); // 在降级模式下调用 ping
return; // 返回默认值
}
throw new CacheException("清空缓存失败", e);
}
}
@Override
public void evictData(Object key) {
if (fallbackMode || key == null) {
if (log.isDebugEnabled()) {
log.debug("拦截无效缓存移除请求[状态:{}, 键类型:{}]",
fallbackMode ? "降级模式" : "运行中",
key != null ? key.getClass().getSimpleName() : "null");
}
return;
}
try {
// 异步移除缓存并处理结果
RFuture<Long> future = mapCache.fastRemoveAsync(key);
future.whenComplete((result, ex) -> {
if (ex != null) {
log.error("缓存移除失败[{}] - 键[{}]: {}",
mapCache.getName(), key, ex.getMessage());
} else if (log.isDebugEnabled()) {
log.debug("缓存移除[{}] - 键[{}]: {}",
mapCache.getName(), key, result > 0 ? "成功" : "未同步");
}
});
} catch (Exception e) {
if (fallback) {
ping(); // 在降级模式下调用 ping
return; // 返回默认值
}
throw new CacheException("缓存清除失败", e);
}
}
@Override
public void release() {
try {
if (log.isDebugEnabled()) {
log.debug("释放缓存[{}]资源", mapCache.getName());
}
// 销毁缓存实例同步清除本地异步清除远程
mapCache.destroy();
} catch (Exception e) {
if (fallback) {
ping(); // 在降级模式下调用 ping
return;
}
log.error("释放缓存[{}]资源失败: {}", mapCache.getName(), e.getMessage());
throw new CacheException("缓存资源释放失败", e);
}
}
public int getCacheSize() {
return mapCache.size();
}
public long getCacheTtl() {
return mapCache.remainTimeToLive();
}
}

View File

@ -0,0 +1,190 @@
package com.aisino.iles.core.hibernate;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext;
import org.hibernate.cache.cfg.spi.DomainDataRegionConfig;
import org.hibernate.cache.spi.CacheKeysFactory;
import org.hibernate.cache.spi.support.DomainDataStorageAccess;
import org.hibernate.cache.spi.support.RegionNameQualifier;
import org.hibernate.cache.spi.support.StorageAccess;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.redisson.Redisson;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.LongCodec;
import org.redisson.hibernate.RedissonCacheKeysFactory;
import org.redisson.hibernate.RedissonRegionFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 支持从spring管理的redisson实例注入
*
* @author hx
*/
@Slf4j
public class RedissonRegionFactoryPlus extends RedissonRegionFactory {
private final RedissonClient redisson;
private CacheKeysFactory cacheKeysFactory;
private final Map<String, RedissonLocalStorage> regionMap = new ConcurrentHashMap<>();
public RedissonRegionFactoryPlus(RedissonClient redisson) {
this.redisson = redisson;
}
@Override
@SuppressWarnings({"unchecked"})
protected void prepareForUse(SessionFactoryOptions settings, Map properties) throws CacheException {
String fallbackValue = (String) properties.getOrDefault(FALLBACK, "false");
fallback = Boolean.parseBoolean(fallbackValue);
StrategySelector selector = settings.getServiceRegistry().getService(StrategySelector.class);
cacheKeysFactory = selector.resolveDefaultableStrategy(CacheKeysFactory.class,
properties.get(Environment.CACHE_KEYS_FACTORY), new RedissonCacheKeysFactory(redisson.getConfig().getCodec()));
}
@Override
protected CacheKeysFactory getImplicitCacheKeysFactory() {
return cacheKeysFactory;
}
@Override
protected void releaseFromUse() {
regionMap.clear();
this.redisson.shutdown();
}
public Map<String, RedissonLocalStorage> getAllCacheRegions() {
return Collections.unmodifiableMap(regionMap);
}
private String qualifyName(String name) {
return RegionNameQualifier.INSTANCE.qualify(name, getOptions());
}
@Override
protected DomainDataStorageAccess createDomainDataStorageAccess(DomainDataRegionConfig regionConfig, DomainDataRegionBuildingContext buildingContext) {
String defaultKey;
if (!regionConfig.getCollectionCaching().isEmpty()) {
defaultKey = COLLECTION_DEF;
} else if (!regionConfig.getEntityCaching().isEmpty()) {
defaultKey = ENTITY_DEF;
} else if (!regionConfig.getNaturalIdCaching().isEmpty()) {
defaultKey = NATURAL_ID_DEF;
} else {
throw new IllegalArgumentException("Unable to determine entity cache type!");
}
RLocalCachedMap<Object, Object> mapCache = getLocalCachedMap(qualifyName(regionConfig.getRegionName()), buildingContext.getSessionFactory().getProperties(), defaultKey);
RedissonLocalStorage storage = new RedissonLocalStorage(mapCache, ((Redisson) redisson).getConnectionManager(), buildingContext.getSessionFactory().getProperties(), defaultKey);
regionMap.put(regionConfig.getRegionName(), storage);
return storage;
}
@Override
protected StorageAccess createQueryResultsRegionStorageAccess(String regionName, SessionFactoryImplementor sessionFactory) {
// 使用本地缓存映射优化查询结果存储
RLocalCachedMap<Object, Object> mapCache = getLocalCachedMap(
qualifyName(regionName),
sessionFactory.getProperties(),
QUERY_DEF
);
RedissonLocalStorage storage = new RedissonLocalStorage(
mapCache,
((Redisson) redisson).getConnectionManager(),
sessionFactory.getProperties(),
QUERY_DEF
);
regionMap.put(regionName, storage);
return storage;
}
@Override
protected StorageAccess createTimestampsRegionStorageAccess(String regionName, SessionFactoryImplementor sessionFactory) {
// 时间戳区域改用本地缓存存储
RLocalCachedMap<Object, Object> mapCache = getLocalCachedMap(
qualifyName(regionName),
sessionFactory.getProperties(),
TIMESTAMPS_DEF
);
RedissonLocalStorage storage = new RedissonLocalStorage(
mapCache,
((Redisson) redisson).getConnectionManager(),
sessionFactory.getProperties(),
TIMESTAMPS_DEF
);
regionMap.put(regionName, storage);
return storage;
}
/**
* 获取本地缓存映射配置
* @param regionName 缓存区域名称
* @param properties 会话工厂配置属性
* @param defaultKey 默认配置键名
* @return 配置好的本地缓存映射实例
*/
private String getPropertyValue(String regionName, String defaultKey, String propertySuffix, Map<String, Object> properties) {
String value = (String) properties.get(RedissonRegionFactory.CONFIG_PREFIX + regionName + propertySuffix);
if (value == null) {
value = (String) properties.get(RedissonRegionFactory.CONFIG_PREFIX + defaultKey + propertySuffix);
}
return value;
}
public RLocalCachedMap<Object, Object> getLocalCachedMap(String name, Map<String, Object> properties, String defaultKey) {
String maxEntries = getPropertyValue(name, defaultKey, RedissonRegionFactory.MAX_ENTRIES_SUFFIX, properties);
int size = (maxEntries != null) ? Integer.parseInt(maxEntries) : 0;
String timeToLive = getPropertyValue(name, defaultKey, RedissonRegionFactory.TTL_SUFFIX, properties);
long ttl = (timeToLive != null) ? Long.parseLong(timeToLive) : 0;
String maxIdleTime = getPropertyValue(name, defaultKey, RedissonRegionFactory.MAX_IDLE_SUFFIX, properties);
long maxIdle = (maxIdleTime != null) ? Long.parseLong(maxIdleTime) : 0;
LocalCachedMapOptions<Object, Object> localCachedMapOptions = LocalCachedMapOptions.defaults()
.evictionPolicy(LocalCachedMapOptions.EvictionPolicy.LRU)
.timeToLive(ttl)
.cacheSize(size)
.reconnectionStrategy(LocalCachedMapOptions.ReconnectionStrategy.CLEAR)
.maxIdle(maxIdle)
.syncStrategy(LocalCachedMapOptions.SyncStrategy.UPDATE);
return redisson.getLocalCachedMap(name, localCachedMapOptions);
}
@Override
public long nextTimestamp() {
long time = System.currentTimeMillis() << 12;
try {
return redisson.getScript(LongCodec.INSTANCE).eval(RScript.Mode.READ_WRITE,
"local currentTime = redis.call('get', KEYS[1]);"
+ "if currentTime == false then "
+ "redis.call('set', KEYS[1], ARGV[1]); "
+ "return ARGV[1]; "
+ "end;"
+ "local nextValue = math.max(tonumber(ARGV[1]), tonumber(currentTime) + 1); "
+ "redis.call('set', KEYS[1], nextValue); "
+ "return nextValue;",
RScript.ReturnType.INTEGER, Collections.singletonList("redisson-hibernate-timestamp"), time);
} catch (Exception e) {
if (fallback) {
return super.nextTimestamp();
}
throw e;
}
}
}

View File

@ -0,0 +1,28 @@
package com.aisino.iles.core.identifiergenerator;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import jakarta.persistence.Column;
import jakarta.persistence.Id;
import java.util.Arrays;
import java.util.Optional;
/**
* ID生成器工具帮助类
*/
public interface GeneratorPlusHelper {
Log log = LogFactory.getLog(GeneratorPlusHelper.class);
String idColumnName();
default Optional<Object> getCurrentIdValue(Object object) {
return Arrays.stream(ReflectUtil.getFields(object.getClass()))
.filter(v-> v.getAnnotation(Id.class)!=null)
.filter(v-> Optional.ofNullable(v.getAnnotation(Column.class))
.filter(c -> StrUtil.isNotBlank(c.name()))
.map(c -> StrUtil.equalsAnyIgnoreCase(idColumnName(),c.name()))
.orElseGet(() -> StrUtil.toCamelCase(idColumnName()).equals(v.getName()))).findFirst()
.map(v->ReflectUtil.getFieldValue(object,v));
}
}

View File

@ -0,0 +1,44 @@
package com.aisino.iles.core.identifiergenerator;
import com.github.f4b6a3.ulid.Ulid;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type;
import java.io.Serializable;
import java.util.Properties;
/**
* ulid生成器
*
* @author suen.sun
* @author hx
*/
public class ULIDGenerator implements GeneratorPlusHelper, IdentifierGenerator, Configurable {
/**
* 主键字段
*/
private String idColumnName;
@Override
public Serializable generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException {
return getCurrentIdValue(o)
.map(v -> (Serializable) v)
.orElseGet(() -> Ulid.fast().toLowerCase());
}
@Override
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
this.idColumnName = params.getProperty(PersistentIdentifierGenerator.PK);
}
@Override
public String idColumnName() {
return this.idColumnName;
}
}

View File

@ -0,0 +1,47 @@
package com.aisino.iles.core.identifiergenerator;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.id.UUIDGenerator;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type;
import java.io.Serializable;
import java.util.Optional;
import java.util.Properties;
/**
* uuid风格ID生成器
* <p>
* 提供当主键属性为空的时候才生成ID数据主键属性不为空的时候使用原来的值保存数据
*
* @author huxin
* @since 2020-07-02
*/
@Slf4j
public class UUIDGeneratorPlus extends UUIDGenerator implements GeneratorPlusHelper {
public static final String ALWAYS_GENERATE = "always_generate";
private String idColumnName;
private boolean always = false;
@Override
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
super.configure(type, params, serviceRegistry);
this.idColumnName = params.getProperty(PersistentIdentifierGenerator.PK);
this.always = Optional.ofNullable(params.getProperty(ALWAYS_GENERATE)).map(Boolean::parseBoolean).orElse(false);
}
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
if(always)
return (Serializable) super.generate(session, object);
return getCurrentIdValue(object).map(r -> ((Serializable) r)).orElse((Serializable) super.generate(session, object));
}
@Override
public String idColumnName() {
return idColumnName;
}
}

View File

@ -0,0 +1,62 @@
package com.aisino.iles.core.interceptor;
import com.aisino.iles.common.util.Constants;
import com.smartlx.sso.client.model.AccessToken;
import com.smartlx.sso.client.model.RemoteUserInfo;
import com.smartlx.sso.client.service.SsoClientService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* Access Token 拦截器
* 拦截请求中的access_token通过SsoClientService.check验证
* 并从RemoteUserInfo中提取yhwybs设置到request属性current_user_id中
*/
@Slf4j
@RequiredArgsConstructor
public class AccessTokenInterceptor implements HandlerInterceptor {
private final SsoClientService ssoClientService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头中获取Authorization
String authorization = request.getHeader("Authorization");
if (StringUtils.hasText(authorization) && authorization.startsWith("Bearer ")) {
try {
// 提取access_token
String token = authorization.substring(7);
// 创建AccessToken对象
AccessToken accessToken = new AccessToken();
accessToken.setAccess_token(token);
// 获取用户信息
RemoteUserInfo userInfo = ssoClientService.getRemoteUserInfo(accessToken);
if (userInfo != null && StringUtils.hasText(userInfo.getYhwybs())) {
// 将yhwybs设置到request属性current_user_id中
request.setAttribute(Constants.CURRENT_USER_ID, userInfo.getYhwybs());
log.debug("设置current_user_id: {}", userInfo.getYhwybs());
return true;
}
log.warn("无效的access_token或用户信息不完整");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
} catch (Exception e) {
log.error("验证access_token时发生错误", e);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
// 如果没有Authorization头或格式不正确返回未授权状态
log.warn("请求中缺少有效的Authorization头");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}

View File

@ -0,0 +1,47 @@
package com.aisino.iles.core.interceptor;
import com.aisino.iles.common.iface.Logger;
import lombok.Setter;
import org.springframework.http.HttpHeaders;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Optional;
/**
* 跨域请求拦截器
*/
@Setter
public class CorsInterceptor implements HandlerInterceptor , Logger {
private String accessControlAllowOrigin;
private String accessControlAllowHeaders;
private String accessControlAllowCredentials;
private String accessControlMaxAge;
private String accessControlAllowMethods;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger().debug("----------------------------------");
logger().debug(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN + ": " + accessControlAllowOrigin);
logger().debug(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS + ": " + accessControlAllowCredentials);
logger().debug(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS + ": " + accessControlAllowHeaders);
logger().debug(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS + ": " + accessControlAllowMethods);
logger().debug(HttpHeaders.ACCESS_CONTROL_MAX_AGE + ": " + accessControlMaxAge);
if("*".equals(accessControlAllowOrigin)){
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader(HttpHeaders.ORIGIN));
} else {
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, Optional.ofNullable(accessControlAllowOrigin).flatMap(allowOrigins -> Arrays.stream(allowOrigins.split(","))
.filter(origin -> origin.equals(request.getHeader(HttpHeaders.ORIGIN))).findFirst())
.orElse(""));
}
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, accessControlAllowCredentials);
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, accessControlAllowHeaders);
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, accessControlAllowMethods);
response.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, accessControlMaxAge);
return true;
}
}

View File

@ -0,0 +1,396 @@
package com.aisino.iles.core.interceptor;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.SmUtil;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.common.model.PageResult;
import com.aisino.iles.common.model.Result;
import com.aisino.iles.common.util.StringUtils;
import com.aisino.iles.core.controller.GlobalExceptionController;
import com.aisino.iles.core.exception.BusinessError;
import com.aisino.iles.core.service.DynamicEncryptService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.util.StreamUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 通用加密解密请求响应过滤器
* (使用OncePerRequestFilter 是确保每个请求只触发一次)
* @author hx
* @since 2023-05-29
*/
@Slf4j
public class EncryptRequestResponseFilter extends OncePerRequestFilter {
private final DynamicEncryptService dynamicEncryptService;
private final GlobalExceptionController globalExceptionController;
private final ObjectMapper objectMapper;
public EncryptRequestResponseFilter(DynamicEncryptService dynamicEncryptService,
GlobalExceptionController globalExceptionController,
ObjectMapper objectMapper) {
this.dynamicEncryptService = dynamicEncryptService;
this.globalExceptionController = globalExceptionController;
this.objectMapper = objectMapper;
}
@Override
protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws IOException {
String encryptSignHeader = request.getHeader(Constants.Headers.ENCRYPT_SIGN);
HttpServletRequest req;
HttpServletResponse res;
try {
req = Optional.ofNullable(encryptSignHeader)
.filter(esh -> !request.getRequestURI().contains(Constants.NO_AUTH_API_PREFIX + Constants.ApiIndustryCategoryPrefixes.SYSTEM + "/dynamic-encrypt-key"))
.map(dynamicEncryptService::getDynamicEncryptKey)
.map(encryptKey -> (HttpServletRequest) new EncryptRequestWrapper(request, encryptKey))
.orElse(request);
res = Optional.ofNullable(encryptSignHeader)
.filter(esh -> !request.getRequestURI().contains(Constants.NO_AUTH_API_PREFIX + Constants.ApiIndustryCategoryPrefixes.SYSTEM + "/dynamic-encrypt-key"))
.map(dynamicEncryptService::getDynamicEncryptKey)
.map(encryptKey -> (HttpServletResponse) new EncryptResponseWrapper(response, encryptKey, objectMapper))
.orElse(response);
filterChain.doFilter(req, res);
} catch (BusinessError e) {
Result<Object> result = globalExceptionController.businessErrorHandler(e);
responseErrorMsg(response, result);
} catch (Exception e) {
Result<Object> result = globalExceptionController.serverErrorHandler(e);
responseErrorMsg(response, result);
}
}
private void responseErrorMsg(HttpServletResponse response, Object result) throws IOException {
response.setStatus(200);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
try (PrintWriter out = response.getWriter()) {
out.write(objectMapper.writeValueAsString(result));
}
}
/**
* 加密响应包装
*
* @author hx
* @since 2023-05-29
*/
public static class EncryptResponseWrapper extends HttpServletResponseWrapper {
private final HttpServletResponse response;
private final String encryptKey;
private final ObjectMapper objectMapper;
/**
* Constructs a response adaptor wrapping the given response.
*
* @param response the {@link HttpServletResponse} to be wrapped.
* @param encryptKey 加密钥密
* @param objectMapper jackson对象序列化反序列化支持
* @throws IllegalArgumentException if the response is null
*/
public EncryptResponseWrapper(HttpServletResponse response, String encryptKey, ObjectMapper objectMapper) {
super(response);
this.response = response;
this.encryptKey = encryptKey;
this.objectMapper = objectMapper;
}
@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(getOutputStream());
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (Optional.ofNullable(response.getContentType()).map(ct -> ct.contains(MediaType.APPLICATION_JSON_VALUE)).orElse(false) ) {
response.setHeader(Constants.Headers.ENCRYPTED_DATA, "true");
return new BodyServletOutputStream(new ByteArrayOutputStream(), response, encryptKey, objectMapper);
}
return super.getOutputStream();
}
/**
* 加密响应输出流包装(只有用自己的输出流包装才可以支持传入字节输出流方面对响应数据做修改
*
* @author hx
* @since 2023-05-29
*/
private static class BodyServletOutputStream extends ServletOutputStream {
private final ByteArrayOutputStream outputStream;
private final HttpServletResponse response;
private final String encryptKey;
private final ObjectMapper objectMapper;
private BodyServletOutputStream(ByteArrayOutputStream outputStream,
HttpServletResponse response,
String encryptKey,
ObjectMapper objectMapper) {
this.outputStream = outputStream;
this.response = response;
this.encryptKey = encryptKey;
this.objectMapper = objectMapper;
}
/**
* 是否准备好(为false不会开始write)
* @return 默认返回true
*/
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) {
outputStream.write(b);
}
@Override
public void write(@NonNull byte[] b, int off, int len) {
outputStream.write(b, off, len);
}
/**
* 刷新在bytearray准备好之后刷新到浏览器
* 这个版本的加密响应输出只加密Result响应体的data数据部分
* @throws IOException io异常
*/
@Override
public void flush() throws IOException {
if (!response.isCommitted()) {
Result<Object> restResult = objectMapper.readValue(getBody(), PageResult.class);
if(Collection.class.isAssignableFrom(restResult.getData().getClass())) {
Collection<Object> data = (Collection<Object>) restResult.getData();
List<Object> encryptedData = data.stream().map(o -> {
try {
return (Object)SmUtil.sm4(HexUtil.decodeHex(encryptKey)).encryptHex(objectMapper.writeValueAsBytes(o));
} catch (JsonProcessingException e) {
throw new BusinessError("加密响应中数据列表序列化错误", e);
}
}).collect(Collectors.toList());
restResult.setData(encryptedData);
} else {
try {
restResult.setData(SmUtil.sm4(HexUtil.decodeHex(encryptKey)).encryptHex(objectMapper.writeValueAsBytes(restResult.getData())));
} catch (JsonProcessingException e) {
throw new BusinessError("加密响应中数据序列化错误", e);
}
}
response.getOutputStream().write(objectMapper.writeValueAsBytes(restResult));
response.getOutputStream().flush();
}
}
@Override
public void write(@NonNull byte[] b) throws IOException {
super.write(b);
}
/**
* 获取响应体数据
* @return 响应体数据
*/
public byte[] getBody() {
return outputStream.toByteArray();
}
}
}
/**
* 加密请求包装
*
* @author hx
* @since 2023-05-29
*/
public static class EncryptRequestWrapper extends HttpServletRequestWrapper {
private final String encryptKey;
private final HttpServletRequest request;
/**
* Constructs a request object wrapping the given request.
*
* @param request the {@link HttpServletRequest} to be wrapped.
* @param encryptKey 加密密钥
* @throws IllegalArgumentException if the request is null
*/
public EncryptRequestWrapper(HttpServletRequest request, String encryptKey) {
super(request);
this.request = request;
this.encryptKey = encryptKey;
}
/**
* 加密请求输入流包装
* @return 服务输入量
* @throws IOException io异常
*/
@Override
public ServletInputStream getInputStream() throws IOException {
byte[] body;
if (this.request.getHeader(HttpHeaders.CONTENT_TYPE).contains(MediaType.APPLICATION_JSON_VALUE)) {
String bodyStr = StreamUtils.copyToString(this.request.getInputStream(), StandardCharsets.UTF_8);
try {
body = SmUtil.sm4(HexUtil.decodeHex(encryptKey)).decrypt(bodyStr);
} catch (CryptoException e) {
log.debug("error encryptKey is {}", encryptKey);
throw new BusinessError("通用加密解密解密失败", Constants.Exceptions.data_encrypt_decrypt_error, e);
}
return new BodyServletInputStream(new ByteArrayInputStream(body));
}
return super.getInputStream();
}
/**
* 获取参数,获取参数的加密串后使用sm4解密
* @param name a <code>String</code> 参数名称
* @return 解密后的参数值
*/
@Override
public String getParameter(String name) {
return Optional.ofNullable(super.getParameter(name))
.filter(StringUtils::isNotEmpty)
.map(value -> {
try {
return SmUtil.sm4(HexUtil.decodeHex(encryptKey)).decryptStr(value);
} catch (CryptoException e) {
throw new BusinessError("通用加密解密解密失败", Constants.Exceptions.data_encrypt_decrypt_error, e);
}
})
.orElse(null);
}
/**
* 获取参数(数组多个值),获取参数的加密串后使用sm4解密取
* @param name a <code>String</code> 参数名称
* @return 解密后的参数值数组
*/
@Override
public String[] getParameterValues(String name) {
return Optional.ofNullable(super.getParameterValues(name))
.map(values -> Arrays.stream(values)
.filter(StringUtils::isNotEmpty)
.map(value -> {
try {
return SmUtil.sm4(HexUtil.decodeHex(encryptKey)).decryptStr(value);
} catch (CryptoException e) {
throw new BusinessError("通用加密解密解密失败", Constants.Exceptions.data_encrypt_decrypt_error, e);
}
}).toArray(String[]::new)).orElse(null);
}
/**
* 加密请求输入流包装 (只有用自己的输入流包装才可以支持从外部传入字节输入流方便我们修改数据)
*
* @author hx
* @since 2023-05-29
*/
private static class BodyServletInputStream extends ServletInputStream {
private final ByteArrayInputStream bodyInputStream;
private BodyServletInputStream(ByteArrayInputStream bodyInputStream) {
this.bodyInputStream = bodyInputStream;
}
/**
* 是否完成如果这里返回true那么他就不会在被读取了
* @return 默认返回false
*/
@Override
public boolean isFinished() {
return false;
}
/**
* 是否准备好
* @return 默认返回true
*/
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() {
return bodyInputStream.read();
}
@Override
public int read(@NonNull byte[] b) throws IOException {
return bodyInputStream.read(b);
}
@Override
public int read(@NonNull byte[] b, int off, int len) {
return bodyInputStream.read(b, off, len);
}
@Override
public long skip(long n) {
return bodyInputStream.skip(n);
}
@Override
public int available() {
return bodyInputStream.available();
}
@Override
public void close() throws IOException {
bodyInputStream.close();
}
@Override
public synchronized void mark(int readlimit) {
bodyInputStream.mark(readlimit);
}
@Override
public synchronized void reset() {
bodyInputStream.reset();
}
@Override
public boolean markSupported() {
return bodyInputStream.markSupported();
}
}
}
}

View File

@ -0,0 +1,108 @@
package com.aisino.iles.core.interceptor;
import cn.hutool.core.util.StrUtil;
import com.aisino.iles.common.model.Result;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.exception.BusinessError;
import com.aisino.iles.core.exception.TokenError;
import com.aisino.iles.core.model.Token;
import com.aisino.iles.core.model.enums.UserStatus;
import com.aisino.iles.core.repository.ResourceRepo;
import com.aisino.iles.core.repository.UserRepo;
import com.aisino.iles.core.util.PermissionUtils;
import com.aisino.iles.core.util.TokenUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Objects;
import static com.aisino.iles.common.util.Constants.Headers.AUTH_TOKEN_MOBILE;
/**
* 令牌验证拦截器
*
* @author huxin
* @since 2020-08-21
*/
@Slf4j
public class JsonTokenValidatorInterceptor implements HandlerInterceptor {
private final ResourceRepo resourceRepo;
private final UserRepo userRepo;
public JsonTokenValidatorInterceptor(ResourceRepo resourceRepo,
UserRepo userRepo) {
this.resourceRepo = resourceRepo;
this.userRepo = userRepo;
}
@Override
public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object o) throws Exception {
boolean tokenValidateResult;
String token = request.getHeader(Constants.Headers.AUTH_TOKEN);
String mToken = request.getHeader(AUTH_TOKEN_MOBILE);
if (request.getMethod().equalsIgnoreCase("options")) {
return true;
}
try {
if (StrUtil.isBlank(token)) {
throw new TokenError("令牌为空,请登录获取,或者把已有的令牌加到请求头参数里");
}
tokenValidateResult = TokenUtil.validateToken(token);
if (!tokenValidateResult) {
throw new TokenError(Constants.Exceptions.token_expired_error, "令牌不正确,可能已经过期了,尝试重新登录");
}
// 需要判断一下用户信息
Token tokenEntity = TokenUtil.parseToken(token);
assert tokenEntity != null;
userRepo.findById(tokenEntity.getUid())
.map(u -> {
if (u.getStatus() == UserStatus.deleted)
throw new TokenError("该用户已经被删除,令牌验证失败");
else if (u.getStatus() == UserStatus.lock)
throw new TokenError("该用户是锁定状态, 令牌验证失败");
else if (u.getStatus() == UserStatus.unnormal)
throw new TokenError("该用户处于某种异常状态, 令牌验证失败");
return u;
})
.orElseThrow(() -> new TokenError("令牌验证失败该uid的用户不存在"));
//用户所使用的资源操作权限判断
tokenValidateResult = PermissionUtils.checkHasResourcePermission(token, request.getRequestURI(), request.getMethod(), resourceRepo);
if (!tokenValidateResult) {
throw new TokenError(Constants.Exceptions.token_no_resource_error, "该令牌不具有该资源的操作权限");
}
} catch (BusinessError e) {
tokenValidateResult = false;
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Result<Object> result = new Result<>();
result.setMsg(e.getMessage());
if (log.isDebugEnabled()) {
result.setException(e);
}
result.setSuccess(false);
result.setCode(e.getCode());
ServletOutputStream out = response.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
JsonGenerator jsonGenerator = mapper.getFactory().createGenerator(out);
jsonGenerator.writeObject(result);
out.close();
}
// 向通过认证的请求的属性添加当前用户的id信息
if (tokenValidateResult)
request.setAttribute(Constants.CURRENT_USER_ID, Objects.requireNonNull(TokenUtil.parseToken(token)).getUid());
return tokenValidateResult;
}
}

View File

@ -0,0 +1,38 @@
package com.aisino.iles.core.interceptor;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.Jurisdiction;
import com.aisino.iles.core.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import java.time.Duration;
/**
* 机构缓存监听当业务程序里对机构信息数据修改删除新增调整那么就会触发机构缓存的监听对缓存的机构信息进行删除
* @author hx
* @since 2023-06-12
*/
@Slf4j
public class JurisdictionListener {
@PostUpdate
@PostPersist
@PostRemove
public void save(Jurisdiction jurisdiction) {
log.info("存在改动机构信息删除机构缓存");
removeAllCache();
}
/**
* 删除全部缓存的机构信息
*/
public void removeAllCache() {
if (RedisUtil.hasKey(Constants.CacheKeys.Jurisdiction.all)) {
RedisUtil.expire(Constants.CacheKeys.Jurisdiction.all, Duration.ZERO);
}
}
}

View File

@ -0,0 +1,32 @@
package com.aisino.iles.core.json.mixins;
import com.aisino.iles.core.model.Function;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.io.IOException;
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "funcCode", scope = Function.class)
@JsonIdentityReference(alwaysAsId = true)
@JsonDeserialize(using = FunctionMixin.FunctionTokenDeserializer.class)
public class FunctionMixin {
public static class FunctionTokenDeserializer extends JsonDeserializer<Function> {
@Override
public Function deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (p.hasTextCharacters()) {
return Function.builder().funcCode(p.getText()).build();
} else {
p.nextToken();
p.nextValue();
Function func = Function.builder().funcCode(p.getText()).build();
p.nextToken();
return func;
}
}
}
}

View File

@ -0,0 +1,34 @@
package com.aisino.iles.core.json.mixins;
import com.aisino.iles.core.model.Menu;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.io.IOException;
@JsonIgnoreProperties(value = {"iconCls", "leaf"}, ignoreUnknown = true)
@JsonIdentityInfo(property = "menuId", generator = ObjectIdGenerators.PropertyGenerator.class, scope = Menu.class)
@JsonIdentityReference(alwaysAsId = true)
@JsonDeserialize(using = MenuMixin.MenuTokenDeserializer.class)
public class MenuMixin {
public static class MenuTokenDeserializer extends JsonDeserializer<Menu> {
@Override
public Menu deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (p.hasTextCharacters()) {
return Menu.builder().menuId(p.getText()).build();
} else {
p.nextToken();
p.nextValue();
Menu menu = Menu.builder().menuId(p.getText()).build();
p.nextToken();
return menu;
}
}
}
}

View File

@ -0,0 +1,32 @@
package com.aisino.iles.core.json.mixins;
import com.aisino.iles.core.model.Resource;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.io.IOException;
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "resourceId", scope = Resource.class)
@JsonIdentityReference(alwaysAsId = true)
@JsonDeserialize(using = ResourceMixin.ResourceTokenDeserializer.class)
public class ResourceMixin {
public static class ResourceTokenDeserializer extends JsonDeserializer<Resource> {
@Override
public Resource deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
if (p.hasTextCharacters()) {
return Resource.builder().resourceId(p.getText()).build();
} else {
JsonNode node = p.getCodec().readTree(p);
String resourceId = node.get("resourceId").textValue();
return Resource.builder().resourceId(resourceId).build();
}
}
}
}

View File

@ -0,0 +1,82 @@
package com.aisino.iles.core.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.Comment;
import java.time.LocalDateTime;
@Entity
@Table(name = "sys_fwjbxx",
indexes = @Index(name = "idx_fw_upd", columnList = "updateTime"))
@EqualsAndHashCode()
@ToString()
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddrInfo {
@Id
@Column(length = 100)
@Comment("房屋编码")
private String fwbm;
@Column(length = 16)
@Comment("行政区划编码")
private String xzqh;
@Column(length = 100)
@Comment("街路号")
private String jlh;
@Column(length = 10)
@Comment("门楼牌号")
private String mlph;
@Column(length = 10)
@Comment("一级门楼牌号")
private String yjmlph;
@Column(length = 10)
@Comment("二级门楼牌号")
private String ejmlph;
@Column(length = 10)
@Comment("三级门楼牌号")
private String sjmlph;
@Column(length = 10)
@Comment("室号")
private String sh;
@Column(length = 10)
@Comment("楼层号")
private String szc;
@Column(length = 100)
@Comment("详情")
private String xxdz;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Comment("更新时间")
private LocalDateTime updateTime;
@Column(length = 100)
@Comment("(政府规范)房屋编码")
private String FWDZBM;
@Column(length = 200)
@Comment("纬度")
private String x;
@Column(length = 200)
@Comment("经度")
private String y;
@Column(length = 30)
@Comment("政府社区")
private String zfsq;
}

View File

@ -0,0 +1,39 @@
package com.aisino.iles.core.model;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
/**
* 审计基类类
*
* @param <U>
*/
@EqualsAndHashCode(callSuper = true)
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public abstract class Audiable<U> extends BaseModel {
@CreatedBy
@Comment("创建人")
private U createBy;
@CreatedDate
@Comment("创建时间")
private LocalDateTime createTime;
@LastModifiedBy
@Comment("修改人")
private U lastModifiedBy;
@LastModifiedDate
@Comment("修改时间")
private LocalDateTime lastModifiedTime;
}

View File

@ -0,0 +1,37 @@
package com.aisino.iles.core.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.Transient;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
@EqualsAndHashCode(of = "clientId")
public class BaseModel implements Serializable {
/**
* 客户端ID 或者说 临时ID
*/
@Transient
private String clientId;
/**
* 是否需要报警
*/
// @Transient
// private boolean needAlarm = false;
public boolean isNeedAlarm() {
return false;
}
/**
* 是否需要预警
*/
// @Transient
// private boolean needEarlyWarning = false;
public boolean isNeedEarlyWarning() {
return false;
}
}

View File

@ -0,0 +1,57 @@
package com.aisino.iles.core.model;
import lombok.Setter;
/**
* 公共查询实体
* @author hx
* @since 2023-11-29
*/
@Setter
public class BaseQuery implements PageQueryable {
private Integer page;
private Integer pagesize;
private long total;
private String sort;
private String dir;
private boolean limit = false;
@Override
public Integer page() {
return page;
}
@Override
public Integer pageSize() {
return pagesize;
}
@Override
public long total() {
return total;
}
@Override
public String sort() {
return sort;
}
@Override
public String dir() {
return dir;
}
@Override
public boolean limit() {
return limit;
}
// 兼容旧接口
public void setPageSize(Integer pageSize) {
this.pagesize = pageSize;
}
public void setPagesize(Integer pageSize) {
this.pagesize = pageSize;
}
}

View File

@ -0,0 +1,74 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.enums.DictType;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import org.hibernate.validator.constraints.Length;
import org.springframework.data.annotation.LastModifiedDate;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 字典
*/
@Entity
@Table(name = "sys_dict")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "Dict")
@Data
@ToString(exclude = {"dictItems"})
@EqualsAndHashCode(of = "dictId", callSuper = false)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Dict extends Audiable<String> {
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("字典主键")
private String dictId;
@Column(nullable = false, length = 100)
@Comment("字典名称")
@NotEmpty(message = "字典名称不能为空")
@Length(max = 100, message = "字典名称最大长度不超过100")
private String dictName;
@Column(length = 50, nullable = false, unique = true)
@Comment("字典代码")
@NotEmpty(message = "字典代码不能为空")
@Length(max = 50, message = "字典代码最大长度不超过50")
private String dictCode;
@Column(length = 200)
@Comment("字典描述")
@Length(max = 200, message = "字典描述最大长度不超过200")
private String description;
@Column(length = 20)
@Comment("字典名称简拼")
@Length(max = 20, message = "字典名称简拼最大长度不超过20")
private String simplePinyin;
@Comment("字典名称全拼")
@Length(max = 255, message = "字典名称全码最大长度不超过255")
private String allPinyin;
@Comment("字典类型 01 简单 02 树形")
@NotNull(message = "字典类型不能为空")
private DictType dictType;
@Transient
private String dictTypeName;
@OneToMany(mappedBy = "dict", cascade = {CascadeType.MERGE, CascadeType.REMOVE, CascadeType.PERSIST, CascadeType.REFRESH}, orphanRemoval = true)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "DictItem")
@OrderBy(" orderNum asc")
@Builder.Default
private Set<DictItem> dictItems = new LinkedHashSet<>();
@Version
@LastModifiedDate
private LocalDateTime lastModifiedTime;
}

View File

@ -0,0 +1,115 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.enums.DataMode;
import jakarta.persistence.*;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Index;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import org.hibernate.annotations.*;
import org.hibernate.annotations.Cache;
import org.hibernate.validator.constraints.Length;
import org.springframework.data.annotation.LastModifiedDate;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 字典项
*/
// 数据映射
@Entity
@Table(name = "sys_dict_item",indexes = {
@Index(name = Constants.Indexes.idx_dictItem_value,columnList = "value")
})
@DynamicUpdate
@DynamicInsert
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "Dict")
@NamedEntityGraphs({
@NamedEntityGraph(
name = "dict-item-list",
attributeNodes = {
@NamedAttributeNode("dict"),
@NamedAttributeNode("parent")
}
)
})
// lombok
@Data
@EqualsAndHashCode(of = "dictItemId", callSuper = false)
@NoArgsConstructor
@AllArgsConstructor
public class DictItem extends Audiable<String> {
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("字典项主键")
private String dictItemId;
@NotEmpty(message = "字典项值不能为空")
@Length(max = 255, message = "字典项值最大长度不超过255")
@Column(nullable = false)
@Comment("字典项值")
private String value;
@NotEmpty(message = "字典项名称不能为空")
@Length(max = 255, message = "字典项名称最大长度不超过255")
@Column(nullable = false)
@Comment("字典项名称")
private String display;
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(nullable = false)
@Comment("排序号")
private Integer orderNum = 0;
@Length(max = 100, message = "字典项名称简拼最大长度不超过100")
@Column(length = 100)
@Comment("字典项名称简拼")
private String simplePinyin;
@Length(max = 255, message = "字典项名称全拼最大长度不超过255")
@Comment("字典项名称全拼")
private String allPinyin;
@NotNull(message = "所属字典不能为空")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dict_id", nullable = false)
@Comment("所属字典主键")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "Dict")
@ToString.Exclude
private Dict dict;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id", foreignKey = @ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT))
@Comment("上级字典项主键")
@ToString.Exclude
private DictItem parent;
@OneToMany(mappedBy = "parent")
@OrderBy(" orderNum asc")
@ToString.Exclude
private Set<DictItem> children = new LinkedHashSet<>();
@Transient
@Enumerated
private DataMode dataFlag = DataMode.nothing;
@LastModifiedDate
@Version
private LocalDateTime lastModifiedTime;
public DictItem(String value, String display, String simplePinyin, String allPinyin, Dict dict, DictItem parent) {
this.value = value;
this.display = display;
this.simplePinyin = simplePinyin;
this.allPinyin = allPinyin;
this.dict = dict;
this.parent = parent;
}
public DictItem(String value, String display, LocalDateTime lastModifiedTime) {
this.value = value;
this.display = display;
this.lastModifiedTime = lastModifiedTime;
}
}

View File

@ -0,0 +1,33 @@
package com.aisino.iles.core.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
@Entity
@Table(name = "sys_function")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "Function")
@Data
@EqualsAndHashCode(of = {"funcCode"}, callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Function extends BaseModel {
@Id
@Comment("功能代码主键")
@NotEmpty(message = "功能代码不能为空")
private String funcCode;
@NotEmpty(message = "功能名称不能为空")
@Size(max = 100, message = "功能名称最大长度不超过100")
@Column(length = 100, nullable = false)
@Comment("功能名称")
private String funcName;
@Size(max = 400, message = "功能描述最大长度不超过400")
@Column(length = 400)
@Comment("功能描述")
private String description;
}

View File

@ -0,0 +1,81 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicUpdate;
import jakarta.persistence.*;
import java.io.Serializable;
/**
* 生成编码信息
*
* @author huxin
* @since 2020-06-11
*/
@Entity
@Table(name = "t_scbm")
@Comment("生成编码信息表")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@DynamicUpdate
public class GenerateCode implements Serializable {
/**
* 编码分类
*/
@Column(name = "bmfl", length = 10, nullable = false)
@Comment("编码分类")
private String codeCategory;
/**
* 行政区划代码 / 编码前缀
*/
@Column(name = "xzqh", length = 30)
@Comment("行政区划代码 / 编码前缀")
private String codePrefix;
/**
* 日期年份
*/
@Column(name = "rqnf", length = 20)
@Comment("日期年份")
private String timeParameter;
/**
* 流水号
*/
@Column(name = "lsh", length = 10)
@Comment("流水号")
private String serialNumber;
/**
* 生成规则 0年;1年月;2日期;3没有年份;4两位年份
*/
@Column(name = "scgz", length = 1)
@Comment("生成规则 0年;1年月;2日期;3没有年份;4两位年份")
private Integer generateRule;
/**
* 省份简拼
*/
@Column(name = "sssf", length = 10)
@Comment("省份简拼")
private String provinceSimplePinyin;
/**
* 生成的编码
*/
@Column(name = "scbm", length = 100)
@Comment("生成的编码")
private String code;
/**
* 主键 , 原主键类型是数字现在改为uuid
*/
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(name = "scbmid", length = 36)
@Comment("主键")
private String id;
@Version
private long version;
}

View File

@ -0,0 +1,49 @@
package com.aisino.iles.core.model;
import jakarta.persistence.Cacheable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import org.hibernate.validator.constraints.Length;
@Entity
@Table(name = "sys_globalpar")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = {"globalParamCode"}, callSuper = true)
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "GlobalParam")
public class GlobalParam extends BaseModel {
/**
* 全局参数代码 主键
*/
@Id
@Comment("全局参数主键")
@NotEmpty(message = "全局参数代码不能为空")
@Pattern(regexp = "^[A-z0-9_*!@#$%^&()\\[\\];'\"<>]*$")
@Size(max = 255, message = "全局参数代码最大长度不超过255")
private String globalParamCode;
/**
* 全局参数名称
*/
@Comment("全局参数名称")
@Length(max = 255, message = "全局参数名称最大长度不超过255")
private String globalParamName;
/**
* 全局参数值
*/
@Comment("全局参数值")
@Length(max = 255, message = "全局参数值最大长度不超过255")
private String globalParamValue;
}

View File

@ -0,0 +1,118 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.validator.constraints.Length;
import java.util.HashSet;
import java.util.Set;
/**
* 管辖机构
*/
@Entity
@Table(name = "sys_jurisdiction")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "Jurisdiction")
@NamedEntityGraphs({
@NamedEntityGraph(
name="jurisdiction-list",
attributeNodes = {
@NamedAttributeNode("parent")
}
)
})
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@EqualsAndHashCode(of = {"jurisdictionId"}, callSuper = false)
@Builder
@AllArgsConstructor
@DynamicUpdate
//@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "jurisdictionId", scope = Jurisdiction.class)
//@EntityListeners(JurisdictionListener.class)
public class Jurisdiction extends Audiable<String> {
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("机构信息主键")
private String jurisdictionId;
@NotEmpty(message = "机构代码不能为空")
@Pattern(regexp = "[A-z0-9]*", message = "机构代码只能是字母和数字")
@Length(max = 20, message = "机构代码最大长度不超过20")
@Column(length = 20, nullable = false, unique = true)
@Comment("机构编码 例如 500103000000")
private String jurisdictionCode;
@NotEmpty(message = "机构名称不能为空")
@Length(max = 255, message = "机构名称最大长度不超过255")
@Column(nullable = false)
@Comment("机构名称")
private String jurisdictionName;
// @Length(max = 50, message = "机构名称简称最大长度不超过50")
// @Column(length = 50, nullable = false)
// @Comment("机构名称简称")
// private String jurisSimpleName;
@NotNull(message = "机构级别不能为空")
@Max(value = 10, message = "机构级别最大值不超过10")
@Column(nullable = false, length = 10)
@Comment("机构级别")
@Builder.Default
private Integer jurisdictionLevel = 1;
@GeneratedValue(strategy = GenerationType.AUTO)
@Builder.Default
@Comment("排序号")
private Integer orderNum = 0;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_juris_id")
@Comment("上级机构主键")
@ToString.Exclude
private Jurisdiction parent;
@Length(max = 255, message = "机构代码全码(包含父级代码)最大长度不超过255")
@Comment("机构信息全码 例如500000000000.500103000000.")
private String jurisFullCode;
@Length(max = 20, message = "机构代码简码(去偶数0)最大长度不超过20")
@Column(length = 20, nullable = false)
@Comment("机构信息简码 例如 500103")
private String jurisSimpleCode;
@Builder.Default
@Comment("是否为最下级")
private Boolean leaf = true;
// @Length(max = 100, message = "机构名称简码最大长度不超过100")
// @Column(length = 100)
// @Comment("机构名称简拼")
// private String simplePinyin;
// @Length(max = 255, message = "机构名称全码最大长度不超过255")
// @Comment("机构名称全拼")
// private String allPinyin;
// @Length(max = 12, message = "公安部代码最大不超过12")
// @Pattern(regexp = "[A-z0-9]*", message = "公安部代码只能是字母和数字")
// @Column(length = 12)
// @Comment("公安部代码")
// private String gabdm;
// @Length(max = 50, message = "公安部名称最大不超过50")
// @Column(length = 50)
// @Comment("公安部名称")
// private String gabmc;
@ManyToMany(mappedBy = "jurisdictions")
@JsonIgnore
@Builder.Default
@ToString.Exclude
private Set<User> users = new HashSet<>();
@OneToMany(mappedBy = "parent")
@Builder.Default
@ToString.Exclude
private Set<Jurisdiction> children = new HashSet<>();
}

View File

@ -0,0 +1,53 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.enums.LoginLogType;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.Comment;
import java.time.LocalDateTime;
/**
* 登录日志
*/
@Entity
@Table(name = "sys_login_log", indexes = {@Index(name = "idx_lglog_sid", columnList = "sessionId")})
@Data
@EqualsAndHashCode(of = {"loginLogId"})
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginLog {
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("登录日志主键")
private String loginLogId;
@Comment("登录用户名")
private String username;
@Column(nullable = false)
@Comment("登录时间")
private LocalDateTime operateTime;
@Column(length = 2, nullable = false)
@Comment("登录日志类型 01 登录 02 登出")
private LoginLogType loginLogType;
@Column(length = 128)
@Comment("登录IP地址")
private String ipAddress;
/**
* 操作结果代码 0 为正常, 非0为错误代码 参考 Constants.Exceptions
*/
@Comment("操作结果代码 0 为正常, 非0为错误代码 参考 Constants.Exceptions")
private Integer resultCode;
/**
* 回话id ,使用token的签名
*/
@Column(length = 50)
@Comment("会话id")
private String sessionId;
@Comment("退出时间")
private LocalDateTime exited;
}

View File

@ -0,0 +1,132 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.converter.persistence.IconClsStringArray2VarcharConverter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotEmpty;
import lombok.*;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Comment;
import org.hibernate.validator.constraints.Length;
import java.util.LinkedHashSet;
import java.util.Set;
@Entity
@Table(name = "sys_menu")
@Cacheable
@org.hibernate.annotations.Cache(region = "Menu", usage = CacheConcurrencyStrategy.READ_WRITE)
@NamedEntityGraphs({
@NamedEntityGraph(
name = "menus-tree",
attributeNodes = {
@NamedAttributeNode("parent")
}
)
})
@Data
@EqualsAndHashCode(of = "menuId", callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = {"parent", "children"})
@JsonIgnoreProperties(ignoreUnknown = true, value = {"hibernateLazyInitializer", "handler", "fieldHandler"})
public class Menu extends BaseModel {
/**
* 菜单ID 主键
*/
@NotEmpty(message = "菜单ID不能为空", groups = {Add.class})
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("菜单主键")
private String menuId;
/**
* 菜单代码 业务唯一标志
*/
@Column(length = 100, nullable = false, unique = true)
@Comment("菜单代码")
@NotEmpty(message = "菜单代码不能为空", groups = {Add.class, Modify.class})
@Length(max = 100, message = "菜单名称最大长度不超过100", groups = {Add.class, Modify.class})
private String menuCode;
/**
* 菜单名称
*/
@Column(nullable = false)
@Comment("菜单名称")
@NotEmpty(message = "菜单名称不能为空", groups = {Add.class, Modify.class})
@Length(max = 255, message = "菜单名称最大长度不超过255", groups = {Add.class, Modify.class})
private String menuName;
/**
* 菜单图标字符图标类型
*/
@Length(max = 20, message = "菜单图标类最大长度不超过20", groups = {Add.class, Modify.class})
@Column(length = 20)
@Comment("菜单图标,字符图标")
@Convert(converter = IconClsStringArray2VarcharConverter.class)
private String[] iconCls;
/**
* 菜单图标图片路径
*/
@Comment("菜单图标,图片类型(地址)")
@Length(max = 255, message = "菜单图标路径最大长度不超过255", groups = {Add.class, Modify.class})
private String iconPath;
/**
* 菜单路径用来描述菜单层级关系
*/
@Length(max = 400, message = "菜单路径最大长度不超过400", groups = {Add.class, Modify.class})
@Column(length = 400, unique = true)
private String path;
/**
* 排序号
*/
@Max(value = 1000, message = "排序号最大值不超过1000")
@Comment("排序号")
@ColumnDefault("0")
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Builder.Default
private Integer orderNum = 0;
/**
* 是否为最下级菜单
*/
@Builder.Default
private Boolean leaf = true;
/**
* 菜单名称简拼
*/
@Column(length = 40)
@Comment("菜单名称简拼")
@Length(max = 40, message = "菜单名称简拼最大长度不超过40", groups = {Add.class, Modify.class})
private String simplePinyin;
/**
* 菜单名称全拼
*/
@Length(max = 255, message = "菜单名称全拼最大长度不超过255", groups = {Add.class, Modify.class})
@Comment("菜单名称全拼")
private String allPinyin;
/**
* 上级菜单通过上级菜单ID关联
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_menu_id")
@Comment("上级菜单主键")
private Menu parent;
/**
* 下级菜单
*/
@OneToMany(mappedBy = "parent")
@BatchSize(size = 10)
@Builder.Default
private Set<Menu> children = new LinkedHashSet<>();
public interface Add {
}
public interface Modify {
}
}

View File

@ -0,0 +1,123 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.enums.IpType;
import com.aisino.iles.core.model.enums.OperateStatus;
import com.aisino.iles.core.model.enums.OperateType;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import java.time.LocalDateTime;
/**
* 操作日志
*/
@Entity
@Table(name = "sys_operate_log")
@DynamicInsert
@DynamicUpdate
@NamedEntityGraphs({
@NamedEntityGraph(
name = "operate-log-detail",
attributeNodes = {
@NamedAttributeNode("args"),
@NamedAttributeNode("error")
}
)
})
@Data
@EqualsAndHashCode(callSuper = false, of = "operateLogId")
@Builder
@ToString(exclude = {"error", "args"})
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog extends Audiable<String> {
/**
* 操作日志ID 主键
*/
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("操作日志ID 主键")
private String operateLogId;
/**
* 操作类型 对应增删改查的枚举类型
*/
@Enumerated(EnumType.STRING)
@Column(length = 6, nullable = false)
@Comment("操作类型 对应增删改查的枚举类型")
private OperateType operateType;
/**
* 操作函数/方法全名格式为 ..方法
*/
@Column(nullable = false)
@Comment("操作函数/方法全名格式为 包.类.方法")
private String method;
/**
* 操作方法使用的参数,格式为JSON
*/
@ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
@JoinColumn(name = "args_id")
@Comment("操作方法使用的参数,格式为JSON")
private OperateLogText args;
/**
* 操作描述
*/
@Column(length = 1000)
@Comment("操作描述")
private String description;
/**
* 操作执行者的ip
*/
@Column(length = 30)
@Comment("操作执行者的ip")
private String ip;
/**
* ip类型
*/
@Column(length = 2, nullable = false)
@Comment("ip类型")
@Enumerated(EnumType.STRING)
private IpType ipType;
/**
* 操作名称
*/
@Column(length = 100, nullable = false)
@Comment("操作名称")
private String name;
/**
* 执行开始时间
*/
@Column(nullable = false)
@Comment("执行开始时间")
private LocalDateTime executionBeginTime;
/**
* 执行结束时间
*/
@Column(nullable = false)
@Comment("执行结束时间")
private LocalDateTime executionEndTime;
/**
* 操作状态 0 成功 1 失败
*/
@Column(nullable = false, length = 1)
@Comment("操作状态 0 成功 1 失败")
private OperateStatus status;
/**
* 错误信息
*/
@ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
@JoinColumn(name = "error_id")
@Comment("错误信息")
private OperateLogText error;
@Column(length = 64)
@Comment("用户身份证号")
private String idNum;
}

View File

@ -0,0 +1,31 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.enums.OperateLogTextType;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
@Entity
@Table(name = "sys_operate_log_text")
@Comment("操作日志详细信息")
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "operateLogTextId")
@Builder
public class OperateLogText {
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("操作日志详细信息ID")
private String operateLogTextId;
@Comment("操作日志类型")
@Enumerated(EnumType.STRING)
private OperateLogTextType type;
@JdbcTypeCode(SqlTypes.LONGVARCHAR)
@Comment("内容")
private String content;
}

View File

@ -0,0 +1,42 @@
package com.aisino.iles.core.model;
/**
* 可分页查询的实体
*/
public interface PageQueryable {
/**
* 页数
* @return 页数
*/
Integer page();
/**
* 每页数
* @return 每页数
*/
Integer pageSize();
/**
* 总记录数
* @return 总记录数
*/
long total();
/**
* 排序字段
* @return 排序字段
*/
String sort();
/**
* 排序方式
* @return 排序方式
*/
String dir();
/**
* 是否限制查询
* @return 是否限制查询
*/
boolean limit();
}

View File

@ -0,0 +1,109 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.*;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import org.hibernate.validator.constraints.Length;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "sys_permission")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "Permission")
@Data
@EqualsAndHashCode(of = "permissionId", callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(of = {"permissionId", "name", "description"})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "permissionId", scope = Permission.class)
public class Permission extends BaseModel {
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("权限许可信息主键")
@NotEmpty(message = "权限ID不能为空", groups = {Modify.class})
private String permissionId;
@Column(length = 100, nullable = false)
@Comment("权限许可名称")
@NotEmpty(message = "权限名称不能为空", groups = {Add.class, Modify.class})
@Length(max = 100, message = "权限名称最大长度不超过100")
private String name;
@Column(length = 500)
@Comment("权限许可信息描述")
@Length(max = 500, message = "权限描述最大长度不超过500")
private String description;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "sys_user_permission",
joinColumns = @JoinColumn(name = "permission_id"),
inverseJoinColumns = @JoinColumn(name = "user_id")
)
@BatchSize(size = 10)
@Builder.Default
private Set<User> users = new HashSet<>();
@ManyToMany
@JoinTable(
name = "sys_role_permission",
joinColumns = @JoinColumn(name = "permission_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
@BatchSize(size = 10)
@Builder.Default
private Set<Role> roles = new HashSet<>();
@ManyToMany
@JoinTable(
name = "sys_function_permission",
joinColumns = @JoinColumn(name = "permission_id"),
inverseJoinColumns = @JoinColumn(name = "func_code")
)
@org.hibernate.annotations.Cache(region = "Function", usage = CacheConcurrencyStrategy.READ_WRITE)
@BatchSize(size = 10)
@Builder.Default
private Set<Function> functions = new HashSet<>();
@ManyToMany
@JoinTable(
name = "sys_menu_permission",
joinColumns = @JoinColumn(name = "permission_id"),
inverseJoinColumns = @JoinColumn(name = "menu_id")
)
@org.hibernate.annotations.Cache(region = "Menu", usage = CacheConcurrencyStrategy.READ_WRITE)
@BatchSize(size = 10)
@Builder.Default
@JsonIgnoreProperties(ignoreUnknown = true, value = {"children"})
private Set<Menu> menus = new HashSet<>();
@ManyToMany
@JoinTable(
name = "sys_resource_permission",
joinColumns = @JoinColumn(name = "permission_id"),
inverseJoinColumns = @JoinColumn(name = "resource_id")
)
@org.hibernate.annotations.Cache(region = "Resource", usage = CacheConcurrencyStrategy.READ_WRITE)
@BatchSize(size = 15)
@Builder.Default
private Set<Resource> resources = new HashSet<>();
/**
* 新增时候的验证信息分组
*/
public interface Add {
}
/**
* 修改的验证信息分组
*/
public interface Modify {
}
}

View File

@ -0,0 +1,24 @@
package com.aisino.iles.core.model;
public class QueryMixin extends BaseQuery {
@Override
public Integer page() {
return super.page();
}
@Override
public Integer pageSize() {
return super.pageSize();
}
@Override
public String sort() {
return super.sort();
}
@Override
public String dir() {
return super.dir();
}
}

View File

@ -0,0 +1,60 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.enums.Action;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.validator.constraints.Length;
@Entity
@Table(name = "sys_resource")
@Cacheable
@org.hibernate.annotations.Cache(region = "Resource", usage = CacheConcurrencyStrategy.READ_WRITE)
@DynamicUpdate
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "resourceId", callSuper = true)
public class Resource extends BaseModel {
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("资源信息主键")
@NotEmpty(message = "资源ID不能为空", groups = {Modify.class})
private String resourceId;
@Column(length = 10, nullable = false)
@Comment("资源方法动作类型")
@Enumerated(EnumType.STRING)
@NotEmpty(message = "资源方法动作不能为空", groups = {Add.class, Modify.class})
private Action action;
@Column(length = 400, nullable = false)
@Comment("资源路径")
@NotEmpty(message = "资源路径不能为空", groups = {Add.class, Modify.class})
@Length(max = 400, message = "资源路径不能为空", groups = {Add.class, Modify.class})
private String resourcePath;
@Column(length = 100)
@Comment("资源说明")
@NotEmpty(message = "资源说明", groups = {Add.class, Modify.class})
private String description;
/**
* 临时分组编号
*/
@Transient
private int group;
/**
* 添加验证分组
*/
public interface Add {
}
/**
* 修改验证分组
*/
public interface Modify {
}
}

View File

@ -0,0 +1,84 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.*;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import org.hibernate.validator.constraints.Length;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "sys_role")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "Role")
@Data
@EqualsAndHashCode(of = "roleId", callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = {"users", "userTypes", "permissions"})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "roleId", scope = Role.class)
public class Role extends BaseModel {
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("角色信息主键")
@NotEmpty(message = "角色ID不能为空", groups = {Modify.class})
private String roleId;
@Column(length = 100, nullable = false)
@Comment("角色信息名称")
@NotEmpty(message = "角色名称不能为空", groups = {Add.class, Modify.class})
@Length(max = 100, message = "角色名称最大长度不超过100", groups = {Add.class, Modify.class})
private String roleName;
@Comment("角色信息描述")
@Length(max = 255, message = "角色描述最大长度不超过255", groups = {Add.class, Modify.class})
private String description;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "sys_role_permission",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id")
)
@org.hibernate.annotations.Cache(region = "Permission", usage = CacheConcurrencyStrategy.READ_WRITE)
@BatchSize(size = 10)
@Builder.Default
private Set<Permission> permissions = new HashSet<>();
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "sys_user_role",
joinColumns = @JoinColumn(name = "roleId"),
inverseJoinColumns = @JoinColumn(name = "userId")
)
@BatchSize(size = 10)
@Builder.Default
private Set<User> users = new HashSet<>();
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "sys_user_type_role",
joinColumns = @JoinColumn(name = "roleId"),
inverseJoinColumns = @JoinColumn(name = "user_type_id")
)
@BatchSize(size = 5)
@Builder.Default
private Set<UserType> userTypes = new HashSet<>();
/**
* 角色新增验证分组
*/
public interface Add {
}
/**
* 角色修改验证分组
*/
public interface Modify {
}
}

View File

@ -0,0 +1,40 @@
package com.aisino.iles.core.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Comment;
@Entity
@Table(name = "sys_jdjbxx")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StreetInfo {
@Id
@Column(length = 100)
@Comment("街道编码")
private String dm;
@Column(length = 100)
@Comment("街道名称")
private String mc;
@Column(length = 16)
private String wb;
@Column(length = 16)
@Comment("拼音")
private String py;
@Column(length = 100)
@Comment("是否有效1有效0无效")
private String valid;
}

View File

@ -0,0 +1,19 @@
package com.aisino.iles.core.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.Instant;
/**
* 令牌实体
*/
@EqualsAndHashCode(of = "token")
@Data
public class Token {
private String uid;
private Instant expireTime;
private String jsonProfile;
private String token;
private String other;
}

View File

@ -0,0 +1,110 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.aisino.iles.core.model.enums.UserStatus;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import org.springframework.data.annotation.LastModifiedDate;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "sys_user")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "User")
@Data
@EqualsAndHashCode(of = {"userId"}, callSuper = false)
@ToString(exclude = {"jurisdictions", "permissions", "roles"})
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User extends Audiable<String> {
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("用户信息主键")
private String userId;
@Column(length = 36, unique = true, nullable = false)
@Comment("用户名/帐号")
private String username;
@Column(length = 64, nullable = false)
@Comment("密码")
private String password;
@Email
@Column(unique = true, length = 80)
@Comment("邮箱")
private String email;
@Column(length = 2, nullable = false)
@Comment("用户状态 01 正常 02 异常 03 锁定 04 删除")
private UserStatus status;
@Transient
private String statusName;
@Column(length = 32)
@Comment("移动电话")
private String mobilePhone;
@Column(length = 64)
@Comment("身份证号码")
private String idNum;
@Column(length = 45, nullable = false)
@Comment("用户昵称/用户显示名称")
private String nickName;
@Comment("用户头像图标(地址)")
private String userIcon;
@Column(length = 10)
@Comment("是否开通移动警务应用")
private String sfktydjwt;
@ManyToMany
@JoinTable(
name = "sys_user_usertype",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "user_type_id")
)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "UserType")
@Builder.Default
private Set<UserType> userTypes = new HashSet<>();
@Builder.Default
@ManyToMany
@JoinTable(
name = "sys_user_permission",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id")
)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "Permission")
private Set<Permission> permissions = new HashSet<>();
@ManyToMany(cascade = {CascadeType.REFRESH})
@JoinTable(
name = "sys_user_jurisdiction",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "jurisdiction_id"))
@Builder.Default
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "Jurisdiction")
@JsonIgnoreProperties(ignoreUnknown = true, value = {"children"})
private Set<Jurisdiction> jurisdictions = new HashSet<>();
@ManyToMany
@JoinTable(
name = "sys_user_role",
joinColumns = @JoinColumn(name = "userId"),
inverseJoinColumns = @JoinColumn(name = "roleId")
)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "Role")
@Builder.Default
private Set<Role> roles = new HashSet<>();
@Transient
private String ipAddress;
@Transient
private LocalDateTime loginTime;
@Column(length = 32)
@Comment("门户注册电话")
private String registerMobilePhone;
@Version
@LastModifiedDate
private LocalDateTime lastModifiedTime;
}

View File

@ -0,0 +1,16 @@
package com.aisino.iles.core.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserStatistics {
private String userType;//用户类型
private String userTypeName;//用户类型
private Long userTotal;//用户总数
}

View File

@ -0,0 +1,117 @@
package com.aisino.iles.core.model;
import com.aisino.iles.common.util.Constants;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
@Entity
@Table(name = "sys_user_type2")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "UserType")
@Data
@EqualsAndHashCode(of = {"userTypeId"}, callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(of = {"userTypeId", "typeCode", "typeName"})
public class UserType extends BaseModel {
/**
* 用户类别id 主键
*/
@Id
@GeneratedValue(generator = Constants.Genernators.gen_ulid)
@Column(length = 36)
@Comment("用户类别主键")
@NotEmpty(message = "用户类别ID不能为空", groups = {Modify.class})
private String userTypeId;
/**
* 用户类别代码
*/
@NotEmpty(message = "用户类别代码不能为空",groups = {Add.class})
@Max(groups = {Modify.class}, value = 3,message = "用户类别代码长度最大不超过3")
@Min(groups = {Modify.class}, value = 3,message = "用户类别代码长度最小为3")
@Column(length = 4, nullable = false)
@Comment("用户类别代码")
private String typeCode;
/**
* 用户类别名称
*/
@NotEmpty(message = "用户类别名称不能为空",groups = Add.class)
@Max(groups = {Modify.class}, value = 20, message = "用户类别名称长度最大不超过20")
@Column(length = 20, nullable = false)
@Comment("用户类别名称")
private String typeName;
/**
* 行业类别代码
*/
@Column(length = 10)
@Comment("用户类别所属行业类别代码")
private String hylbdm; // 行业类别代码
/**
* 行业类别名称
*/
@Column(length = 30)
@Comment("用户类别所属行业类别名称")
private String hylb; // 行业类别
@Column(length = 10, name = "level_")
@Comment("用户级别")
private String level;
/**
* 业务类别代码
*/
@Column(length = 5)
@Comment("用户类别业务类别代码,对应一些额外的分类情况,比如企业主分类")
private String businessCategory;
/**
* 业务类别名称
*/
@Column(length = 40)
@Comment("业务类别名称")
private String businessCategoryName;
/**
* 关联菜单信息
*/
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "sys_user_type_menu",
joinColumns = @JoinColumn(name = "user_type_id"),
inverseJoinColumns = @JoinColumn(name = "menu_id")
)
@org.hibernate.annotations.Cache(region = "Menu", usage = CacheConcurrencyStrategy.READ_WRITE)
@JsonIgnoreProperties(ignoreUnknown = true, value = {"children"})
@Builder.Default
private Set<Menu> menus = new LinkedHashSet<>();
/**
* 关联角色信息
*/
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "sys_user_type_role",
joinColumns = @JoinColumn(name = "user_type_id"),
inverseJoinColumns = @JoinColumn(name = "roleId")
)
@Builder.Default
private Set<Role> roles = new HashSet<>();
/**
* 新增验证
*/
public interface Add {}
/**
* 修改验证
*/
public interface Modify extends Add {}
}

View File

@ -0,0 +1,8 @@
package com.aisino.iles.core.model;
/**
* 单值枚举
*/
public interface ValueEnum<T> {
T getValue();
}

View File

@ -0,0 +1,39 @@
package com.aisino.iles.core.model.dto;
import com.aisino.iles.core.model.enums.DictType;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* 字典
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DictDTO implements Serializable {
private String dictId;
@NotNull
private String dictName;
@NotNull
private String dictCode;
private String description;
private String simplePinyin;
private String allPinyin;
private DictType dictType;
private String dictTypeName;
private String createBy;
private LocalDateTime createTime;
private String lastModifiedBy;
private LocalDateTime lastModifiedTime;
@Builder.Default
private Set<DictItemDTO> dictItems = new LinkedHashSet<>();
}

View File

@ -0,0 +1,46 @@
package com.aisino.iles.core.model.dto;
import com.aisino.iles.core.model.enums.DataMode;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.Transient;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Set;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DictItemDTO implements Serializable {
private String dictItemId;
private String value;
private String display;
private String appendValue;
private String appendValueMark;
@Builder.Default
private Integer orderNum = 0;
private String simplePinyin;
private String allPinyin;
@JsonBackReference
private DictDTO dict;
private DictItemDTO parent;
@JsonManagedReference("parent")
@Builder.Default
private Set<DictItemDTO> children = new LinkedHashSet<>();
@Transient
@Builder.Default
private DataMode dataFlag = DataMode.nothing;
private String createBy;
private LocalDateTime createTime;
private String lastModifiedBy;
private LocalDateTime lastModifiedTime;
@Transient
private String parentId;
}

View File

@ -0,0 +1,44 @@
package com.aisino.iles.core.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 吉大正元证书信息
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JitPkiAttr {
/**
* 证书过期时间时间格式yyyy年MM月dd日 HH时mm分ss秒 或者 yyyyMMddHHmmss格式
*/
private LocalDateTime expiredTime;
/**
* 证书申请时间格式同过期时间
*/
private LocalDateTime notBeforeTime;
/**
* 证件号码
*/
private String idNum;
/**
* 用户的名称
*/
private String userName;
/**
* 机构代码
*/
private String jurisdictionCode;
/**
* 证书附带角色属性
*/
private String roles;
/**
* ukey 的唯一标志
*/
private String ukeyId;
}

View File

@ -0,0 +1,48 @@
package com.aisino.iles.core.model.dto;
import com.aisino.iles.core.model.Jurisdiction;
import com.aisino.iles.core.model.UserType;
import com.aisino.iles.core.model.enums.UserStatus;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.Email;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 登录用户数据实体
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginUserDto {
private String userId;
private String username;
@JsonIgnore
private String password;
@Email
private String email;
private UserStatus status;
private String statusName;
private String mobilePhone;
private String idNum;
private String nickName;
private String userIcon;
private String ipAddress;
private LocalDateTime loginTime;
@Builder.Default
private Set<UserType> userTypes = new HashSet<>();
@Builder.Default
private Set<Jurisdiction> jurisdictions = new HashSet<>();
@Builder.Default
private Map<String, Set<MenuTreeNodeDTO>> typeMenus = new HashMap<>();
}

View File

@ -0,0 +1,31 @@
package com.aisino.iles.core.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.LinkedHashSet;
import java.util.Set;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MenuTreeNodeDTO {
private String menuId;
private String menuCode;
private String menuName;
private String[] iconCls;
private String iconPath;
private String path;
@Builder.Default
private Integer orderNum = 0;
@Builder.Default
private Boolean leaf = true;
private String simplePinyin;
private String allPinyin;
private MenuTreeNodeDTO parent;
@Builder.Default
private Set<MenuTreeNodeDTO> children = new LinkedHashSet<>();
}

Some files were not shown because too many files have changed in this diff Show More