Commit e43d6c29 authored by lijun's avatar lijun

init project

parents
# 开发组IDE 编辑器标准 V2.1.0
root = true
[*.{adoc, bat, groovy, html, java, js, jsp, kt, kts, md, properties, py, rb, sh, sql, svg, txt, xml, xsd}]
charset = utf-8
[*.{groovy, java, kt, kts, xml, xsd}]
indent_style = tab
indent_size = 4
continuation_indent_size = 8
end_of_line = lf
[*.{js, html}]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# 忽略匹配下列规则的Git 提交 V2.1.0
### gradle ###
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
### STS ###
.settings/
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
*.lock
rebel.xml
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
### maven ###
target/
*.war
*.ear
*.zip
*.tar
*.tar.gz
### logs ####
/logs/
*.log
### temp ignore ###
*.cache
*.diff
*.patch
*.tmp
*.java~
*.properties~
*.xml~
### system ignore ###
.DS_Store
Thumbs.db
Servers
.metadata
upload
gen_code
### node ###
node_modules
# 使用说明 V3.2.0
# 1. 使用docker-compose 宿主机不需要配置host来发现
# 2. 无需修改源码,根目录 docker-compose up 即可
# 3. 静静等待服务启动
version: '2'
services:
smart-mysql:
build:
context: ./
dockerfile: ./db/Dockerfile
environment:
MYSQL_ROOT_PASSWORD: root
restart: always
container_name: smart-mysql
image: smart-mysql
ports:
- 3306:3306
volumes:
- ./smart-mysql:/var/lib/mysql
command: --lower_case_table_names=1
smart-redis:
image: redis:5.0.4
restart: always
container_name: smart-redis
ports:
- 6379:6379
smart-minio:
image: minio/minio
command: server /data
ports:
- 9000:9000
volumes:
- ./data:/data
- ./config:/root/.minio
environment:
- MINIO_DOMAIN=smart-minio
- MINIO_ACCESS_KEY=giaogiao
- MINIO_SECRET_KEY=giaogiao
smart-gateway:
build:
context: ./
dockerfile: ./smart-hospital-gateway/Dockerfile
restart: always
container_name: smart-hospital-gateway
image: smart-hospital-gateway
ports:
- 9999:9999
smart-auth:
build:
context: ./
dockerfile: ./smart-auth/Dockerfile
restart: always
container_name: smart-auth
image: smart-auth
smart-upms:
build:
context: ./
dockerfile: ./smart-upms/smart-upms-biz/Dockerfile
restart: always
container_name: smart-upms
image: smart-upms
smart-pay:
build:
context: ./
dockerfile: ./smart-hospital-visual/smart-pay/Dockerfile
restart: always
image: smart-pay
container_name: smart-pay
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-hospital</artifactId>
<version>3.3.0</version>
<name>${project.artifactId}</name>
<packaging>pom</packaging>
<properties>
<spring-boot.version>2.1.7.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
<spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
<spring-platform.version>Cairo-SR8</spring-platform.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot-admin.version>2.1.6</spring-boot-admin.version>
<hutool.version>4.6.3</hutool.version>
<kaptcha.version>0.0.9</kaptcha.version>
<swagger.fox.version>2.9.2</swagger.fox.version>
<swagger-bootstrap-ui.version>1.9.5</swagger-bootstrap-ui.version>
<curator.version>2.10.0</curator.version>
<velocity.version>1.7</velocity.version>
<lcn.version>4.1.0</lcn.version>
<jasypt.version>2.1.1</jasypt.version>
<ttl.version>2.10.2</ttl.version>
<minio.version>6.0.8</minio.version>
<elastic-job-lite.version>2.1.5</elastic-job-lite.version>
<security.oauth.version>2.3.6.RELEASE</security.oauth.version>
<activiti.version>5.22.0</activiti.version>
<docker.url>http://192.168.0.13:4243</docker.url>
<docker.plugin.version>0.4.12</docker.plugin.version>
<registry.url>192.168.0.13:5000</registry.url>
<mapstruct.version>1.3.1.Final</mapstruct.version>
</properties>
<dependencies>
<!--配置文件处理器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--jasypt配置文件加解密-->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>${jasypt.version}</version>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--监控客户端-->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring-boot-admin.version}</version>
</dependency>
<!--断路器依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<modules>
<module>smart-hospital-gateway</module>
<module>smart-hospital-auth</module>
<module>smart-hospital-upms</module>
<module>smart-hospital-common</module>
<module>smart-hospital-service</module>
<module>smart-hospital-visual</module>
<module>smart-hospital-register</module>
<module>smart-hospital-pay</module>
<module>smart-hospital-consult</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>${spring-platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>smart-common-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--稳定版本,替代spring security bom内置-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${security.oauth.version}</version>
</dependency>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
<exclusions>
<!--排除tomcat依赖-->
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.name}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>${docker.plugin.version}</version>
<configuration>
<imageName>${registry.url}/${project.name}:0.0.1</imageName>
<dockerHost>${docker.url}</dockerHost>
<dockerDirectory>${project.basedir}</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
<serverId>docker-hub</serverId>
<registryUrl>https://index.docker.io/v1/</registryUrl>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<target>${maven.compiler.target}</target>
<source>${maven.compiler.source}</source>
<encoding>UTF-8</encoding>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>2.2.5</version>
</plugin>
</plugins>
</build>
<repositories>
<!--阿里云私服-->
<repository>
<id>aliyun</id>
<name>aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</repository>
<!--gitee 私服-->
<repository>
<id>gitee.wang</id>
<name>gitee.wang</name>
<url>http://nexus.gitee.wang/repository/maven-releases/</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitee.wang</id>
<name>gitee.wang</name>
<url>http://nexus.gitee.wang/repository/maven-releases/</url>
</repository>
</distributionManagement>
<profiles>
<profile>
<id>dev</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>dev</profiles.active>
</properties>
<activation>
<!-- 默认环境 -->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>test</id>
<properties>
<profiles.active>test</profiles.active>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<profiles.active>prod</profiles.active>
</properties>
</profile>
</profiles>
</project>
FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER wangiegie@gmail.com
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN mkdir -p /pigx-auth
WORKDIR /pigx-auth
EXPOSE 3000
ADD ./pigx-auth/target/pigx-auth.jar ./
CMD sleep 120;java -Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom -jar pigx-auth.jar
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-hospital</artifactId>
<version>3.3.0</version>
</parent>
<artifactId>smart-hospital-auth</artifactId>
<packaging>jar</packaging>
<dependencies>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--配置中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--upms api、model 模块-->
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-upms-api</artifactId>
</dependency>
<!--security-->
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-security</artifactId>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--缓存操作-->
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-data</artifactId>
</dependency>
<!--JDBC相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--freemarker-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--undertow容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
package com.smart.hospital.auth;
import com.smart.hospital.common.security.annotation.EnableSmartFeignClients;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
/**
* @author giaogiao
* @date 2018年06月21日
* 认证授权中心
*/
@SpringCloudApplication
@EnableSmartFeignClients
public class SmartAuthApplication {
public static void main(String[] args) {
SpringApplication.run(SmartAuthApplication.class, args);
}
}
package com.smart.hospital.auth.config;
import cn.hutool.core.util.StrUtil;
import com.smart.hospital.auth.endpoint.SmartRedisAuthorizationCodeService;
import com.smart.hospital.common.core.constant.SecurityConstants;
import com.smart.hospital.common.data.tenant.TenantContextHolder;
import com.smart.hospital.common.security.component.SmartWebResponseExceptionTranslator;
import com.smart.hospital.common.security.service.SmartClientDetailsService;
import com.smart.hospital.common.security.service.SmartUser;
import com.smart.hospital.common.security.service.SmartUserDetailsService;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @author giaogiao
* @date 2018/6/22
* 认证服务器配置
*/
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final DataSource dataSource;
private final SmartUserDetailsService smartUserDetailsService;
private final AuthenticationManager authenticationManagerBean;
private final RedisConnectionFactory redisConnectionFactory;
@Override
@SneakyThrows
public void configure(ClientDetailsServiceConfigurer clients) {
SmartClientDetailsService clientDetailsService = new SmartClientDetailsService(dataSource);
clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
.allowFormAuthenticationForClients()
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancer())
.userDetailsService(smartUserDetailsService)
// 自定义的code生成
.authorizationCodeServices(smartRedisAuthorizationCodeService())
.authenticationManager(authenticationManagerBean)
.reuseRefreshTokens(false)
// 确认授权页面
.pathMapping("/oauth/confirm_access", "/token/confirm_access")
.exceptionTranslator(new SmartWebResponseExceptionTranslator());
}
@Bean
public TokenStore tokenStore() {
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
tokenStore.setPrefix(SecurityConstants.SMART_PREFIX + SecurityConstants.OAUTH_PREFIX);
tokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() {
@Override
public String extractKey(OAuth2Authentication authentication) {
return super.extractKey(authentication) + StrUtil.COLON + TenantContextHolder.getTenantId();
}
});
return tokenStore;
}
/**
* token增强,客户端模式不增强。
*
* @return TokenEnhancer
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
if (SecurityConstants.CLIENT_CREDENTIALS
.equals(authentication.getOAuth2Request().getGrantType())) {
return accessToken;
}
final Map<String, Object> additionalInfo = new HashMap<>(8);
SmartUser smartUser = (SmartUser) authentication.getUserAuthentication().getPrincipal();
additionalInfo.put(SecurityConstants.DETAILS_USER_ID, smartUser.getId());
additionalInfo.put(SecurityConstants.DETAILS_USERNAME, smartUser.getUsername());
additionalInfo.put(SecurityConstants.DETAILS_DEPT_ID, smartUser.getDeptId());
additionalInfo.put(SecurityConstants.DETAILS_TENANT_ID, smartUser.getTenantId());
additionalInfo.put(SecurityConstants.DETAILS_LICENSE, SecurityConstants.SMART_LICENSE);
additionalInfo.put(SecurityConstants.ACTIVE, Boolean.TRUE);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
};
}
/**
* 自定义生成授权码模式的code
*
* @return
*/
@Bean
@Primary
protected AuthorizationCodeServices smartRedisAuthorizationCodeService() {
return new SmartRedisAuthorizationCodeService();
}
}
package com.smart.hospital.auth.config;
import com.smart.hospital.common.security.handler.FormAuthenticationFailureHandler;
import com.smart.hospital.common.security.handler.MobileLoginSuccessHandler;
import com.smart.hospital.common.security.mobile.MobileSecurityConfigurer;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
/**
* @author giaogiao
* @date 2018/6/22
* 认证相关配置
*/
@Primary
@Order(90)
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
@SneakyThrows
protected void configure(HttpSecurity http) {
http
.formLogin()
.loginPage("/token/login")
.loginProcessingUrl("/token/form")
.failureHandler(authenticationFailureHandler())
.and()
.authorizeRequests()
.antMatchers(
"/token/**",
"/actuator/**",
"/mobile/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable()
.apply(mobileSecurityConfigurer());
}
/**
* 不拦截静态资源
*
* @param web
*/
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/css/**");
}
@Bean
@Override
@SneakyThrows
public AuthenticationManager authenticationManagerBean() {
return super.authenticationManagerBean();
}
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new FormAuthenticationFailureHandler();
}
@Bean
public AuthenticationSuccessHandler mobileLoginSuccessHandler() {
return new MobileLoginSuccessHandler();
}
@Bean
public MobileSecurityConfigurer mobileSecurityConfigurer() {
return new MobileSecurityConfigurer();
}
/**
* https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated
* Encoded password does not look like BCrypt
*
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
package com.smart.hospital.auth.endpoint;
import cn.hutool.core.util.IdUtil;
import com.smart.hospital.common.core.constant.CacheConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
/**
* 自定义生成授权码模式的code的实现类
* 并通过redis存储
*/
@Slf4j
public class SmartRedisAuthorizationCodeService implements AuthorizationCodeServices {
@Autowired
private RedisTemplate redisTemplate;
@Override
public String createAuthorizationCode(OAuth2Authentication authentication) {
// 1.首先生成code
String uuidCode = IdUtil.fastUUID();
log.info("生成的授权code为:{}", uuidCode);
// 存储在redis中
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.opsForValue().set(CacheConstants.OAUTH_CODE + uuidCode, authentication);
return uuidCode;
}
@Override
public OAuth2Authentication consumeAuthorizationCode(String code) throws InvalidGrantException {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
OAuth2Authentication authentication = (OAuth2Authentication) redisTemplate.opsForValue().get(CacheConstants.OAUTH_CODE + code);
log.info("通过code获取到的auth2Authentication为:{}", authentication);
if (authentication == null) {
throw new InvalidGrantException("Invalid authorization code: " + code);
}
return authentication;
}
}
package com.smart.hospital.auth.endpoint;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.smart.hospital.common.core.constant.CacheConstants;
import com.smart.hospital.common.core.constant.PaginationConstants;
import com.smart.hospital.common.core.constant.SecurityConstants;
import com.smart.hospital.common.core.util.R;
import com.smart.hospital.common.data.tenant.TenantContextHolder;
import com.smart.hospital.common.security.annotation.Inner;
import com.smart.hospital.common.security.util.SecurityUtils;
import lombok.AllArgsConstructor;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author giaogiao
* @date 2018/6/24
* 删除token端点
*/
@RestController
@AllArgsConstructor
@RequestMapping("/token")
public class SmartTokenEndpoint {
private static final String PIGX_OAUTH_ACCESS = SecurityConstants.SMART_PREFIX + SecurityConstants.OAUTH_PREFIX + "auth_to_access:";
private final ClientDetailsService clientDetailsService;
private final RedisTemplate redisTemplate;
private final TokenStore tokenStore;
private final CacheManager cacheManager;
/**
* 认证页面
*
* @param modelAndView
* @param error 表单登录失败处理回调的错误信息
* @return ModelAndView
*/
@GetMapping("/login")
public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
modelAndView.setViewName("ftl/login");
modelAndView.addObject("error", error);
return modelAndView;
}
/**
* 确认授权页面
*
* @param request
* @param session
* @param modelAndView
* @return
*/
@GetMapping("/confirm_access")
public ModelAndView confirm(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) {
Map<String, Object> scopeList = (Map<String, Object>) request.getAttribute("scopes");
modelAndView.addObject("scopeList", scopeList.keySet());
Object auth = session.getAttribute("authorizationRequest");
if (auth != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) auth;
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
modelAndView.addObject("app", clientDetails.getAdditionalInformation());
modelAndView.addObject("user", SecurityUtils.getUser());
}
modelAndView.setViewName("ftl/confirm");
return modelAndView;
}
/**
* 退出token
*
* @param authHeader Authorization
*/
@DeleteMapping("/logout")
public R logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
if (StrUtil.isBlank(authHeader)) {
return R.ok(Boolean.FALSE, "退出失败,token 为空");
}
String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim();
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
if (accessToken == null || StrUtil.isBlank(accessToken.getValue())) {
return R.ok(Boolean.TRUE, "退出失败,token 无效");
}
OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(accessToken);
// 清空用户信息
cacheManager.getCache(CacheConstants.USER_DETAILS)
.evict(auth2Authentication.getName());
// 清空access token
tokenStore.removeAccessToken(accessToken);
// 清空 refresh token
OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
return R.ok(Boolean.TRUE);
}
/**
* 令牌管理调用
*
* @param token token
* @return
*/
@Inner
@DeleteMapping("/{token}")
public R<Boolean> delToken(@PathVariable("token") String token) {
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
tokenStore.removeAccessToken(oAuth2AccessToken);
return new R<>();
}
/**
* 查询token
*
* @param params 分页参数
* @return
*/
@Inner
@PostMapping("/page")
public R<Page> tokenList(@RequestBody Map<String, Object> params) {
//根据分页参数获取对应数据
String key = String.format("%s*:%s", PIGX_OAUTH_ACCESS, TenantContextHolder.getTenantId());
List<String> pages = findKeysForPage(key, MapUtil.getInt(params, PaginationConstants.CURRENT)
, MapUtil.getInt(params, PaginationConstants.SIZE));
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
Page result = new Page(MapUtil.getInt(params, PaginationConstants.CURRENT), MapUtil.getInt(params, PaginationConstants.SIZE));
result.setRecords(redisTemplate.opsForValue().multiGet(pages));
result.setTotal(Long.valueOf(redisTemplate.keys(key).size()));
return R.ok(result);
}
private List<String> findKeysForPage(String patternKey, int pageNum, int pageSize) {
ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection(redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
List<String> result = new ArrayList<>();
int tmpIndex = 0;
int startIndex = (pageNum - 1) * pageSize;
int end = pageNum * pageSize;
assert cursor != null;
while (cursor.hasNext()) {
if (tmpIndex >= startIndex && tmpIndex < end) {
result.add(cursor.next().toString());
tmpIndex++;
continue;
}
if (tmpIndex >= end) {
break;
}
tmpIndex++;
cursor.next();
}
return result;
}
}
package com.smart.hospital.auth.handler;
import com.smart.hospital.common.security.handler.AbstractAuthenticationFailureEvenHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
/**
* @author giaogiao
* @date 2018/10/8
*/
@Slf4j
@Component
public class SmartAuthenticationFailureEvenHandler extends AbstractAuthenticationFailureEvenHandler {
/**
* 处理登录失败方法
* <p>
*
* @param authenticationException 登录的authentication 对象
* @param authentication 登录的authenticationException 对象
*/
@Override
public void handle(AuthenticationException authenticationException, Authentication authentication) {
log.info("用户:{} 登录失败,异常:{}", authentication.getPrincipal(), authenticationException.getLocalizedMessage());
}
}
package com.smart.hospital.auth.handler;
import com.smart.hospital.common.security.handler.AbstractAuthenticationSuccessEventHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
/**
* @author giaogiao
* @date 2018/10/8
*/
@Slf4j
@Component
public class SmartAuthenticationSuccessEventHandler extends AbstractAuthenticationSuccessEventHandler {
/**
* 处理登录成功方法
* <p>
* 获取到登录的authentication 对象
*
* @param authentication 登录对象
*/
@Override
public void handle(Authentication authentication) {
log.info("用户:{} 登录成功", authentication.getPrincipal());
}
}
server:
port: 3000
spring:
application:
name: @artifactId@
cloud:
nacos:
discovery:
server-addr: ${NACOS-HOST:smart-register}:${NACOS-PORT:8848}
namespace: c75c0cb5-da84-417f-838c-27b53a39e1dc
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yml
shared-dataids: application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: c75c0cb5-da84-417f-838c-27b53a39e1dc
profiles:
active: @profiles.active@
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2018-2025, giaogiao All rights reserved.
~
~ Redistribution and use in source and binary forms, with or without
~ modification, are permitted provided that the following conditions are met:
~
~ Redistributions of source code must retain the above copyright notice,
~ this list of conditions and the following disclaimer.
~ Redistributions in binary form must reproduce the above copyright
~ notice, this list of conditions and the following disclaimer in the
~ documentation and/or other materials provided with the distribution.
~ Neither the name of the pig4cloud.com developer nor the names of its
~ contributors may be used to endorse or promote products derived from
~ this software without specific prior written permission.
~ Author: giaogiao (wangiegie@gmail.com)
-->
<!--
小技巧: 在根pom里面设置统一存放路径,统一管理方便维护
<properties>
<log-path>/Users/giaogiao</log-path>
</properties>
1. 其他模块加日志输出,直接copy本文件放在resources 目录即可
2. 注意修改 <property name="${log-path}/log.path" value=""/> 的value模块
-->
<configuration debug="false" scan="false">
<property name="log.path" value="logs/${project.artifactId}"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- Console log output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Log file debug output -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
</appender>
<!-- Log file error output -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<logger name="org.activiti.engine.impl.db" level="DEBUG">
<appender-ref ref="debug"/>
</logger>
<!--nacos 心跳 INFO 屏蔽-->
<logger name="com.alibaba.nacos" level="OFF">
<appender-ref ref="error"/>
</logger>
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
</root>
</configuration>
This source diff could not be displayed because it is too large. You can view the blob instead.
.sign_body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-margin-top {
margin-top: 50px;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
footer{
text-align: center;
position:absolute;
bottom:0;
width:100%;
height:100px;
}
<!DOCTYPE html>
<html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
<title>PigX第三方授权</title>
<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="/css/signin.css"/>
</head>
<body>
<nav class="navbar navbar-default container-fluid">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">开放平台</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-5">
<p class="navbar-text navbar-right">
<a target="_blank" href="https://pig4cloud.com">技术支持</a>
</p>
<p class="navbar-text navbar-right">
<a target="_blank" href="https://pig4cloud.com">${user.username}</a>
</p>
</div>
</div>
</nav>
<div style="padding-top: 80px;width: 300px; color: #555; margin:0px auto;">
<form id='confirmationForm' name='confirmationForm' action="/oauth/authorize" method='post'>
<input name='user_oauth_approval' value='true' type='hidden'/>
<p>
<a href="${app.website!''}" target="_blank">${app.appName!'未定义应用名称'}</a> 将获得以下权限:</p>
<ul class="list-group">
<li class="list-group-item"> <span>
<#list scopeList as scope>
<input type="hidden" name="${scope}" value="true"/>
<input type="checkbox" disabled checked="checked"/><label>${scope}</label>
</#list>
</ul>
<p class="help-block">授权后表明你已同意 <a>服务协议</a></p>
<button class="btn btn-success pull-right" type="submit" id="write-email-btn">授权</button>
</p>
</form>
</div>
<footer>
<p>support by: pig4cloud.com</p>
<p>email: <a href="mailto:wangiegie@gmail.com">wangiegie@gmail.com</a>.</p>
</footer>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<title>PigX微服务统一认证</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
</head>
<body class="sign_body">
<div class="container form-margin-top">
<form class="form-signin" action="/token/form" method="post">
<h2 class="form-signin-heading" align="center">统一认证系统</h2>
<input type="text" name="username" class="form-control form-margin-top" placeholder="账号" required autofocus>
<input type="password" name="password" class="form-control" placeholder="密码" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">sign in</button>
<#if error??>
<span style="color: red; ">${error}</span>
</#if>
</form>
</div>
<footer>
<p>support by: pig4cloud</p>
<p>email: <a href="mailto:wangiegie@gmail.com">wangiegie@gmail.com</a>.</p>
</footer>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-hospital</artifactId>
<version>3.3.0</version>
</parent>
<artifactId>smart-hospital-common</artifactId>
<packaging>pom</packaging>
<modules>
<module>smart-common-bom</module>
<module>smart-common-core</module>
<module>smart-common-data</module>
<module>smart-common-datasource</module>
<module>smart-common-gateway</module>
<module>smart-common-job</module>
<module>smart-common-log</module>
<module>smart-common-minio</module>
<module>smart-common-security</module>
<module>smart-common-sequence</module>
<module>smart-common-swagger</module>
<module>smart-common-im</module>
<module>smart-common-lock</module>
<module>smart-common-mq</module>
<module>smart-common-pay</module>
</modules>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<artifactId>smart-common-bom</artifactId>
<packaging>pom</packaging>
<version>${smart.version}</version>
<description>pigx 公共版本控制</description>
<properties>
<smart.version>3.3.0</smart.version>
<mybatis-plus.version>3.1.0</mybatis-plus.version>
<druid.version>1.1.18</druid.version>
<mysql.connector.version>8.0.16</mysql.connector.version>
<swagger.core.version>1.5.22</swagger.core.version>
<mp.weixin.version>3.3.0</mp.weixin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>hospital-register-api</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-core</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-data</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-gateway</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-datasource</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-job</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-log</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-minio</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-security</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-sequence</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-swagger</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-transaction</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-upms-api</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-im</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-lock</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-mq</artifactId>
<version>${smart.version}</version>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-pay</artifactId>
<version>${smart.version}</version>
</dependency>
<!--mybatis plus extension,包含了mybatis plus core-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
<!--swagger 最新依赖内置版本-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>${swagger.core.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger.core.version}</version>
</dependency>
<!--微信依赖-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${mp.weixin.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-common</artifactId>
<version>${mp.weixin.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-hospital-common</artifactId>
<version>3.3.0</version>
</parent>
<artifactId>smart-common-core</artifactId>
<packaging>jar</packaging>
<dependencies>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--server-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!--feign 依赖-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hibernate-validator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--json模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<!--TTL-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>${ttl.version}</version>
</dependency>
<!--swagger 依赖-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
</dependencies>
</project>
package com.smart.hospital.common.core.config;
import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.smart.hospital.common.core.jackson.SmartJavaTimeModule;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
/**
* JacksonConfig
*
* @author: giaogiao
* @author L.cm
* @author: lishangbu
* @date: 2018/10/22
*/
@Configuration
@ConditionalOnClass(ObjectMapper.class)
@AutoConfigureBefore(JacksonAutoConfiguration.class)
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.locale(Locale.CHINA);
builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
builder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN);
builder.modules(new SmartJavaTimeModule());
};
}
}
package com.smart.hospital.common.core.config;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
/**
* @author giaogiao
* @date 2018/11/14
* <p>
* 国际化配置
*/
@Configuration
public class MessageSourceConfig {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource
= new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:i18n/messages");
return messageSource;
}
}
package com.smart.hospital.common.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author giaogiao
* @date 2018/8/16
* RestTemplate
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package com.smart.hospital.common.core.constant;
/**
* @author giaogiao
* @date 2019-04-28
* <p>
* 缓存的key 常量
*/
public interface CacheConstants {
/**
* 菜单信息缓存
*/
String MENU_DETAILS = "menu_details";
/**
* 用户信息缓存
*/
String USER_DETAILS = "user_details";
/**
* 字典信息缓存
*/
String DICT_DETAILS = "dict_details";
/**
* oauth 客户端信息
*/
String CLIENT_DETAILS_KEY = "smart_oauth:client:details";
/**
* spring boot admin 事件key
*/
String EVENT_KEY = "event_key";
/**
* 路由存放
*/
String ROUTE_KEY = "gateway_route_key";
/**
* redis reload 事件
*/
String ROUTE_REDIS_RELOAD_TOPIC = "gateway_redis_route_reload_topic";
/**
* 内存reload 时间
*/
String ROUTE_JVM_RELOAD_TOPIC = "gateway_jvm_route_reload_topic";
/**
* 参数缓存
*/
String PARAMS_DETAILS = "params_details";
/**
* 租户缓存
*/
String TENANT_DETAILS = "tenant_details";
/**
* oauth2授权code存储的key
*/
String OAUTH_CODE = "smart_oauth:client:code:";
}
package com.smart.hospital.common.core.constant;
/**
* @author giaogiao
* @date 2017/10/29
*/
public interface CommonConstants {
/**
* header 中租户ID
*/
String TENANT_ID = "HOSPITAL-ID";
/**
* header 中版本信息
*/
String VERSION = "VERSION";
/**
* 租户ID
*/
Integer TENANT_ID_1 = 1;
/**
* 删除
*/
String STATUS_DEL = "1";
/**
* 正常
*/
String STATUS_NORMAL = "0";
/**
* 锁定
*/
String STATUS_LOCK = "9";
/**
* 菜单
*/
String MENU = "0";
/**
* 菜单树根节点
*/
Integer MENU_TREE_ROOT_ID = -1;
/**
* 编码
*/
String UTF8 = "UTF-8";
/**
* 前端工程名
*/
String FRONT_END_PROJECT = "Smart-ui";
/**
* 后端工程名
*/
String BACK_END_PROJECT = "Smart";
/**
* 验证码前缀
*/
String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY_";
/**
* 公共参数
*/
String PIG_PUBLIC_PARAM_KEY = "SMART_PUBLIC_PARAM_KEY";
/**
* 成功标记
*/
Integer SUCCESS = 0;
/**
* 失败标记
*/
Integer FAIL = 1;
/**
* 默认存储bucket
*/
String BUCKET_NAME = "giaogiao";
}
package com.smart.hospital.common.core.constant;
/**
* 分页相关的参数
* @author lishangbu
* @date 2018/11/22
*/
public interface PaginationConstants {
/**
* 当前页
*/
String CURRENT="current";
/**
* 每页大小
*/
String SIZE="size";
}
package com.smart.hospital.common.core.constant;
/**
* @author giaogiao
* @date 2017-12-18
*/
public interface SecurityConstants {
/**
* 刷新
*/
String REFRESH_TOKEN = "refresh_token";
/**
* 验证码有效期
*/
int CODE_TIME = 60;
/**
* 验证码长度
*/
String CODE_SIZE = "4";
/**
* 角色前缀
*/
String ROLE = "ROLE_";
/**
* 前缀
*/
String SMART_PREFIX = "smart_";
/**
* oauth 相关前缀
*/
String OAUTH_PREFIX = "oauth:";
/**
* 项目的license
*/
String SMART_LICENSE = "made by smart";
/**
* 内部
*/
String FROM_IN = "Y";
/**
* 标志
*/
String FROM = "from";
/**
* OAUTH URL
*/
String OAUTH_TOKEN_URL = "/oauth/token";
/**
* 手机号登录URL
*/
String SMS_TOKEN_URL = "/mobile/token/sms";
/**
* 社交登录URL
*/
String SOCIAL_TOKEN_URL = "/mobile/token/social";
/**
* 自定义登录URL
*/
String MOBILE_TOKEN_URL = "/mobile/token/*";
/**
* 微信获取OPENID
*/
String WX_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
/**
* 小程序端获取access_token
*/
String WX_APPLET_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
/**
* 获取微信access_token的前缀
*/
String WX_ACCESS_TOKEN_PREFIX = "wx:token";
/**
* 微信获取手机号
*/
String WX_PHONE_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s";
/**
* 码云获取token
*/
String GITEE_AUTHORIZATION_CODE_URL = "https://gitee.com/oauth/token?grant_type=" +
"authorization_code&code=%S&client_id=%s&redirect_uri=" +
"%s&client_secret=%s";
/**
* 开源中国获取token
*/
String OSC_AUTHORIZATION_CODE_URL = "https://www.oschina.net/action/openapi/token";
/**
* 码云获取用户信息
*/
String GITEE_USER_INFO_URL = "https://gitee.com/api/v5/user?access_token=%s";
/**
* 开源中国用户信息
*/
String OSC_USER_INFO_URL = "https://www.oschina.net/action/openapi/user?access_token=%s&dataType=json";
/**
* {bcrypt} 加密的特征码
*/
String BCRYPT = "{bcrypt}";
/**
* sys_oauth_client_details 表的字段,不包括client_id、client_secret
*/
String CLIENT_FIELDS = "client_id, CONCAT('{noop}',client_secret) as client_secret, resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";
/**
* JdbcClientDetailsService 查询语句
*/
String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS
+ " from sys_oauth_client_details";
/**
* 默认的查询语句
*/
String DEFAULT_FIND_STATEMENT = BASE_FIND_STATEMENT + " order by client_id";
/**
* 按条件client_id 查询
*/
String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ?";
/**
* 资源服务器默认bean名称
*/
String RESOURCE_SERVER_CONFIGURER = "resourceServerConfigurerAdapter";
/**
* 客户端模式
*/
String CLIENT_CREDENTIALS = "client_credentials";
/**
* 用户ID字段
*/
String DETAILS_USER_ID = "user_id";
/**
* 用户名字段
*/
String DETAILS_USERNAME = "username";
/**
* 用户部门字段
*/
String DETAILS_DEPT_ID = "dept_id";
/**
* 租户ID 字段
*/
String DETAILS_TENANT_ID = "tenant_id";
/**
* 协议字段
*/
String DETAILS_LICENSE = "license";
/**
* 激活字段 兼容外围系统接入
*/
String ACTIVE = "active";
/**
* AES 加密
*/
String AES = "aes";
}
package com.smart.hospital.common.core.constant;
/**
* @author giaogiao
* @date 2018年06月22日16:41:01
* 服务名称
*/
public interface ServiceNameConstants {
/**
* 认证中心
*/
String AUTH_SERVICE = "smart-hospital-auth";
/**
* UMPS模块
*/
String UPMS_SERVICE = "smart-upms-biz";
}
package com.smart.hospital.common.core.constant.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author giaogiao
* @date 2019-05-16
* <p>
* 字典类型
*/
@Getter
@AllArgsConstructor
public enum DictTypeEnum {
/**
* 字典类型-系统内置(不可修改)
*/
SYSTEM("1", "系统内置"),
/**
* 字典类型-业务类型
*/
BIZ("0", "业务类");
/**
* 类型
*/
private String type;
/**
* 描述
*/
private String description;
}
package com.smart.hospital.common.core.constant.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author giaogiao
* @date 2018/8/15
* 社交登录类型
*/
@Getter
@AllArgsConstructor
public enum LoginTypeEnum {
/**
* 账号密码登录
*/
PWD("PWD", "账号密码登录"),
/**
* 验证码登录
*/
SMS("SMS", "验证码登录"),
/**
* QQ登录
*/
QQ("QQ", "QQ登录"),
/**
* 微信登录
*/
WECHAT("WX", "微信登录"),
/**
* 微信小程序登录(获取手机号)
*/
WX_APPLET("WX_APPLET", "微信小程序登录"),
/**
* 码云登录
*/
GITEE("GITEE", "码云登录"),
/**
* 开源中国登录
*/
OSC("OSC", "开源中国登录");
/**
* 类型
*/
private String type;
/**
* 描述
*/
private String description;
}
package com.smart.hospital.common.core.constant.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author giaogiao
* @date 2018/9/30
* 流程状态
*/
@Getter
@AllArgsConstructor
public enum ProcessStatusEnum {
/**
* 图片资源
*/
ACTIVE("active", "图片资源"),
/**
* xml资源
*/
SUSPEND("suspend", "xml资源");
/**
* 类型
*/
private final String status;
/**
* 描述
*/
private final String description;
}
package com.smart.hospital.common.core.constant.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author giaogiao
* @date 2018/9/30
* 资源类型
*/
@Getter
@AllArgsConstructor
public enum ResourceTypeEnum {
/**
* 图片资源
*/
IMAGE("image", "图片资源"),
/**
* xml资源
*/
XML("xml", "xml资源");
/**
* 类型
*/
private final String type;
/**
* 描述
*/
private final String description;
}
package com.smart.hospital.common.core.constant.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author giaogiao
* @date 2018/9/30
* 流程状态
*/
@Getter
@AllArgsConstructor
public enum TaskStatusEnum {
/**
* 未提交
*/
UNSUBMIT("0", "未提交"),
/**
* 审核中
*/
CHECK("1", "审核中"),
/**
* 已完成
*/
COMPLETED("2", "已完成"),
/**
* 驳回
*/
OVERRULE("9", "驳回");
/**
* 类型
*/
private final String status;
/**
* 描述
*/
private final String description;
}
package com.smart.hospital.common.core.exception;
import lombok.NoArgsConstructor;
/**
* @author giaogiao
* @date 😴2018年06月22日16:21:57
*/
@NoArgsConstructor
public class CheckedException extends RuntimeException {
private static final long serialVersionUID = 1L;
public CheckedException(String message) {
super(message);
}
public CheckedException(Throwable cause) {
super(cause);
}
public CheckedException(String message, Throwable cause) {
super(message, cause);
}
public CheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
package com.smart.hospital.common.core.exception;
/**
* @author giaogiao
* @date 2018年06月22日16:22:15
*/
public class ValidateCodeException extends Exception {
private static final long serialVersionUID = -7285211528095468156L;
public ValidateCodeException() {
}
public ValidateCodeException(String msg) {
super(msg);
}
}
package com.smart.hospital.common.core.jackson;
import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* java 8 时间默认序列化
*
* @author L.cm
* @author lishanbu
*/
public class SmartJavaTimeModule extends SimpleModule {
public SmartJavaTimeModule() {
super(PackageVersion.VERSION);
this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
}
}
package com.smart.hospital.common.core.sensitive;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 对象脱敏注解
*
* @author mayee
* @version v1.0
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerialize.class)
public @interface Sensitive {
/**
* 脱敏数据类型, 非Customer时, 将忽略 refixNoMaskLen 和 suffixNoMaskLen 和 maskStr
*/
SensitiveTypeEnum type() default SensitiveTypeEnum.CUSTOMER;
/**
* 前置不需要打码的长度
*/
int prefixNoMaskLen() default 0;
/**
* 后置不需要打码的长度
*/
int suffixNoMaskLen() default 0;
/**
* 用什么打码
*/
String maskStr() default "*";
}
package com.smart.hospital.common.core.sensitive;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.google.common.base.Preconditions;
import com.smart.hospital.common.core.util.DesensitizedUtils;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.Objects;
/**
* @author giaogiao
* @date 2019-08-13
* <p>
* 脱敏序列化
*/
@NoArgsConstructor
@AllArgsConstructor
public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveTypeEnum type;
private Integer prefixNoMaskLen;
private Integer suffixNoMaskLen;
private String maskStr;
@Override
public void serialize(final String origin, final JsonGenerator jsonGenerator,
final SerializerProvider serializerProvider) throws IOException {
Preconditions.checkNotNull(type, "Sensitive type enum should not be null.");
switch (type) {
case CHINESE_NAME:
jsonGenerator.writeString(DesensitizedUtils.chineseName(origin));
break;
case ID_CARD:
jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin));
break;
case FIXED_PHONE:
jsonGenerator.writeString(DesensitizedUtils.fixedPhone(origin));
break;
case MOBILE_PHONE:
jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin));
break;
case ADDRESS:
jsonGenerator.writeString(DesensitizedUtils.address(origin));
break;
case EMAIL:
jsonGenerator.writeString(DesensitizedUtils.email(origin));
break;
case BANK_CARD:
jsonGenerator.writeString(DesensitizedUtils.bankCard(origin));
break;
case PASSWORD:
jsonGenerator.writeString(DesensitizedUtils.password(origin));
break;
case KEY:
jsonGenerator.writeString(DesensitizedUtils.key(origin));
break;
case CUSTOMER:
jsonGenerator.writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, maskStr));
break;
default:
throw new IllegalArgumentException("Unknow sensitive type enum " + type);
}
}
@Override
public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
final BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
if (sensitive == null) {
sensitive = beanProperty.getContextAnnotation(Sensitive.class);
}
if (sensitive != null) {
return new SensitiveSerialize(sensitive.type(), sensitive.prefixNoMaskLen(), sensitive.suffixNoMaskLen(), sensitive.maskStr());
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}
package com.smart.hospital.common.core.sensitive;
/**
* 敏感信息枚举类
*
* @author mayee
* @version v1.0
**/
public enum SensitiveTypeEnum {
/**
* 自定义
*/
CUSTOMER,
/**
* 用户名, 刘*华, 徐*
*/
CHINESE_NAME,
/**
* 身份证号, 110110********1234
*/
ID_CARD,
/**
* 座机号, ****1234
*/
FIXED_PHONE,
/**
* 手机号, 176****1234
*/
MOBILE_PHONE,
/**
* 地址, 北京********
*/
ADDRESS,
/**
* 电子邮件, s*****o@xx.com
*/
EMAIL,
/**
* 银行卡, 622202************1234
*/
BANK_CARD,
/**
* 密码, 永远是 ******, 与长度无关
*/
PASSWORD,
/**
* 密钥, 永远是 ******, 与长度无关
*/
KEY
}
package com.smart.hospital.common.core.util;
import lombok.experimental.UtilityClass;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.web.method.HandlerMethod;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* 类工具类
*
* @author L.cm
*/
@UtilityClass
public class ClassUtils extends org.springframework.util.ClassUtils {
private final ParameterNameDiscoverer PARAMETERNAMEDISCOVERER = new DefaultParameterNameDiscoverer();
/**
* 获取方法参数信息
*
* @param constructor 构造器
* @param parameterIndex 参数序号
* @return {MethodParameter}
*/
public MethodParameter getMethodParameter(Constructor<?> constructor, int parameterIndex) {
MethodParameter methodParameter = new SynthesizingMethodParameter(constructor, parameterIndex);
methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
return methodParameter;
}
/**
* 获取方法参数信息
*
* @param method 方法
* @param parameterIndex 参数序号
* @return {MethodParameter}
*/
public MethodParameter getMethodParameter(Method method, int parameterIndex) {
MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
return methodParameter;
}
/**
* 获取Annotation
*
* @param method Method
* @param annotationType 注解类
* @param <A> 泛型标记
* @return {Annotation}
*/
public <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
Class<?> targetClass = method.getDeclaringClass();
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
// If we are dealing with method with generic parameters, find the original method.
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// 先找方法,再找方法上的类
A annotation = AnnotatedElementUtils.findMergedAnnotation(specificMethod, annotationType);
;
if (null != annotation) {
return annotation;
}
// 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
return AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), annotationType);
}
/**
* 获取Annotation
*
* @param handlerMethod HandlerMethod
* @param annotationType 注解类
* @param <A> 泛型标记
* @return {Annotation}
*/
public <A extends Annotation> A getAnnotation(HandlerMethod handlerMethod, Class<A> annotationType) {
// 先找方法,再找方法上的类
A annotation = handlerMethod.getMethodAnnotation(annotationType);
if (null != annotation) {
return annotation;
}
// 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
Class<?> beanType = handlerMethod.getBeanType();
return AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType);
}
}
package com.smart.hospital.common.core.util;
import org.apache.commons.lang.StringUtils;
/**
* 脱敏工具类
*
* @author mayee
* @version v1.0
**/
public class DesensitizedUtils {
/**
* 对字符串进行脱敏操作
*
* @param origin 原始字符串
* @param prefixNoMaskLen 左侧需要保留几位明文字段
* @param suffixNoMaskLen 右侧需要保留几位明文字段
* @param maskStr 用于遮罩的字符串, 如'*'
* @return 脱敏后结果
*/
public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
if (origin == null) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0, n = origin.length(); i < n; i++) {
if (i < prefixNoMaskLen) {
sb.append(origin.charAt(i));
continue;
}
if (i > (n - suffixNoMaskLen - 1)) {
sb.append(origin.charAt(i));
continue;
}
sb.append(maskStr);
}
return sb.toString();
}
/**
* 【中文姓名】只显示最后一个汉字,其他隐藏为星号,比如:**梦
*
* @param fullName 姓名
* @return 结果
*/
public static String chineseName(String fullName) {
if (fullName == null) {
return null;
}
return desValue(fullName, 0, 1, "*");
}
/**
* 【身份证号】显示前六位, 四位,其他隐藏。共计18位或者15位,比如:340304*******1234
*
* @param id 身份证号码
* @return 结果
*/
public static String idCardNum(String id) {
return desValue(id, 6, 4, "*");
}
/**
* 【固定电话】后四位,其他隐藏,比如 ****1234
*
* @param num 固定电话
* @return 结果
*/
public static String fixedPhone(String num) {
return desValue(num, 0, 4, "*");
}
/**
* 【手机号码】前三位,后四位,其他隐藏,比如135****6810
*
* @param num 手机号码
* @return 结果
*/
public static String mobilePhone(String num) {
return desValue(num, 3, 4, "*");
}
/**
* 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
*
* @param address 地址
* @return 结果
*/
public static String address(String address) {
return desValue(address, 6, 0, "*");
}
/**
* 【电子邮箱 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
*
* @param email 电子邮箱
* @return 结果
*/
public static String email(String email) {
if (email == null) {
return null;
}
int index = StringUtils.indexOf(email, "@");
if (index <= 1) {
return email;
}
String preEmail = desValue(email.substring(0, index), 1, 0, "*");
return preEmail + email.substring(index);
}
/**
* 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:622260**********1234
*
* @param cardNum 银行卡号
* @return 结果
*/
public static String bankCard(String cardNum) {
return desValue(cardNum, 6, 4, "*");
}
/**
* 【密码】密码的全部字符都用*代替,比如:******
*
* @param password 密码
* @return 结果
*/
public static String password(String password) {
if (password == null) {
return null;
}
return "******";
}
/**
* 【密钥】密钥除了最后三位,全部都用*代替,比如:***xdS
* 脱敏后长度为6,如果明文长度不足三位,则按实际长度显示,剩余位置补*
*
* @param key 密钥
* @return 结果
*/
public static String key(String key) {
if (key == null) {
return null;
}
int viewLength = 6;
StringBuilder tmpKey = new StringBuilder(desValue(key, 0, 3, "*"));
if (tmpKey.length() > viewLength) {
return tmpKey.substring(tmpKey.length() - viewLength);
} else if (tmpKey.length() < viewLength) {
int buffLength = viewLength - tmpKey.length();
for (int i = 0; i < buffLength; i++) {
tmpKey.insert(0, "*");
}
return tmpKey.toString();
} else {
return tmpKey.toString();
}
}
}
package com.smart.hospital.common.core.util;
import com.smart.hospital.common.core.constant.CommonConstants;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 响应信息主体
*
* @param <T>
* @author giaogiao
*/
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@ApiModel(value = "响应信息主体")
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
@Getter
@Setter
@ApiModelProperty(value = "返回标记:成功标记=0,失败标记=1")
private int code;
@Getter
@Setter
@ApiModelProperty(value = "返回信息")
private String msg;
@Getter
@Setter
@ApiModelProperty(value = "数据")
private T data;
public static <T> R<T> ok() {
return restResult(null, CommonConstants.SUCCESS, null);
}
public static <T> R<T> ok(T data) {
return restResult(data, CommonConstants.SUCCESS, null);
}
public static <T> R<T> ok(T data, String msg) {
return restResult(data, CommonConstants.SUCCESS, msg);
}
public static <T> R<T> failed() {
return restResult(null, CommonConstants.FAIL, null);
}
public static <T> R<T> failed(String msg) {
return restResult(null, CommonConstants.FAIL, msg);
}
public static <T> R<T> failed(T data) {
return restResult(data, CommonConstants.FAIL, null);
}
public static <T> R<T> failed(T data, String msg) {
return restResult(data, CommonConstants.FAIL, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
}
package com.smart.hospital.common.core.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
/**
* @author giaogiao
* @date 2018/6/27
* Spring 工具类
*/
@Slf4j
@Service
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext;
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
public static void clearHolder() {
if (log.isDebugEnabled()) {
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}
/**
* 发布事件
*
* @param event
*/
public static void publishEvent(ApplicationEvent event) {
if (applicationContext == null) {
return;
}
applicationContext.publishEvent(event);
}
/**
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
*/
@Override
public void destroy() {
SpringContextHolder.clearHolder();
}
}
package com.smart.hospital.common.core.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.json.JSONUtil;
import com.smart.hospital.common.core.exception.CheckedException;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
/**
* Miscellaneous utilities for web applications.
*
* @author L.cm
*/
@Slf4j
@UtilityClass
public class WebUtils extends org.springframework.web.util.WebUtils {
private final String BASIC_ = "Basic ";
private final String UNKNOWN = "unknown";
/**
* 判断是否ajax请求
* spring ajax 返回含有 ResponseBody 或者 RestController注解
*
* @param handlerMethod HandlerMethod
* @return 是否ajax请求
*/
public boolean isBody(HandlerMethod handlerMethod) {
ResponseBody responseBody = ClassUtils.getAnnotation(handlerMethod, ResponseBody.class);
return responseBody != null;
}
/**
* 读取cookie
*
* @param name cookie name
* @return cookie value
*/
public String getCookieVal(String name) {
HttpServletRequest request = WebUtils.getRequest();
Assert.notNull(request, "request from RequestContextHolder is null");
return getCookieVal(request, name);
}
/**
* 读取cookie
*
* @param request HttpServletRequest
* @param name cookie name
* @return cookie value
*/
public String getCookieVal(HttpServletRequest request, String name) {
Cookie cookie = getCookie(request, name);
return cookie != null ? cookie.getValue() : null;
}
/**
* 清除 某个指定的cookie
*
* @param response HttpServletResponse
* @param key cookie key
*/
public void removeCookie(HttpServletResponse response, String key) {
setCookie(response, key, null, 0);
}
/**
* 设置cookie
*
* @param response HttpServletResponse
* @param name cookie name
* @param value cookie value
* @param maxAgeInSeconds maxage
*/
public void setCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setMaxAge(maxAgeInSeconds);
cookie.setHttpOnly(true);
response.addCookie(cookie);
}
/**
* 获取 HttpServletRequest
*
* @return {HttpServletRequest}
*/
public HttpServletRequest getRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
/**
* 获取 HttpServletResponse
*
* @return {HttpServletResponse}
*/
public HttpServletResponse getResponse() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
/**
* 返回json
*
* @param response HttpServletResponse
* @param result 结果对象
*/
public void renderJson(HttpServletResponse response, Object result) {
renderJson(response, result, MediaType.APPLICATION_JSON_UTF8_VALUE);
}
/**
* 返回json
*
* @param response HttpServletResponse
* @param result 结果对象
* @param contentType contentType
*/
public void renderJson(HttpServletResponse response, Object result, String contentType) {
response.setCharacterEncoding("UTF-8");
response.setContentType(contentType);
try (PrintWriter out = response.getWriter()) {
out.append(JSONUtil.toJsonStr(result));
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
/**
* 获取ip
*
* @return {String}
*/
public String getIP() {
return getIP(WebUtils.getRequest());
}
/**
* 获取ip
*
* @param request HttpServletRequest
* @return {String}
*/
public String getIP(HttpServletRequest request) {
Assert.notNull(request, "HttpServletRequest is null");
String ip = request.getHeader("X-Requested-For");
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return StringUtils.isBlank(ip) ? null : ip.split(",")[0];
}
/**
* 从request 获取CLIENT_ID
*
* @return
*/
@SneakyThrows
public String[] getClientId(ServerHttpRequest request) {
String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (header == null || !header.startsWith(BASIC_)) {
throw new CheckedException("请求头中client信息为空");
}
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.decode(base64Token);
} catch (IllegalArgumentException e) {
throw new CheckedException(
"Failed to decode basic authentication token");
}
String token = new String(decoded, StandardCharsets.UTF_8);
int delim = token.indexOf(":");
if (delim == -1) {
throw new CheckedException("Invalid basic authentication token");
}
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
}
package com.smart.hospital.common.core.xss;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.HtmlUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author giaogiao
* @date 2019-08-13
* <p>
* 1. XSS过滤
* 2. body重复读
*/
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
RequestWrapper(HttpServletRequest request) {
super(request);
this.body = getByteBody(request);
}
@Override
public BufferedReader getReader() {
return ObjectUtils.isEmpty(body) ? null
: new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() {
return byteArrayInputStream.read();
}
};
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = HtmlUtils.htmlEscape(values[i]);
}
return encodedValues;
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value == null) {
return null;
}
return HtmlUtils.htmlEscape(value);
}
@Override
public Object getAttribute(String name) {
Object value = super.getAttribute(name);
if (value instanceof String) {
HtmlUtils.htmlEscape((String) value);
}
return value;
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
if (value == null) {
return null;
}
return HtmlUtils.htmlEscape(value);
}
@Override
public String getQueryString() {
String value = super.getQueryString();
if (value == null) {
return null;
}
return HtmlUtils.htmlEscape(value);
}
private static byte[] getByteBody(HttpServletRequest request) {
byte[] body = new byte[0];
try {
body = StreamUtils.copyToByteArray(request.getInputStream());
} catch (IOException e) {
log.error("解析流中数据异常", e);
}
return body;
}
}
package com.smart.hospital.common.core.xss;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author giaogiao
* @date 2019-08-13
* <p>
* request 处理过滤器
*/
public class RequestWrapperFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(new RequestWrapper(request), response);
}
}
\ No newline at end of file
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.smart.hospital.common.core.config.JacksonConfig,\
com.smart.hospital.common.core.config.MessageSourceConfig,\
com.smart.hospital.common.core.config.RestTemplateConfig,\
com.smart.hospital.common.core.util.SpringContextHolder,\
com.smart.hospital.common.core.xss.RequestWrapperFilter
${AnsiColor.BRIGHT_YELLOW}
_________ _____ _____ _____________________
/ _____/ / \ / _ \\______ \__ ___/
\_____ \ / \ / \ / /_\ \| _/ | |
/ \/ Y \/ | \ | \ | |
/_______ /\____|__ /\____|__ /____|_ / |____|
\/ \/ \/ \/
www.lizhi.net
Fusion Microservice Architecture
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-hospital-common</artifactId>
<version>3.3.0</version>
</parent>
<artifactId>smart-common-data</artifactId>
<packaging>jar</packaging>
<dependencies>
<!--工具类核心包-->
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-core</artifactId>
</dependency>
<!--mybatis plus extension,包含了mybatis plus core-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<!--安全依赖获取上下文信息-->
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-security</artifactId>
</dependency>
<!--缓存依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--高性能bean转换-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
</project>
package com.smart.hospital.common.data.cache;
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* {@link RedisCacheWriter} implementation capable of reading/writing binary data from/to Redis in {@literal standalone}
* and {@literal cluster} environments. Works upon a given {@link RedisConnectionFactory} to obtain the actual
* {@link RedisConnection}. <br />
* {@link DefaultRedisCacheWriter} can be used in
* {@link RedisCacheWriter#lockingRedisCacheWriter(RedisConnectionFactory) locking} or
* {@link RedisCacheWriter#nonLockingRedisCacheWriter(RedisConnectionFactory) non-locking} mode. While
* {@literal non-locking} aims for maximum performance it may result in overlapping, non atomic, command execution for
* operations spanning multiple Redis interactions like {@code putIfAbsent}. The {@literal locking} counterpart prevents
* command overlap by setting an explicit lock key and checking against presence of this key which leads to additional
* requests and potential command wait times.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 2.0
*/
class DefaultRedisCacheWriter implements RedisCacheWriter {
private final RedisConnectionFactory connectionFactory;
private final Duration sleepTime;
/**
* @param connectionFactory must not be {@literal null}.
*/
DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory) {
this(connectionFactory, Duration.ZERO);
}
/**
* @param connectionFactory must not be {@literal null}.
* @param sleepTime sleep time between lock request attempts. Must not be {@literal null}. Use {@link Duration#ZERO}
* to disable locking.
*/
private DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
Assert.notNull(sleepTime, "SleepTime must not be null!");
this.connectionFactory = connectionFactory;
this.sleepTime = sleepTime;
}
@Override
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
execute(name, connection -> {
if (shouldExpireWithin(ttl)) {
connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
} else {
connection.set(key, value);
}
return "OK";
});
}
@Override
public byte[] get(String name, byte[] key) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
return execute(name, connection -> connection.get(key));
}
@Override
public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
return execute(name, connection -> {
if (isLockingCacheWriter()) {
doLock(name, connection);
}
try {
if (connection.setNX(key, value)) {
if (shouldExpireWithin(ttl)) {
connection.pExpire(key, ttl.toMillis());
}
return null;
}
return connection.get(key);
} finally {
if (isLockingCacheWriter()) {
doUnlock(name, connection);
}
}
});
}
@Override
public void remove(String name, byte[] key) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
execute(name, connection -> connection.del(key));
}
@Override
public void clean(String name, byte[] pattern) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(pattern, "Pattern must not be null!");
execute(name, connection -> {
boolean wasLocked = false;
try {
if (isLockingCacheWriter()) {
doLock(name, connection);
wasLocked = true;
}
byte[][] keys = Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())
.toArray(new byte[0][]);
if (keys.length > 0) {
connection.del(keys);
}
} finally {
if (wasLocked && isLockingCacheWriter()) {
doUnlock(name, connection);
}
}
return "OK";
});
}
/**
* Explicitly set a write lock on a cache.
*
* @param name the name of the cache to lock.
*/
void lock(String name) {
execute(name, connection -> doLock(name, connection));
}
/**
* Explicitly remove a write lock from a cache.
*
* @param name the name of the cache to unlock.
*/
void unlock(String name) {
executeLockFree(connection -> doUnlock(name, connection));
}
private Boolean doLock(String name, RedisConnection connection) {
return connection.setNX(createCacheLockKey(name), new byte[0]);
}
private Long doUnlock(String name, RedisConnection connection) {
return connection.del(createCacheLockKey(name));
}
boolean doCheckLock(String name, RedisConnection connection) {
return connection.exists(createCacheLockKey(name));
}
/**
* @return {@literal true} if {@link RedisCacheWriter} uses locks.
*/
private boolean isLockingCacheWriter() {
return !sleepTime.isZero() && !sleepTime.isNegative();
}
private <T> T execute(String name, Function<RedisConnection, T> callback) {
RedisConnection connection = connectionFactory.getConnection();
try {
checkAndPotentiallyWaitUntilUnlocked(name, connection);
return callback.apply(connection);
} finally {
connection.close();
}
}
private void executeLockFree(Consumer<RedisConnection> callback) {
RedisConnection connection = connectionFactory.getConnection();
try {
callback.accept(connection);
} finally {
connection.close();
}
}
private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {
if (!isLockingCacheWriter()) {
return;
}
try {
while (doCheckLock(name, connection)) {
Thread.sleep(sleepTime.toMillis());
}
} catch (InterruptedException ex) {
// Re-interrupt current thread, to allow other participants to react.
Thread.currentThread().interrupt();
throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name),
ex);
}
}
private static boolean shouldExpireWithin(@Nullable Duration ttl) {
return ttl != null && !ttl.isZero() && !ttl.isNegative();
}
private static byte[] createCacheLockKey(String name) {
return (name + "~lock").getBytes(StandardCharsets.UTF_8);
}
}
package com.smart.hospital.common.data.cache;
import cn.hutool.core.util.StrUtil;
import com.smart.hospital.common.data.tenant.TenantContextHolder;
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;
/**
* redis cache 扩展cache name自动化配置
*
* @author L.cm
* @author giaogiao
* <p>
* cachename = xx#ttl
*/
@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<String, RedisCacheConfiguration> 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);
}
/**
* 从上下文中获取租户ID,重写@Cacheable value 值
* @param name
* @return
*/
@Override
public Cache getCache(String name) {
return super.getCache(TenantContextHolder.getTenantId() + StrUtil.COLON + name);
}
}
package com.smart.hospital.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;
/**
* 扩展redis-cache支持注解cacheName添加超时时间
*
* @author L.cm
*/
@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> 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<String> cacheNames = this.cacheProperties.getCacheNames();
Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
if (!cacheNames.isEmpty()) {
Map<String, RedisCacheConfiguration> 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;
}
}
}
package com.smart.hospital.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;
/**
* CacheManagerCustomizers配置
*
* @author L.cm
*/
@Configuration
@ConditionalOnMissingBean(CacheManagerCustomizers.class)
public class RedisCacheManagerConfig {
@Bean
public CacheManagerCustomizers cacheManagerCustomizers(
ObjectProvider<List<CacheManagerCustomizer<?>>> customizers) {
return new CacheManagerCustomizers(customizers.getIfAvailable());
}
}
package com.smart.hospital.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.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* RedisTemplate 配置
*
* @author L.cm
*/
@EnableCaching
@Configuration
@AllArgsConstructor
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisTemplateConfig {
private final RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
package com.smart.hospital.common.data.datascope;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @author giaogiao
* @date 2018/8/30
* 数据权限查询参数
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class DataScope extends HashMap {
/**
* 限制范围的字段名称
*/
private String scopeName = "deptId";
/**
* 具体的数据范围
*/
private List<Integer> deptIds = new ArrayList<>();
/**
* 是否只查询本部门
*/
private Boolean isOnly = false;
}
package com.smart.hospital.common.data.datascope;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
import com.smart.hospital.common.core.constant.SecurityConstants;
import com.smart.hospital.common.data.enums.DataScopeTypeEnum;
import com.smart.hospital.common.security.service.SmartUser;
import com.smart.hospital.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;
/**
* @author giaogiao
* @date 2018/12/26
* <p>
* mybatis 数据权限拦截器
*/
@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<Integer> deptIds = dataScope.getDeptIds();
// 优先获取赋值数据
if (CollUtil.isEmpty(deptIds)) {
SmartUser user = SecurityUtils.getUser();
List<String> roleIdList = user.getAuthorities()
.stream().map(GrantedAuthority::getAuthority)
.filter(authority -> authority.startsWith(SecurityConstants.ROLE))
.map(authority -> authority.split(StrUtil.UNDERLINE)[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(StrUtil.COMMA))
.map(Integer::parseInt).collect(Collectors.toList()));
}
// 查询本级及其下级
if (DataScopeTypeEnum.OWN_CHILD_LEVEL.getType() == dsType) {
List<Integer> 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;
}
}
package com.smart.hospital.common.data.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author giaogiao
* @date 2018/12/26
* <p>
* 数据权限类型
*/
@Getter
@AllArgsConstructor
public enum DataScopeTypeEnum {
/**
* 查询全部数据
*/
ALL(0, "全部"),
/**
* 自定义
*/
CUSTOM(1, "自定义"),
/**
* 本级及子级
*/
OWN_CHILD_LEVEL(2, "本级及子级"),
/**
* 本级
*/
OWN_LEVEL(3, "本级");
/**
* 类型
*/
private final int type;
/**
* 描述
*/
private final String description;
}
package com.smart.hospital.common.data.mybatis;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
import com.smart.hospital.common.data.datascope.DataScopeInterceptor;
import com.smart.hospital.common.data.tenant.SmartTenantHandler;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
/**
* @author giaogiao
* @date 2017/10/29
*/
@Configuration
@ConditionalOnBean(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@MapperScan("com.smart.hospital.**.mapper")
public class MybatisPlusConfig {
/**
* 创建租户维护处理器对象
*
* @return 处理后的租户维护处理器
*/
@Bean
@ConditionalOnMissingBean
public SmartTenantHandler pigxTenantHandler() {
return new SmartTenantHandler();
}
/**
* 分页插件
*
* @param tenantHandler 租户处理器
* @return PaginationInterceptor
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "mybatisPlus.tenantEnable", havingValue = "true", matchIfMissing = true)
public PaginationInterceptor paginationInterceptor(SmartTenantHandler tenantHandler) {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
TenantSqlParser tenantSqlParser = new TenantSqlParser();
tenantSqlParser.setTenantHandler(tenantHandler);
sqlParserList.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParserList);
return paginationInterceptor;
}
/**
* 数据权限插件
*
* @param dataSource 数据源
* @return DataScopeInterceptor
*/
@Bean
@ConditionalOnMissingBean
public DataScopeInterceptor dataScopeInterceptor(DataSource dataSource) {
return new DataScopeInterceptor(dataSource);
}
/**
* 逻辑删除插件
*
* @return LogicSqlInjector
*/
@Bean
@ConditionalOnMissingBean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
}
package com.smart.hospital.common.data.resolver;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.smart.hospital.common.core.exception.CheckedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
/**
* @author giaogiao
* @date 2019-06-24
* <p>
* 解决Mybatis Plus Order By SQL注入问题
*/
@Slf4j
public class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver {
private final static String[] KEYWORDS = {"master", "truncate", "insert", "select"
, "delete", "update", "declare", "alter", "drop", "sleep"};
/**
* 判断Controller是否包含page 参数
*
* @param parameter 参数
* @return 是否过滤
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(Page.class);
}
/**
* @param parameter 入参集合
* @param mavContainer model 和 view
* @param webRequest web相关
* @param binderFactory 入参解析
* @return 检查后新的page对象
* <p>
* page 只支持查询 GET .如需解析POST获取请求报文体处理
*/
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer
, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String[] ascs = request.getParameterValues("ascs");
String[] descs = request.getParameterValues("descs");
String current = request.getParameter("current");
String size = request.getParameter("size");
Page page = new Page();
if (StrUtil.isNotBlank(current)) {
page.setCurrent(Long.parseLong(current));
}
if (StrUtil.isNotBlank(size)) {
page.setSize(Long.parseLong(size));
}
page.setAsc(sqlInject(ascs));
page.setDesc(sqlInject(descs));
return page;
}
/**
* SQL注入过滤
*
* @param str 待验证的字符串
*/
public static String[] sqlInject(String[] str) {
if (ArrayUtil.isEmpty(str)) {
return null;
}
//转换成小写
String inStr = ArrayUtil.join(str, StrUtil.COMMA).toLowerCase();
//判断是否包含非法字符
for (String keyword : KEYWORDS) {
if (inStr.contains(keyword)) {
log.error("查询包含非法字符 {}", keyword);
throw new CheckedException(keyword + "包含非法字符");
}
}
return str;
}
}
package com.smart.hospital.common.data.resolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* @author giaogiao
* @date 2019-06-24
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new SqlFilterArgumentResolver());
}
}
package com.smart.hospital.common.data.tenant;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author giaogiao
* @date 2018/9/14
* feign 租户信息拦截
*/
@Configuration
public class SmartFeignTenantConfiguration {
@Bean
public RequestInterceptor smartFeignTenantInterceptor() {
return new SmartFeignTenantInterceptor();
}
}
package com.smart.hospital.common.data.tenant;
import com.smart.hospital.common.core.constant.CommonConstants;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
/**
* @author giaogiao
* @date 2018/9/14
*/
@Slf4j
public class SmartFeignTenantInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
if (TenantContextHolder.getTenantId() == null) {
log.error("TTL 中的 租户ID为空,feign拦截器 >> 增强失败");
return;
}
requestTemplate.header(CommonConstants.TENANT_ID, TenantContextHolder.getTenantId().toString());
}
}
package com.smart.hospital.common.data.tenant;
import lombok.Data;
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;
/**
* 多租户配置
*
* @author oathsign
*/
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "smart.tenant")
public class SmartTenantConfigProperties {
/**
* 维护租户列名称
*/
private String column = "tenant_id";
/**
* 多租户的数据表集合
*/
private List<String> tables = new ArrayList<>();
}
package com.smart.hospital.common.data.tenant;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author giaogiao
* @date 2018-12-26
* <p>
* 租户维护处理器
*/
@Slf4j
public class SmartTenantHandler implements TenantHandler {
@Autowired
private SmartTenantConfigProperties properties;
/**
* 获取租户值
* <p>
* TODO 校验租户状态
*
* @return 租户值
*/
@Override
public Expression getTenantId() {
Integer tenantId = TenantContextHolder.getTenantId();
log.debug("当前租户为 >> {}", tenantId);
if (tenantId == null) {
return new NullValue();
}
return new LongValue(tenantId);
}
/**
* 获取租户字段名
*
* @return 租户字段名
*/
@Override
public String getTenantIdColumn() {
return properties.getColumn();
}
/**
* 根据表名判断是否进行过滤
*
* @param tableName 表名
* @return 是否进行过滤
*/
@Override
public boolean doTableFilter(String tableName) {
Integer tenantId = TenantContextHolder.getTenantId();
// 租户中ID 为空,查询全部,不进行过滤
if (tenantId == null) {
return Boolean.TRUE;
}
return !properties.getTables().contains(tableName);
}
}
package com.smart.hospital.common.data.tenant;
import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.experimental.UtilityClass;
/**
* @author giaogiao
* @date 2018/10/4
* 租户工具类
*/
@UtilityClass
public class TenantContextHolder {
private final ThreadLocal<Integer> THREAD_LOCAL_TENANT = new TransmittableThreadLocal<>();
/**
* TTL 设置租户ID
*
* @param tenantId
*/
public void setTenantId(Integer tenantId) {
THREAD_LOCAL_TENANT.set(tenantId);
}
/**
* 获取TTL中的租户ID
*
* @return
*/
public Integer getTenantId() {
return THREAD_LOCAL_TENANT.get();
}
public void clear() {
THREAD_LOCAL_TENANT.remove();
}
}
package com.smart.hospital.common.data.tenant;
import cn.hutool.core.util.StrUtil;
import com.smart.hospital.common.core.constant.CommonConstants;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author giaogiao
* @date 2018/9/13
*/
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TenantContextHolderFilter extends GenericFilterBean {
@Override
@SneakyThrows
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String tenantId = request.getHeader(CommonConstants.TENANT_ID);
log.debug("获取header中的租户ID为:{}", tenantId);
if (StrUtil.isNotBlank(tenantId)) {
TenantContextHolder.setTenantId(Integer.parseInt(tenantId));
} else {
TenantContextHolder.setTenantId(CommonConstants.TENANT_ID_1);
}
filterChain.doFilter(request, response);
TenantContextHolder.clear();
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.smart.hospital.common.data.cache.RedisTemplateConfig,\
com.smart.hospital.common.data.cache.RedisCacheManagerConfig,\
com.smart.hospital.common.data.cache.RedisCacheAutoConfiguration,\
com.smart.hospital.common.data.tenant.SmartTenantConfigProperties,\
com.smart.hospital.common.data.tenant.TenantContextHolderFilter,\
com.smart.hospital.common.data.tenant.SmartFeignTenantConfiguration,\
com.smart.hospital.common.data.mybatis.MybatisPlusConfig,\
com.smart.hospital.common.data.resolver.WebMvcConfig
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-hospital-common</artifactId>
<version>3.3.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-datasource</artifactId>
<packaging>jar</packaging>
<description>pigx 动态切换数据源</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!--common-->
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-core</artifactId>
</dependency>
<!--数据操作-->
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-data</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-swagger</artifactId>
</dependency>
<!--安全模块-->
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-security</artifactId>
</dependency>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-log</artifactId>
</dependency>
<!--代码生成模板引擎-->
<dependency>
<artifactId>velocity</artifactId>
<groupId>org.apache.velocity</groupId>
<version>${velocity.version}</version>
</dependency>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--undertow容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.smart.hospital.common.datasource;
import com.smart.hospital.common.datasource.config.DruidDataSourceProperties;
import lombok.AllArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author Lucky
* @date 2019-05-18
* <p>
* 自动配置类
*/
@AllArgsConstructor
@EnableConfigurationProperties({DruidDataSourceProperties.class})
public class DataSourceAutoConfiguration {
}
package com.smart.hospital.common.datasource.annotation;
import com.smart.hospital.common.datasource.config.DynamicDataSourceConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author Lucky
* @date 2019-05-18
* <p>
* 开启动态数据源
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({DynamicDataSourceConfig.class})
public @interface EnableDynamicDataSource {
}
package com.smart.hospital.common.datasource.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author giaogiao
* @date 2019-05-14
* <p>
* 参考DruidDataSourceWrapper
*/
@Data
@Component
@ConfigurationProperties("spring.datasource.druid")
public class DruidDataSourceProperties {
private String username;
private String password;
private String url;
private String driverClassName;
}
package com.smart.hospital.common.datasource.config;
import com.smart.hospital.common.datasource.support.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @author giaogiao
* @date 2019-03-31
* <p>
* 动态数据源类
*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 指定路由Key
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
\ No newline at end of file
package com.smart.hospital.common.datasource.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.mysql.cj.jdbc.Driver;
import com.smart.hospital.common.datasource.support.DataSourceConstants;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.transaction.ChainedTransactionManager;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* @author giaogiao
* @date 2019-03-31
* <p>
* 动态数据源切换配置
*/
@Slf4j
@Configuration
@AllArgsConstructor
public class DynamicDataSourceConfig implements TransactionManagementConfigurer {
private final Map<Object, Object> dataSourceMap = new HashMap<>(8);
private final DruidDataSourceProperties dataSourceProperties;
private final StringEncryptor stringEncryptor;
@Bean("dynamicDataSource")
public DynamicDataSource dataSource() {
DynamicDataSource ds = new DynamicDataSource();
DruidDataSource cads = new DruidDataSource();
cads.setUrl(dataSourceProperties.getUrl());
cads.setDriverClassName(dataSourceProperties.getDriverClassName());
cads.setUsername(dataSourceProperties.getUsername());
cads.setPassword(dataSourceProperties.getPassword());
ds.setDefaultTargetDataSource(cads);
dataSourceMap.put(0, cads);
ds.setTargetDataSources(dataSourceMap);
return ds;
}
/**
* 组装默认配置的数据源,查询数据库配置
*/
@PostConstruct
public void init() {
DriverManagerDataSource dds = new DriverManagerDataSource();
dds.setUrl(dataSourceProperties.getUrl());
dds.setDriverClassName(dataSourceProperties.getDriverClassName());
dds.setUsername(dataSourceProperties.getUsername());
dds.setPassword(dataSourceProperties.getPassword());
List<Map<String, Object>> dbList = new JdbcTemplate(dds).queryForList(DataSourceConstants.QUERY_DS_SQL);
log.info("开始 -> 初始化动态数据源");
Optional.of(dbList).ifPresent(list -> list.forEach(db -> {
log.info("数据源:{}", db.get(DataSourceConstants.DS_NAME));
DruidDataSource ds = new DruidDataSource();
ds.setUrl(String.valueOf(db.get(DataSourceConstants.DS_JDBC_URL)));
ds.setDriverClassName(Driver.class.getName());
ds.setUsername((String) db.get(DataSourceConstants.DS_USER_NAME));
String decPwd = stringEncryptor.decrypt((String) db.get(DataSourceConstants.DS_USER_PWD));
ds.setPassword(decPwd);
dataSourceMap.put(db.get(DataSourceConstants.DS_ROUTE_KEY), ds);
}));
log.info("完毕 -> 初始化动态数据源,共计 {} 条", dataSourceMap.size());
}
/**
* 重新加载数据源配置
*/
public Boolean reload() {
init();
DynamicDataSource dataSource = dataSource();
dataSource.setTargetDataSources(dataSourceMap);
dataSource.afterPropertiesSet();
return Boolean.FALSE;
}
@Bean
public PlatformTransactionManager txManager() {
DataSourceTransactionManager transactionManager
= new DataSourceTransactionManager(dataSource());
return new ChainedTransactionManager(transactionManager);
}
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return txManager();
}
}
\ No newline at end of file
package com.smart.hospital.common.datasource.config;
import com.smart.hospital.common.datasource.support.DynamicDataSourceContextHolder;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author giaogiao
* @date 2019-05-18
* <p>
* 动态数据源拦截器
*/
@Slf4j
@Component
@AllArgsConstructor
public class DynamicDatasourceInterceptor implements HandlerInterceptor {
/**
* TODO 根据上下文判断路由哪个数据库
*
* @param request
* @param response
* @param handler
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
DynamicDataSourceContextHolder.setDataSourceType(1);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
package com.smart.hospital.common.datasource.support;
/**
* @author giaogiao
* @date 2019-04-01
* <p>
* 数据源相关常量
*/
public interface DataSourceConstants {
/**
* 查询数据源的SQL
*/
String QUERY_DS_SQL = "select * from gen_datasource_conf where del_flag = 0";
/**
* 动态路由KEY
*/
String DS_ROUTE_KEY = "id";
/**
* 数据源名称
*/
String DS_NAME = "name";
/**
* jdbcurl
*/
String DS_JDBC_URL = "url";
/**
* 用户名
*/
String DS_USER_NAME = "username";
/**
* 密码
*/
String DS_USER_PWD = "password";
}
package com.smart.hospital.common.datasource.support;
import lombok.experimental.UtilityClass;
/**
* @author giaogiao
* @date 2019-05-18
* <p>
* 根据当前线程来选择具体的数据源
*/
@UtilityClass
public class DynamicDataSourceContextHolder {
private final ThreadLocal<Integer> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 提供给AOP去设置当前的线程的数据源的信息
*
* @param dataSourceType
*/
public void setDataSourceType(Integer dataSourceType) {
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 提供给AbstractRoutingDataSource的实现类,通过key选择数据源
*
* @return
*/
public Integer getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 使用默认的数据源
*
*/
public void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
\ No newline at end of file
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.smart.hospital.common.datasource.DataSourceAutoConfiguration
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-hospital-common</artifactId>
<version>3.3.0</version>
</parent>
<artifactId>smart-common-gateway</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.smart.hospital</groupId>
<artifactId>smart-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
package com.smart.hospital.common.gateway.annotation;
import com.smart.hospital.common.gateway.configuration.DynamicRouteAutoConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* @author giaogiao
* @date 2018/11/5
* <p>
* 开启动态路由
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(DynamicRouteAutoConfiguration.class)
public @interface EnableSmartDynamicRoute {
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment