unmodifiableCollection(accessTokens);
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisAutoCacheManager.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisAutoCacheManager.java
new file mode 100644
index 0000000..e5acb82
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisAutoCacheManager.java
@@ -0,0 +1,56 @@
+package cn.sh.stc.sict.cloud.common.data.cache;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.Cache;
+import org.springframework.data.redis.cache.RedisCache;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.lang.Nullable;
+
+import java.time.Duration;
+import java.util.Map;
+
+/**
+ * @Description redis cache 扩展cache name自动化配置
+ *
+ *
+ * cachename = xx#ttl
+ *
+ * @Author
+ * @Date
+ */
+@Slf4j
+public class RedisAutoCacheManager extends RedisCacheManager {
+ private static final String SPLIT_FLAG = "#";
+ private static final int CACHE_LENGTH = 2;
+
+ RedisAutoCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
+ Map initialCacheConfigurations, boolean allowInFlightCacheCreation) {
+ super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
+ }
+
+ @Override
+ protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
+ if (StrUtil.isBlank(name) || !name.contains(SPLIT_FLAG)) {
+ return super.createRedisCache(name, cacheConfig);
+ }
+
+ String[] cacheArray = name.split(SPLIT_FLAG);
+ if (cacheArray.length < CACHE_LENGTH) {
+ return super.createRedisCache(name, cacheConfig);
+ }
+
+ if (cacheConfig != null) {
+ long cacheAge = Long.parseLong(cacheArray[1]);
+ cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(cacheAge));
+ }
+ return super.createRedisCache(name, cacheConfig);
+ }
+
+ @Override
+ public Cache getCache(String name) {
+ return super.getCache(name);
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisCacheAutoConfiguration.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisCacheAutoConfiguration.java
new file mode 100644
index 0000000..712bfc2
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisCacheAutoConfiguration.java
@@ -0,0 +1,94 @@
+package cn.sh.stc.sict.cloud.common.data.cache;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
+import org.springframework.boot.autoconfigure.cache.CacheProperties;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.lang.Nullable;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description 扩展redis-cache支持注解cacheName添加超时时间
+ *
+ * @Author
+ * @Date
+ */
+@Configuration
+@AutoConfigureAfter({RedisAutoConfiguration.class})
+@ConditionalOnBean({RedisConnectionFactory.class})
+@ConditionalOnMissingBean({CacheManager.class})
+@EnableConfigurationProperties(CacheProperties.class)
+public class RedisCacheAutoConfiguration {
+ private final CacheProperties cacheProperties;
+ private final CacheManagerCustomizers customizerInvoker;
+ @Nullable
+ private final RedisCacheConfiguration redisCacheConfiguration;
+
+ RedisCacheAutoConfiguration(CacheProperties cacheProperties,
+ CacheManagerCustomizers customizerInvoker,
+ ObjectProvider redisCacheConfiguration) {
+ this.cacheProperties = cacheProperties;
+ this.customizerInvoker = customizerInvoker;
+ this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
+ }
+
+ @Bean
+ public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, ResourceLoader resourceLoader) {
+ DefaultRedisCacheWriter redisCacheWriter = new DefaultRedisCacheWriter(connectionFactory);
+ RedisCacheConfiguration cacheConfiguration = this.determineConfiguration(resourceLoader.getClassLoader());
+ List cacheNames = this.cacheProperties.getCacheNames();
+ Map initialCaches = new LinkedHashMap<>();
+ if (!cacheNames.isEmpty()) {
+ Map cacheConfigMap = new LinkedHashMap<>(cacheNames.size());
+ cacheNames.forEach(it -> cacheConfigMap.put(it, cacheConfiguration));
+ initialCaches.putAll(cacheConfigMap);
+ }
+ RedisAutoCacheManager cacheManager = new RedisAutoCacheManager(redisCacheWriter, cacheConfiguration,
+ initialCaches, true);
+ cacheManager.setTransactionAware(false);
+ return this.customizerInvoker.customize(cacheManager);
+ }
+
+ private RedisCacheConfiguration determineConfiguration(ClassLoader classLoader) {
+ if (this.redisCacheConfiguration != null) {
+ return this.redisCacheConfiguration;
+ } else {
+ CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
+ RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
+ config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
+ if (redisProperties.getTimeToLive() != null) {
+ config = config.entryTtl(redisProperties.getTimeToLive());
+ }
+
+ if (redisProperties.getKeyPrefix() != null) {
+ config = config.prefixKeysWith(redisProperties.getKeyPrefix());
+ }
+
+ if (!redisProperties.isCacheNullValues()) {
+ config = config.disableCachingNullValues();
+ }
+
+ if (!redisProperties.isUseKeyPrefix()) {
+ config = config.disableKeyPrefix();
+ }
+
+ return config;
+ }
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisCacheManagerConfig.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisCacheManagerConfig.java
new file mode 100644
index 0000000..b4e272e
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisCacheManagerConfig.java
@@ -0,0 +1,27 @@
+package cn.sh.stc.sict.cloud.common.data.cache;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+/**
+ * @Description CacheManagerCustomizers配置
+ *
+ * @Author
+ * @Date
+ */
+@Configuration
+@ConditionalOnMissingBean(CacheManagerCustomizers.class)
+public class RedisCacheManagerConfig {
+
+ @Bean
+ public CacheManagerCustomizers cacheManagerCustomizers(
+ ObjectProvider>> customizers) {
+ return new CacheManagerCustomizers(customizers.getIfAvailable());
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisTemplateConfig.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisTemplateConfig.java
new file mode 100644
index 0000000..b05d9b4
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/cache/RedisTemplateConfig.java
@@ -0,0 +1,39 @@
+package cn.sh.stc.sict.cloud.common.data.cache;
+
+import lombok.AllArgsConstructor;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+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.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * @Description RedisTemplate 配置
+ * @Author
+ * @Date
+ */
+@EnableCaching
+@Configuration
+@AllArgsConstructor
+@AutoConfigureBefore(RedisAutoConfiguration.class)
+public class RedisTemplateConfig {
+ private final RedisConnectionFactory redisConnectionFactory;
+
+ @Bean
+ public RedisTemplate redisTemplate() {
+ RedisTemplate redisTemplate = new RedisTemplate<>();
+ Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
+ Jackson2JsonRedisSerializer(Object.class);
+ redisTemplate.setKeySerializer(new StringRedisSerializer());
+ redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+ redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
+ redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
+ redisTemplate.setConnectionFactory(redisConnectionFactory);
+ return redisTemplate;
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/datascope/DataScope.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/datascope/DataScope.java
new file mode 100644
index 0000000..9ef4f00
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/datascope/DataScope.java
@@ -0,0 +1,32 @@
+package cn.sh.stc.sict.cloud.common.data.datascope;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @Description 数据权限查询参数
+ * @Author
+ * @Date
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DataScope extends HashMap {
+ /**
+ * 限制范围的字段名称
+ */
+ private String scopeName = "deptId";
+
+ /**
+ * 具体的数据范围
+ */
+ private List deptIds = new ArrayList<>();
+
+ /**
+ * 是否只查询本部门
+ */
+ private Boolean isOnly = false;
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/datascope/DataScopeInterceptor.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/datascope/DataScopeInterceptor.java
new file mode 100644
index 0000000..16713df
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/datascope/DataScopeInterceptor.java
@@ -0,0 +1,157 @@
+package cn.sh.stc.sict.cloud.common.data.datascope;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.db.Db;
+import cn.hutool.db.Entity;
+import cn.sh.stc.sict.cloud.common.core.constant.SecurityConstants;
+import cn.sh.stc.sict.cloud.common.core.exception.CheckedException;
+import cn.sh.stc.sict.cloud.common.data.enums.DataScopeTypeEnum;
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
+import cn.sh.stc.sict.cloud.common.security.service.SictUser;
+import cn.sh.stc.sict.cloud.common.security.util.SecurityUtils;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.plugin.*;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.reflection.SystemMetaObject;
+import org.springframework.security.core.GrantedAuthority;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @Description mybatis 数据权限拦截器
+ * @Author
+ * @Date
+ */
+@Slf4j
+@AllArgsConstructor
+@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
+public class DataScopeInterceptor extends AbstractSqlParserHandler implements Interceptor {
+ private final DataSource dataSource;
+
+ @Override
+ @SneakyThrows
+ public Object intercept(Invocation invocation) {
+ StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
+ MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
+ this.sqlParser(metaObject);
+ // 先判断是不是SELECT操作
+ MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
+ if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
+ return invocation.proceed();
+ }
+
+ BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
+ String originalSql = boundSql.getSql();
+ Object parameterObject = boundSql.getParameterObject();
+
+ //查找参数中包含DataScope类型的参数
+ DataScope dataScope = findDataScopeObject(parameterObject);
+ if (dataScope == null) {
+ return invocation.proceed();
+ }
+
+ String scopeName = dataScope.getScopeName();
+ List deptIds = dataScope.getDeptIds();
+ // 优先获取赋值数据
+ if (CollUtil.isEmpty(deptIds)) {
+ SictUser user = SecurityUtils.getUser();
+ if (user == null) {
+ throw new CheckedException("auto datascope, set up security details true");
+ }
+
+ List roleIdList = user.getAuthorities()
+ .stream().map(GrantedAuthority::getAuthority)
+ .filter(authority -> authority.startsWith(SecurityConstants.ROLE))
+ .map(authority -> authority.split("_")[1])
+ .collect(Collectors.toList());
+
+ Entity query = Db.use(dataSource)
+ .query("SELECT * FROM sys_role where role_id IN (" + CollUtil.join(roleIdList, ",") + ")")
+ .stream().min(Comparator.comparingInt(o -> o.getInt("ds_type"))).get();
+
+ Integer dsType = query.getInt("ds_type");
+ // 查询全部
+ if (DataScopeTypeEnum.ALL.getType() == dsType) {
+ return invocation.proceed();
+ }
+ // 自定义
+ if (DataScopeTypeEnum.CUSTOM.getType() == dsType) {
+ String dsScope = query.getStr("ds_scope");
+ deptIds.addAll(Arrays.stream(dsScope.split(","))
+ .map(Integer::parseInt).collect(Collectors.toList()));
+ }
+ /*
+ // 查询本级及其下级
+ if (DataScopeTypeEnum.OWN_CHILD_LEVEL.getType() == dsType) {
+ List deptIdList = Db.use(dataSource)
+ .findBy("sys_dept_relation", "ancestor", user.getDeptId())
+ .stream().map(entity -> entity.getInt("descendant"))
+ .collect(Collectors.toList());
+ deptIds.addAll(deptIdList);
+ }
+ // 只查询本级
+ if (DataScopeTypeEnum.OWN_LEVEL.getType() == dsType) {
+ deptIds.add(user.getDeptId());
+ }*/
+ }
+ String join = CollectionUtil.join(deptIds, ",");
+ originalSql = "select * from (" + originalSql + ") temp_data_scope where temp_data_scope." + scopeName + " in (" + join + ")";
+ metaObject.setValue("delegate.boundSql.sql", originalSql);
+ return invocation.proceed();
+ }
+
+ /**
+ * 生成拦截对象的代理
+ *
+ * @param target 目标对象
+ * @return 代理对象
+ */
+ @Override
+ public Object plugin(Object target) {
+ if (target instanceof StatementHandler) {
+ return Plugin.wrap(target, this);
+ }
+ return target;
+ }
+
+ /**
+ * mybatis配置的属性
+ *
+ * @param properties mybatis配置的属性
+ */
+ @Override
+ public void setProperties(Properties properties) {
+
+ }
+
+ /**
+ * 查找参数是否包括DataScope对象
+ *
+ * @param parameterObj 参数列表
+ * @return DataScope
+ */
+ private DataScope findDataScopeObject(Object parameterObj) {
+ if (parameterObj instanceof DataScope) {
+ return (DataScope) parameterObj;
+ } else if (parameterObj instanceof Map) {
+ for (Object val : ((Map, ?>) parameterObj).values()) {
+ if (val instanceof DataScope) {
+ return (DataScope) val;
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/dto/EventInfo.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/dto/EventInfo.java
new file mode 100644
index 0000000..12755db
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/dto/EventInfo.java
@@ -0,0 +1,76 @@
+package cn.sh.stc.sict.cloud.common.data.dto;
+
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author admin
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@TableName("chain_event_info")
+public class EventInfo extends Model {
+
+ @ApiModelProperty(hidden = true)
+ @TableId("`key`")
+ protected String key;
+
+ private String hospId;
+ private String doctorId;
+ private String patientId;
+ @ApiModelProperty(example = "2020-09-10 08:29:31")
+ @TableField("`time`")
+ private Date time;
+ /**
+ * 操作类型:
+ */
+ private String type;
+ /**
+ * 业务类型:知情同意书
+ */
+ private String biz;
+ /**
+ * 具体数据
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private Object data;
+
+ /**
+ * 入链状态:0-失败 1-成功
+ */
+ private Byte status;
+
+ /**
+ * 主键值
+ */
+ @Override
+ protected Serializable pkVal(){
+ return this.key;
+ }
+
+ @Override
+ public String toString() {
+ return "EventInfo{" +
+ "key='" + key + '\'' +
+ ", hospId='" + hospId + '\'' +
+ ", doctorId='" + doctorId + '\'' +
+ ", patientId='" + patientId + '\'' +
+ ", time=" + time +
+ ", type='" + type + '\'' +
+ ", biz='" + biz + '\'' +
+ ", data='" + data + '\'' +
+ '}';
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/enums/DataScopeTypeEnum.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/enums/DataScopeTypeEnum.java
new file mode 100644
index 0000000..2e5346c
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/enums/DataScopeTypeEnum.java
@@ -0,0 +1,42 @@
+package cn.sh.stc.sict.cloud.common.data.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Description 数据权限类型
+ * @Author
+ * @Date
+ */
+@Getter
+@AllArgsConstructor
+public enum DataScopeTypeEnum {
+ /**
+ * 查询全部数据
+ */
+ ALL(0, "全部"),
+
+ /**
+ * 自定义
+ */
+ CUSTOM(1, "自定义"),
+
+ /**
+ * 本级及子级
+ */
+ OWN_CHILD_LEVEL(2, "本级及子级"),
+
+ /**
+ * 本级
+ */
+ OWN_LEVEL(3, "本级");
+
+ /**
+ * 类型
+ */
+ private final int type;
+ /**
+ * 描述
+ */
+ private final String description;
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/mybatis/MyMetaObjectHandler.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/mybatis/MyMetaObjectHandler.java
new file mode 100644
index 0000000..243f5a3
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/mybatis/MyMetaObjectHandler.java
@@ -0,0 +1,89 @@
+package cn.sh.stc.sict.cloud.common.data.mybatis;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.sh.stc.sict.cloud.common.core.constant.Constant;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import cn.sh.stc.sict.cloud.common.security.service.SictUser;
+import cn.sh.stc.sict.cloud.common.security.util.SecurityUtils;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+/**
+ * @ClassName MyMetaObjectHandler
+ * @Description 自定义公共字段填充处理器
+ * @Author Y
+ * @Date 2020-01-06 18:21
+ */
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+ /**
+ * 插入操作自动填充
+ *
+ * @author:
+ * @param:
+ * @return:
+ * @date:
+ */
+ @Override
+ public void insertFill(MetaObject metaObject) {
+ Object createUserName = getFieldValByName("createUserName", metaObject);
+ Object createUserId = getFieldValByName("createUserId", metaObject);
+ Object createTime = getFieldValByName("createTime", metaObject);
+ Object isDeteled = getFieldValByName("isDeleted", metaObject);
+ Object isUse = getFieldValByName("isUse", metaObject);
+ SictUser user = SecurityUtils.getUser();
+ Date currentDate = DateUtil.date();
+ if (createUserName == null) {
+ String name = ObjectUtil.isNull(user) ? "未获取" : StrUtil.isBlank(user.getName()) ? user.getUsername() : user.getName();
+ setFieldValByName("createUserName", name, metaObject);
+ }
+ if (createUserId == null) {
+ Long userId = ObjectUtil.isNull(user) ? null : user.getId();
+ setFieldValByName("createUserId", userId, metaObject);
+ }
+ if (createTime == null) {
+ setFieldValByName("createTime", currentDate, metaObject);
+ }
+ if (isDeteled == null) {
+ setFieldValByName("isDeleted", Constant.BYTE_NO, metaObject);
+ }
+ if (isUse == null) {
+ setFieldValByName("isUse", Constant.BYTE_YES, metaObject);
+ }
+ }
+
+ /**
+ * 修改操作自动填充
+ *
+ * @author:
+ * @param:
+ * @return:
+ * @date:
+ */
+ @Override
+ public void updateFill(MetaObject metaObject) {
+ Object updateUserName = getFieldValByName("updateUserName", metaObject);
+ Object updateUserId = getFieldValByName("updateUserId", metaObject);
+ Object updateTime = getFieldValByName("updateTime", metaObject);
+ SictUser user = SecurityUtils.getUser();
+ DateTime currentDate = DateUtil.date();
+ if (updateUserName == null) {
+ String name = ObjectUtil.isNull(user) ? "未获取" : StrUtil.isBlank(user.getName()) ? user.getUsername() : user.getName();
+ setFieldValByName("updateUserName", name, metaObject);
+ }
+ if (updateUserId == null) {
+ Long userId = ObjectUtil.isNull(user) ? null : user.getId();
+ setFieldValByName("updateUserId", userId, metaObject);
+ }
+ if (updateTime == null) {
+ setFieldValByName("updateTime", currentDate, metaObject);
+ }
+ }
+
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/mybatis/MybatisPlusConfig.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/mybatis/MybatisPlusConfig.java
new file mode 100644
index 0000000..34798b7
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/mybatis/MybatisPlusConfig.java
@@ -0,0 +1,50 @@
+package cn.sh.stc.sict.cloud.common.data.mybatis;
+
+import cn.sh.stc.sict.cloud.common.data.datascope.DataScopeInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+/**
+ * @Description
+ * @Author
+ * @Date
+ */
+@Configuration
+@ConditionalOnClass(MybatisPlusConfig.class)
+@MapperScan(basePackages = {"cn.sh.stc.sict.**.dao", "com.smarthealth.themestore.*.dao"})
+public class MybatisPlusConfig {
+
+ /**
+ * 分页插件
+ *
+ * @return PaginationInterceptor
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public PaginationInterceptor paginationInterceptor() {
+ PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
+ // 设置最大单页限制数量,默认 500 条,-1 不受限制
+ // paginationInterceptor.setLimit(500);
+ // 开启 count 的 join 优化,只针对部分 left join
+ // paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
+ return paginationInterceptor;
+ }
+
+ /**
+ * 数据权限插件
+ *
+ * @param dataSource 数据源
+ * @return DataScopeInterceptor
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public DataScopeInterceptor dataScopeInterceptor(DataSource dataSource) {
+ return new DataScopeInterceptor(dataSource);
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/ConfigUtils.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/ConfigUtils.java
new file mode 100644
index 0000000..49df9c0
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/ConfigUtils.java
@@ -0,0 +1,14 @@
+package cn.sh.stc.sict.cloud.common.data.util;
+
+
+import lombok.experimental.UtilityClass;
+
+/**
+ * @Description 系统参数配置获取类
+ * @Author
+ * @Date
+ */
+@UtilityClass
+public class ConfigUtils {
+
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/HySearchWrapperUtil.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/HySearchWrapperUtil.java
new file mode 100644
index 0000000..dabbd86
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/HySearchWrapperUtil.java
@@ -0,0 +1,25 @@
+package cn.sh.stc.sict.cloud.common.data.util;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+
+public class HySearchWrapperUtil {
+
+ public static void setWrapper(LambdaQueryWrapper wrapper, String param) {
+ StringBuilder sb = new StringBuilder();
+ if (StrUtil.isNotBlank(param) && JSONUtil.isJsonObj(param)) {
+ sb.append("where 1 = 1");
+ JSONObject json = JSONUtil.parseObj(param);
+ json.forEach((k, v) -> {
+ sb.append(" and ");
+ sb.append(k);
+ sb.append(" like '%");
+ sb.append(v);
+ sb.append("%'");
+ });
+ wrapper.last(sb.toString());
+ }
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/UnionChainUtil.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/UnionChainUtil.java
new file mode 100644
index 0000000..2c57bf3
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/UnionChainUtil.java
@@ -0,0 +1,28 @@
+package cn.sh.stc.sict.cloud.common.data.util;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONUtil;
+import cn.sh.stc.sict.cloud.common.data.dto.EventInfo;
+import lombok.experimental.UtilityClass;
+
+/**
+ * 联盟链工具类
+ * @author admin
+ */
+@UtilityClass
+public class UnionChainUtil {
+
+ /**
+ * 存储一般事件
+ *
+ * @param event
+ */
+ public String saveEvent(String url, EventInfo event) {
+ String body = HttpRequest.post(url)
+ .body(JSONUtil.toJsonStr(event))
+ .execute()
+ .body();
+ System.out.println(body);
+ return body;
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/ValidateCodeUtil.java b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/ValidateCodeUtil.java
new file mode 100644
index 0000000..cedcd52
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/java/cn/sh/stc/sict/cloud/common/data/util/ValidateCodeUtil.java
@@ -0,0 +1,37 @@
+package cn.sh.stc.sict.cloud.common.data.util;
+
+import cn.hutool.core.util.StrUtil;
+import cn.sh.stc.sict.cloud.common.core.constant.RedisCacheConstant;
+import org.springframework.data.redis.core.RedisTemplate;
+
+/**
+ * @Description 验证码校验工具类
+ * @Author
+ * @Date
+ */
+public class ValidateCodeUtil {
+
+ /**
+ * 从redis取出验证码并验证
+ */
+ public static boolean validateCode(RedisTemplate redisTemplate, String code, String phone) {
+ String key = RedisCacheConstant.SICT_PHONE_CODE_KEY + phone;
+ if (!redisTemplate.hasKey(key)) {
+ return false;
+ }
+ Object codeObj = redisTemplate.opsForValue().get(key);
+
+ if (codeObj == null) {
+ return false;
+ }
+ String savedCode = codeObj.toString();
+ if (StrUtil.isBlank(savedCode)) {
+ redisTemplate.delete(key);
+ return false;
+ }
+ if (!StrUtil.equals(savedCode, code)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/cloud-common/cloud-common-data/src/main/resources/META-INF/spring.factories b/cloud-common/cloud-common-data/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..4c882b0
--- /dev/null
+++ b/cloud-common/cloud-common-data/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,6 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ cn.sh.stc.sict.cloud.common.data.cache.RedisTemplateConfig,\
+ cn.sh.stc.sict.cloud.common.data.cache.RedisCacheManagerConfig,\
+ cn.sh.stc.sict.cloud.common.data.cache.RedisCacheAutoConfiguration,\
+ cn.sh.stc.sict.cloud.common.data.mybatis.MyMetaObjectHandler,\
+ cn.sh.stc.sict.cloud.common.data.mybatis.MybatisPlusConfig
\ No newline at end of file
diff --git a/cloud-common/cloud-common-dynamic-gateway/pom.xml b/cloud-common/cloud-common-dynamic-gateway/pom.xml
new file mode 100644
index 0000000..4f0d25b
--- /dev/null
+++ b/cloud-common/cloud-common-dynamic-gateway/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ cloud-common
+ cn.sh.stc.sict
+ 1.0.0
+
+ 4.0.0
+
+ cloud-common-dynamic-gateway
+ jar
+
+
+
+
+ cn.sh.stc.sict
+ cloud-common-core
+
+
+ org.springframework.cloud
+ spring-cloud-gateway-core
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis-reactive
+
+
+
\ No newline at end of file
diff --git a/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/annotation/EnableDynamicRoute.java b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/annotation/EnableDynamicRoute.java
new file mode 100644
index 0000000..836d291
--- /dev/null
+++ b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/annotation/EnableDynamicRoute.java
@@ -0,0 +1,19 @@
+package cn.sh.stc.sict.cloud.common.dynamic.gateway.annotation;
+
+import cn.sh.stc.sict.cloud.common.dynamic.gateway.configuration.DynamicRouteAutoConfiguration;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * @Description 开启 动态路由
+ * @Author
+ * @Date
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@Import(DynamicRouteAutoConfiguration.class)
+public @interface EnableDynamicRoute {
+}
diff --git a/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/configuration/DynamicRouteAutoConfiguration.java b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/configuration/DynamicRouteAutoConfiguration.java
new file mode 100644
index 0000000..e08a3f0
--- /dev/null
+++ b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/configuration/DynamicRouteAutoConfiguration.java
@@ -0,0 +1,27 @@
+package cn.sh.stc.sict.cloud.common.dynamic.gateway.configuration;
+
+import org.springframework.cloud.gateway.config.GatewayProperties;
+import org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @Description 动态路由配置类
+ * @Author
+ * @Date
+ */
+@Configuration
+@ComponentScan("cn.sh.stc.sict.cloud.common.dynamic.gateway")
+public class DynamicRouteAutoConfiguration {
+ /**
+ * 配置文件设置为空
+ * redis 加载为准
+ *
+ * @return
+ */
+ @Bean
+ public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator() {
+ return new PropertiesRouteDefinitionLocator(new GatewayProperties());
+ }
+}
diff --git a/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/support/DynamicRouteInitEvent.java b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/support/DynamicRouteInitEvent.java
new file mode 100644
index 0000000..b79b1f4
--- /dev/null
+++ b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/support/DynamicRouteInitEvent.java
@@ -0,0 +1,14 @@
+package cn.sh.stc.sict.cloud.common.dynamic.gateway.support;
+
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @Description 路由初始化事件
+ * @Author
+ * @Date
+ */
+public class DynamicRouteInitEvent extends ApplicationEvent {
+ public DynamicRouteInitEvent(Object source) {
+ super(source);
+ }
+}
diff --git a/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/support/RedisRouteDefinitionWriter.java b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/support/RedisRouteDefinitionWriter.java
new file mode 100644
index 0000000..3bbc5e1
--- /dev/null
+++ b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/support/RedisRouteDefinitionWriter.java
@@ -0,0 +1,73 @@
+package cn.sh.stc.sict.cloud.common.dynamic.gateway.support;
+
+import cn.sh.stc.sict.cloud.common.core.constant.RedisCacheConstant;
+import cn.sh.stc.sict.cloud.common.dynamic.gateway.vo.RouteDefinitionVo;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.cloud.gateway.route.RouteDefinition;
+import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Description redis 保存路由信息,优先级比配置文件高
+ * @Author
+ * @Date
+ */
+@Slf4j
+@Component
+@AllArgsConstructor
+public class RedisRouteDefinitionWriter implements RouteDefinitionRepository {
+ private final RedisTemplate redisTemplate;
+
+ @Override
+ public Mono save(Mono route) {
+ return route.flatMap(r -> {
+ RouteDefinitionVo vo = new RouteDefinitionVo();
+ BeanUtils.copyProperties(r, vo);
+ log.info("保存路由信息{}", vo);
+ redisTemplate.setKeySerializer(new StringRedisSerializer());
+ redisTemplate.opsForHash().put(RedisCacheConstant.ROUTE_KEY, r.getId(), vo);
+ return Mono.empty();
+ });
+ }
+
+ @Override
+ public Mono delete(Mono routeId) {
+ routeId.subscribe(id -> {
+ log.info("删除路由信息{}", id);
+ redisTemplate.setKeySerializer(new StringRedisSerializer());
+ redisTemplate.opsForHash().delete(RedisCacheConstant.ROUTE_KEY, id);
+ });
+ return Mono.empty();
+ }
+
+
+ /**
+ * 动态路由入口
+ *
+ * @return
+ */
+ @Override
+ public Flux getRouteDefinitions() {
+ redisTemplate.setKeySerializer(new StringRedisSerializer());
+ redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
+ List values = redisTemplate.opsForHash().values(RedisCacheConstant.ROUTE_KEY);
+ List definitionList = new ArrayList<>();
+ values.forEach(vo -> {
+ RouteDefinition routeDefinition = new RouteDefinition();
+ BeanUtils.copyProperties(vo, routeDefinition);
+ definitionList.add(vo);
+ });
+ log.debug("redis 中路由定义条数: {}, {}", definitionList.size(), definitionList);
+ return Flux.fromIterable(definitionList);
+ }
+}
diff --git a/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/vo/RouteDefinitionVo.java b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/vo/RouteDefinitionVo.java
new file mode 100644
index 0000000..1bd3724
--- /dev/null
+++ b/cloud-common/cloud-common-dynamic-gateway/src/main/java/cn/sh/stc/sict/cloud/common/dynamic/gateway/vo/RouteDefinitionVo.java
@@ -0,0 +1,23 @@
+package cn.sh.stc.sict.cloud.common.dynamic.gateway.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.cloud.gateway.route.RouteDefinition;
+
+import java.io.Serializable;
+
+/**
+ * @Description 扩展此类支持序列化a
+ * See RouteDefinition.class
+ * @Author
+ * @Date
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class RouteDefinitionVo extends RouteDefinition implements Serializable {
+
+ /**
+ * 路由名称
+ */
+ private String routeName;
+}
diff --git a/cloud-common/cloud-common-gateway/pom.xml b/cloud-common/cloud-common-gateway/pom.xml
new file mode 100644
index 0000000..d843c1a
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/pom.xml
@@ -0,0 +1,51 @@
+
+
+
+ cloud-common
+ cn.sh.stc.sict
+ 1.0.0
+
+ 4.0.0
+
+ cloud-common-gateway
+ jar
+ cloud-common-gateway
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis-reactive
+
+
+
+ cn.sh.stc.sict
+ cloud-common-dynamic-gateway
+
+
+
+ com.github.axet
+ kaptcha
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ io.springfox
+ springfox-swagger-ui
+
+
+ io.springfox
+ springfox-swagger2
+
+
+
\ No newline at end of file
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/GatewayAutoConfiguration.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/GatewayAutoConfiguration.java
new file mode 100644
index 0000000..8793f07
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/GatewayAutoConfiguration.java
@@ -0,0 +1,12 @@
+package cn.sh.stc.sict.cloud.common.gateway;
+
+import org.springframework.context.annotation.ComponentScan;
+
+/**
+ * @Description
+ * @Author
+ * @Date
+ */
+@ComponentScan("cn.sh.stc.sict.cloud.common.gateway")
+public class GatewayAutoConfiguration {
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/FilterIgnorePropertiesConfig.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/FilterIgnorePropertiesConfig.java
new file mode 100644
index 0000000..03584b6
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/FilterIgnorePropertiesConfig.java
@@ -0,0 +1,27 @@
+package cn.sh.stc.sict.cloud.common.gateway.config;
+
+import lombok.Data;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Description 网关不校验终端配置
+ * @Author
+ * @Date
+ */
+@Data
+@Configuration
+@RefreshScope
+@ConditionalOnExpression("!'${ignore}'.isEmpty()")
+@ConfigurationProperties(prefix = "ignore")
+public class FilterIgnorePropertiesConfig {
+ private String gatewayPrefix;
+ private List clients = new ArrayList<>();
+ private List swaggerProviders = new ArrayList<>();
+ private List userNames = new ArrayList<>();
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/GlobalCorsConfig.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/GlobalCorsConfig.java
new file mode 100644
index 0000000..d6d2b4b
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/GlobalCorsConfig.java
@@ -0,0 +1,54 @@
+package cn.sh.stc.sict.cloud.common.gateway.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.cors.reactive.CorsUtils;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+/**
+ * @Description
+ * @Author
+ * @Date
+ */
+@Configuration
+public class GlobalCorsConfig {
+
+ /**
+ * 这里为支持的请求头,如果有自定义的header字段请自己添加(不知道为什么不能使用*)
+ */
+ private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,username,client,access-token";
+ private static final String ALLOWED_METHODS = "*";
+ private static final String ALLOWED_ORIGIN = "*";
+ private static final String ALLOWED_Expose = "*";
+ private static final String MAX_AGE = "18000L";
+
+ @Bean
+ public WebFilter corsFilter() {
+ return (ServerWebExchange ctx, WebFilterChain chain) -> {
+ ServerHttpRequest request = ctx.getRequest();
+ if (CorsUtils.isCorsRequest(request)) {
+ ServerHttpResponse response = ctx.getResponse();
+ HttpHeaders headers = response.getHeaders();
+ headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
+ headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
+ headers.add("Access-Control-Max-Age", MAX_AGE);
+ headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
+ headers.add("Access-Control-Expose-Headers", ALLOWED_Expose);
+ headers.add("Access-Control-Allow-Credentials", "true");
+ if (request.getMethod() == HttpMethod.OPTIONS) {
+ response.setStatusCode(HttpStatus.OK);
+ return Mono.empty();
+ }
+ }
+ return chain.filter(ctx);
+ };
+ }
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/KaptchaConfiguration.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/KaptchaConfiguration.java
new file mode 100644
index 0000000..f213b5d
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/KaptchaConfiguration.java
@@ -0,0 +1,70 @@
+package cn.sh.stc.sict.cloud.common.gateway.config;
+
+import cn.sh.stc.sict.cloud.common.core.constant.SecurityConstants;
+import com.google.code.kaptcha.impl.DefaultKaptcha;
+import com.google.code.kaptcha.util.Config;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Properties;
+
+/**
+ * @Description 验证码
+ * @Author
+ * @Date
+ */
+@Configuration
+public class KaptchaConfiguration {
+
+ private static final String KAPTCHA_BORDER = "kaptcha.border";
+ private static final String KAPTCHA_TEXTPRODUCER_FONT_COLOR = "kaptcha.textproducer.font.color";
+ private static final String KAPTCHA_TEXTPRODUCER_CHAR_SPACE = "kaptcha.textproducer.char.space";
+ private static final String KAPTCHA_IMAGE_WIDTH = "kaptcha.image.width";
+ private static final String KAPTCHA_IMAGE_HEIGHT = "kaptcha.image.height";
+ private static final String KAPTCHA_TEXTPRODUCER_CHAR_LENGTH = "kaptcha.textproducer.char.length";
+ private static final Object KAPTCHA_IMAGE_FONT_SIZE = "kaptcha.textproducer.font.size";
+
+ /**
+ * 默认生成图形验证码宽度
+ */
+ private static final String DEFAULT_IMAGE_WIDTH = "100";
+
+ /**
+ * 默认生成图像验证码高度
+ */
+ private static final String DEFAULT_IMAGE_HEIGHT = "40";
+
+ /**
+ * 边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue.
+ */
+ private static final String DEFAULT_COLOR_FONT = "black";
+
+ /**
+ * 图片边框
+ */
+ private static final String DEFAULT_IMAGE_BORDER = "no";
+ /**
+ * 默认图片间隔
+ */
+ private static final String DEFAULT_CHAR_SPACE = "5";
+ /**
+ * 验证码文字大小
+ */
+ private static final String DEFAULT_IMAGE_FONT_SIZE = "30";
+
+ @Bean
+ public DefaultKaptcha producer() {
+ Properties properties = new Properties();
+ properties.put(KAPTCHA_BORDER, DEFAULT_IMAGE_BORDER);
+ properties.put(KAPTCHA_TEXTPRODUCER_FONT_COLOR, DEFAULT_COLOR_FONT);
+ properties.put(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, DEFAULT_CHAR_SPACE);
+ properties.put(KAPTCHA_IMAGE_WIDTH, DEFAULT_IMAGE_WIDTH);
+ properties.put(KAPTCHA_IMAGE_HEIGHT, DEFAULT_IMAGE_HEIGHT);
+ properties.put(KAPTCHA_IMAGE_FONT_SIZE, DEFAULT_IMAGE_FONT_SIZE);
+ properties.put(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, SecurityConstants.CODE_SIZE);
+ Config config = new Config(properties);
+ DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
+ defaultKaptcha.setConfig(config);
+ return defaultKaptcha;
+ }
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/RateLimiterConfiguration.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/RateLimiterConfiguration.java
new file mode 100644
index 0000000..7f1f688
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/RateLimiterConfiguration.java
@@ -0,0 +1,21 @@
+package cn.sh.stc.sict.cloud.common.gateway.config;
+
+import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import reactor.core.publisher.Mono;
+
+/**
+ * @Description 路由限流配置
+ * @Author
+ * @Date
+ */
+@Configuration
+public class RateLimiterConfiguration {
+ @Primary
+ @Bean(value = "remoteAddrKeyResolver")
+ public KeyResolver remoteAddrKeyResolver() {
+ return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
+ }
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/RouterFunctionConfiguration.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/RouterFunctionConfiguration.java
new file mode 100644
index 0000000..82aacbc
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/RouterFunctionConfiguration.java
@@ -0,0 +1,54 @@
+package cn.sh.stc.sict.cloud.common.gateway.config;
+
+import cn.sh.stc.sict.cloud.common.gateway.handler.*;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.web.reactive.function.server.RequestPredicates;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+
+/**
+ * @Description 路由配置信息
+ * @Author
+ * @Date
+ */
+@Slf4j
+@Configuration
+@AllArgsConstructor
+public class RouterFunctionConfiguration {
+ private final HystrixFallbackHandler hystrixFallbackHandler;
+ private final SwaggerResourceHandler swaggerResourceHandler;
+ private final SwaggerSecurityHandler swaggerSecurityHandler;
+ private final SwaggerUiHandler swaggerUiHandler;
+ private final ImageCodeHandler imageCodeHandler;
+ private final SmsCodeHandler smsCodeHandler;
+ private final CurrentDateHandler currentDateHandler;
+
+ @Bean
+ public RouterFunction routerFunction() {
+ // 当前系统时间
+ // 图形验证码
+ // 短信验证码
+ // swagger
+ return RouterFunctions.route(
+ RequestPredicates.path("/fallback")
+ .and(RequestPredicates.accept(MediaType.APPLICATION_JSON_UTF8)), hystrixFallbackHandler)
+ .andRoute(RequestPredicates.GET("/currentDate")
+ .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), currentDateHandler)
+ .andRoute(RequestPredicates.GET("/img/code")
+ .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), imageCodeHandler)
+ .andRoute(RequestPredicates.GET("/sms/code")
+ .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), smsCodeHandler)
+ .andRoute(RequestPredicates.GET("/swagger-resources")
+ .and(RequestPredicates.accept(MediaType.ALL)), swaggerResourceHandler)
+ .andRoute(RequestPredicates.GET("/swagger-resources/configuration/ui")
+ .and(RequestPredicates.accept(MediaType.ALL)), swaggerUiHandler)
+ .andRoute(RequestPredicates.GET("/swagger-resources/configuration/security")
+ .and(RequestPredicates.accept(MediaType.ALL)), swaggerSecurityHandler);
+
+ }
+
+}
\ No newline at end of file
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/SwaggerProvider.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/SwaggerProvider.java
new file mode 100644
index 0000000..b44797e
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/config/SwaggerProvider.java
@@ -0,0 +1,54 @@
+package cn.sh.stc.sict.cloud.common.gateway.config;
+
+import lombok.AllArgsConstructor;
+import org.springframework.cloud.gateway.route.RouteDefinition;
+import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
+import org.springframework.cloud.gateway.support.NameUtils;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Component;
+import springfox.documentation.swagger.web.SwaggerResource;
+import springfox.documentation.swagger.web.SwaggerResourcesProvider;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @Description 聚合接口文档注册,和zuul实现类似
+ * @Author
+ * @Date
+ */
+@Component
+@Primary
+@AllArgsConstructor
+public class SwaggerProvider implements SwaggerResourcesProvider {
+ private static final String API_URI = "/v2/api-docs";
+ private final RouteDefinitionRepository routeDefinitionRepository;
+ private final FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;
+
+
+ @Override
+ public List get() {
+ List resources = new ArrayList<>();
+ List routes = new ArrayList<>();
+ routeDefinitionRepository.getRouteDefinitions().subscribe(route -> routes.add(route));
+ routes.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
+ .filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName()))
+ .filter(predicateDefinition -> !filterIgnorePropertiesConfig.getSwaggerProviders().contains(routeDefinition.getId()))
+ .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
+ predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
+ .replace("/**", API_URI)))));
+
+ return resources.stream().sorted(Comparator.comparing(SwaggerResource::getName))
+ .collect(Collectors.toList());
+ }
+
+ private SwaggerResource swaggerResource(String name, String location) {
+ SwaggerResource swaggerResource = new SwaggerResource();
+ swaggerResource.setName(name);
+ swaggerResource.setLocation(location);
+ swaggerResource.setSwaggerVersion("2.0");
+ return swaggerResource;
+ }
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/HttpBasicGatewayFilter.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/HttpBasicGatewayFilter.java
new file mode 100644
index 0000000..058aa4b
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/HttpBasicGatewayFilter.java
@@ -0,0 +1,48 @@
+package cn.sh.stc.sict.cloud.common.gateway.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * @Description 自定义basic认证,针对特殊场景使用
+ * @Author
+ * @Date
+ */
+@Slf4j
+@Component
+public class HttpBasicGatewayFilter extends AbstractGatewayFilterFactory {
+ @Override
+ public GatewayFilter apply(Object config) {
+ return (exchange, chain) -> {
+ if (hasAuth(exchange)) {
+ return chain.filter(exchange);
+ } else {
+ ServerHttpResponse response = exchange.getResponse();
+ response.setStatusCode(HttpStatus.UNAUTHORIZED);
+ response.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE, "Basic Realm=\"sict\"");
+ return response.setComplete();
+ }
+ };
+ }
+
+ /**
+ * 简单的basic认证
+ *
+ * @param exchange 上下文
+ * @return 是否有权限
+ */
+ private boolean hasAuth(ServerWebExchange exchange) {
+ ServerHttpRequest request = exchange.getRequest();
+ String auth = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+ log.info("Basic认证信息为:{}", auth);
+ return true;
+ }
+
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/PasswordDecoderFilter.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/PasswordDecoderFilter.java
new file mode 100644
index 0000000..45cda3d
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/PasswordDecoderFilter.java
@@ -0,0 +1,113 @@
+package cn.sh.stc.sict.cloud.common.gateway.filter;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.Mode;
+import cn.hutool.crypto.Padding;
+import cn.hutool.crypto.symmetric.AES;
+import cn.hutool.http.HttpUtil;
+import cn.sh.stc.sict.cloud.common.core.constant.SecurityConstants;
+import cn.sh.stc.sict.cloud.common.core.util.CryptUtil;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.util.UriComponentsBuilder;
+import reactor.core.publisher.Mono;
+
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+/**
+ * @Description 密码解密工具类
+ * @Author
+ * @Date
+ */
+@Slf4j
+@Component
+public class PasswordDecoderFilter extends AbstractGatewayFilterFactory {
+ private static final String PASSWORD = "password";
+ private static final String USERNAME = "username";
+ private static final String KEY_ALGORITHM = "AES";
+ @Value("${security.encode.key:1qaz@WSX3edc$RFV}")
+ private String encodeKey;
+
+ @SneakyThrows
+ private static String decryptAES(String data, String pass) {
+ AES aes = new AES(Mode.CBC, Padding.NoPadding,
+ new SecretKeySpec(pass.getBytes(), KEY_ALGORITHM),
+ new IvParameterSpec(pass.getBytes()));
+ byte[] result = aes.decrypt(Base64.decode(data.getBytes(StandardCharsets.UTF_8)));
+ return new String(result, StandardCharsets.UTF_8);
+ }
+
+ @SneakyThrows
+ private static String encryptAES(String data, String pass) {
+ AES aes = new AES(Mode.CBC, Padding.NoPadding,
+ new SecretKeySpec(pass.getBytes(), KEY_ALGORITHM),
+ new IvParameterSpec(pass.getBytes()));
+ byte[] result = aes.encrypt(Base64.decode(data.getBytes(StandardCharsets.UTF_8)));
+ return new String(result, StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public GatewayFilter apply(Object config) {
+ return (exchange, chain) -> {
+ ServerHttpRequest request = exchange.getRequest();
+
+ // 不是登录请求,直接向下执行
+ if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) {
+ return chain.filter(exchange);
+ }
+
+ URI uri = exchange.getRequest().getURI();
+ log.error("uri ==============>: {}", uri.toString());
+ String queryParam = uri.getRawQuery();
+ log.error("queryParam==============>: {}", queryParam);
+ Map paramMap = HttpUtil.decodeParamMap(queryParam, CharsetUtil.UTF_8);
+ log.error("paramMap==============>: {}", paramMap.toString());
+
+ String password = paramMap.get(PASSWORD);
+ if (StrUtil.isNotBlank(password)) {
+ try {
+ // password = decryptAES(password, encodeKey);
+ log.error("before password: {}", password);
+ password = CryptUtil.AESToString(password, encodeKey);
+ log.error("after password: {}", password);
+ } catch (Exception e) {
+ log.error("密码解密失败:password = {}", password);
+ return Mono.error(e);
+ }
+ paramMap.put(PASSWORD, password.trim());
+ }
+ String username = paramMap.get(USERNAME);
+ if (StrUtil.isNotBlank(username)) {
+ try {
+ // password = decryptAES(password, encodeKey);
+ log.error("before username: {}", username);
+ username = CryptUtil.AESToString(username, encodeKey);
+ log.error("after username: {}", username);
+ } catch (Exception e) {
+ log.error("用户名解密失败:username = {}", username);
+ return Mono.error(e);
+ }
+ paramMap.put(USERNAME, username.trim());
+ }
+
+ URI newUri = UriComponentsBuilder.fromUri(uri)
+ .replaceQuery(HttpUtil.toParams(paramMap))
+ .build(true)
+ .toUri();
+
+ ServerHttpRequest newRequest = exchange.getRequest().mutate().uri(newUri).build();
+ return chain.filter(exchange.mutate().request(newRequest).build());
+ };
+ }
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/PreviewGatewayFilter.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/PreviewGatewayFilter.java
new file mode 100644
index 0000000..14133d6
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/PreviewGatewayFilter.java
@@ -0,0 +1,40 @@
+package cn.sh.stc.sict.cloud.common.gateway.filter;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Description 演示环境过滤处理
+ * @Author
+ * @Date
+ */
+@Slf4j
+@Component
+public class PreviewGatewayFilter extends AbstractGatewayFilterFactory {
+ private static final String TOKEN = "token";
+
+ @Override
+ public GatewayFilter apply(Object config) {
+ return (exchange, chain) -> {
+ ServerHttpRequest request = exchange.getRequest();
+
+ // GET,直接向下执行
+ if (StrUtil.equalsIgnoreCase(request.getMethodValue(), HttpMethod.GET.name()) ||
+ StrUtil.containsIgnoreCase(request.getURI().getPath(), TOKEN)) {
+ return chain.filter(exchange);
+ }
+
+ log.warn("演示环境不能操作-> {},{}", request.getMethodValue(), request.getURI().getPath());
+ ServerHttpResponse response = exchange.getResponse();
+ response.setStatusCode(HttpStatus.LOCKED);
+ return response.setComplete();
+ };
+ }
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/RequestGlobalFilter.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/RequestGlobalFilter.java
new file mode 100644
index 0000000..a8f8651
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/RequestGlobalFilter.java
@@ -0,0 +1,69 @@
+package cn.sh.stc.sict.cloud.common.gateway.filter;
+
+import cn.sh.stc.sict.cloud.common.core.constant.SecurityConstants;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
+import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
+
+/**
+ * @Description
+ *
+ * 全局拦截器,作用所有的微服务
+ *
+ * 1. 对请求头中参数进行处理 from 参数进行清洗
+ * 2. 重写StripPrefix = 1,支持全局
+ *
+ * 支持swagger添加X-Forwarded-Prefix header (F SR2 已经支持,不需要自己维护)
+ * @Author
+ * @Date
+ */
+@Component
+public class RequestGlobalFilter implements GlobalFilter, Ordered {
+ private static final String HEADER_NAME = "X-Forwarded-Prefix";
+
+ /**
+ * Process the Web request and (optionally) delegate to the next
+ * {@code WebFilter} through the given {@link GatewayFilterChain}.
+ *
+ * @param exchange the current server exchange
+ * @param chain provides a way to delegate to the next filter
+ * @return {@code Mono} to indicate when request processing is complete
+ */
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ // 1. 清洗请求头中from 参数
+ ServerHttpRequest request = exchange.getRequest().mutate()
+ .headers(httpHeaders -> {httpHeaders.remove(SecurityConstants.FROM);})
+ .build();
+
+ // 2. 重写StripPrefix
+ addOriginalRequestUrl(exchange, request.getURI());
+ String rawPath = request.getURI().getRawPath();
+ String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/"))
+ .skip(1L).collect(Collectors.joining("/"));
+ ServerHttpRequest newRequest = request.mutate()
+ .path(newPath)
+ .build();
+ exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
+
+ return chain.filter(exchange.mutate()
+ .request(newRequest.mutate()
+ .build()).build());
+ }
+
+ @Override
+ public int getOrder() {
+ return -1000;
+ }
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/ValidateCodeGatewayFilter.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/ValidateCodeGatewayFilter.java
new file mode 100644
index 0000000..498c316
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/ValidateCodeGatewayFilter.java
@@ -0,0 +1,149 @@
+package cn.sh.stc.sict.cloud.common.gateway.filter;
+
+import cn.hutool.core.util.StrUtil;
+import cn.sh.stc.sict.cloud.common.core.constant.Constant;
+import cn.sh.stc.sict.cloud.common.core.constant.SecurityConstants;
+import cn.sh.stc.sict.cloud.common.core.constant.enums.LoginTypeEnum;
+import cn.sh.stc.sict.cloud.common.core.exception.ValidateCodeException;
+import cn.sh.stc.sict.cloud.common.core.util.R;
+import cn.sh.stc.sict.cloud.common.core.util.WebUtils;
+import cn.sh.stc.sict.cloud.common.gateway.config.FilterIgnorePropertiesConfig;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+/**
+ * @Description 验证码处理
+ * @Author
+ * @Date
+ */
+@Slf4j
+@Component
+@AllArgsConstructor
+public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
+ private final ObjectMapper objectMapper;
+ private final FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;
+ // private final SmsSendUtil smsSendUtil;
+
+ @Override
+ public GatewayFilter apply(Object config) {
+ return (exchange, chain) -> {
+ ServerHttpRequest request = exchange.getRequest();
+
+ // 不是登录请求,直接向下执行
+ if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath()
+ , SecurityConstants.OAUTH_TOKEN_URL, SecurityConstants.SMS_TOKEN_URL
+ , SecurityConstants.SOCIAL_TOKEN_URL)) {
+ return chain.filter(exchange);
+ }
+
+ // 刷新token,直接向下执行
+ String grantType = request.getQueryParams().getFirst("grant_type");
+ if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
+ return chain.filter(exchange);
+ }
+
+ // 终端设置不校验, 直接向下执行
+ try {
+ String[] clientInfos = WebUtils.getClientId(request);
+ if (filterIgnorePropertiesConfig.getClients().contains(clientInfos[0])) {
+ return chain.filter(exchange);
+ }
+
+ // wechat
+ if (StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.SOCIAL_TOKEN_URL)) {
+ String mobile = request.getQueryParams().getFirst("mobile");
+ if (StrUtil.containsAny(mobile, LoginTypeEnum.SMS.getType())) {
+ throw new ValidateCodeException("验证码不合法");
+ } else {
+ return chain.filter(exchange);
+ }
+ }
+
+ // sms
+ if (StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.SMS_TOKEN_URL)) {
+ String mobile = request.getQueryParams().getFirst("mobile");
+ if (StrUtil.containsAny(mobile, LoginTypeEnum.SMS.getType())) {
+ String code = request.getQueryParams().getFirst("code");
+ mobile = mobile.split("@")[1];
+// if(!filterIgnorePropertiesConfig.getUserNames().contains(mobile) && !smsSendUtil.checkCode(mobile, 5, code)){
+// throw new ValidateCodeException("验证码错误!");
+// }
+ return chain.filter(exchange);
+ } else {
+ throw new ValidateCodeException("登录参数不合法");
+ }
+ }
+
+ //校验验证码
+ // checkCode(request);
+ } catch (Exception e) {
+ ServerHttpResponse response = exchange.getResponse();
+ response.setStatusCode(HttpStatus.PRECONDITION_REQUIRED);
+ response.getHeaders().set("Content-type", "application/json; charset=utf-8");
+ try {
+ return response.writeWith(Mono.just(response.bufferFactory()
+ .wrap(objectMapper.writeValueAsBytes(
+ R.builder().msg(e.getMessage())
+ .code(Constant.BYTE_NO).build()))));
+ } catch (JsonProcessingException e1) {
+ log.error("对象输出异常", e1);
+ }
+ }
+
+ return chain.filter(exchange);
+ };
+ }
+
+ /**
+ * 检查code
+ *
+ * @param request
+ */
+ @SneakyThrows
+ private void checkCode(ServerHttpRequest request) {
+// String code = request.getQueryParams().getFirst("code");
+//
+// if (StrUtil.isBlank(code)) {
+// throw new ValidateCodeException("验证码不能为空");
+// }
+//
+// String randomStr = request.getQueryParams().getFirst("randomStr");
+// if (StrUtil.isBlank(randomStr)) {
+// randomStr = request.getQueryParams().getFirst("mobile");
+// }
+
+// String key = CommonConstants.DEFAULT_CODE_KEY + randomStr;
+// if (!redisTemplate.hasKey(key)) {
+// throw new ValidateCodeException("验证码不合法");
+// }
+//
+// Object codeObj = redisTemplate.opsForValue().get(key);
+//
+// if (codeObj == null) {
+// throw new ValidateCodeException("验证码不合法");
+// }
+//
+// String saveCode = codeObj.toString();
+// if (StrUtil.isBlank(saveCode)) {
+// redisTemplate.delete(key);
+// throw new ValidateCodeException("验证码不合法");
+// }
+//
+// if (!StrUtil.equals(saveCode, code)) {
+// redisTemplate.delete(key);
+// throw new ValidateCodeException("验证码不合法");
+// }
+//
+// redisTemplate.delete(key);
+ }
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/handler/CurrentDateHandler.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/handler/CurrentDateHandler.java
new file mode 100644
index 0000000..54c53e8
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/handler/CurrentDateHandler.java
@@ -0,0 +1,36 @@
+package cn.sh.stc.sict.cloud.common.gateway.handler;
+
+import cn.hutool.core.date.DateUtil;
+import cn.sh.stc.sict.cloud.common.core.constant.Constant;
+import cn.sh.stc.sict.cloud.common.core.util.R;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.HandlerFunction;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+/**
+ * @Description 当前时间获取[部分业务需要校准时间]
+ * @Author
+ * @Date
+ */
+@Slf4j
+@Component
+@AllArgsConstructor
+public class CurrentDateHandler implements HandlerFunction {
+
+ @Override
+ public Mono handle(ServerRequest serverRequest) {
+
+ // 转换流信息写出
+ return ServerResponse
+ .status(HttpStatus.OK)
+ .contentType(MediaType.APPLICATION_JSON_UTF8)
+ .body(BodyInserters.fromObject(new R().setCode(Constant.BYTE_YES ).setMsg("操作成功!").setData(DateUtil.now())));
+ }
+}
diff --git a/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/handler/HystrixFallbackHandler.java b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/handler/HystrixFallbackHandler.java
new file mode 100644
index 0000000..71047b9
--- /dev/null
+++ b/cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/handler/HystrixFallbackHandler.java
@@ -0,0 +1,40 @@
+package cn.sh.stc.sict.cloud.common.gateway.handler;
+
+import cn.sh.stc.sict.cloud.common.core.constant.Constant;
+import cn.sh.stc.sict.cloud.common.core.util.R;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.HandlerFunction;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Mono;
+
+import java.util.Optional;
+
+import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
+
+/**
+ * @Description Hystrix 降级处理
+ * @Author
+ * @Date
+ */
+@Slf4j
+@Component
+public class HystrixFallbackHandler implements HandlerFunction {
+ @Override
+ public Mono handle(ServerRequest serverRequest) {
+ Optional