├── docs ├── scripts │ ├── build.sh │ ├── run.sh │ └── sql │ │ └── schema.sql └── images │ ├── create_token_test.png │ ├── oauth-client-flow.png │ ├── oauth-password-flow.png │ ├── create_token_test_result.png │ └── oauth-multi-services-flow.png ├── mini-admin ├── doc │ ├── admin_uri.png │ ├── admin_login.png │ ├── admin_details.png │ ├── admin_threaddump.png │ ├── admin_wallboard.png │ └── admin_applications.png ├── src │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── hiling │ │ │ └── admin │ │ │ └── AdminApplicationTests.java │ └── main │ │ ├── resources │ │ └── bootstrap.yml │ │ └── java │ │ └── com │ │ └── github │ │ └── hiling │ │ └── admin │ │ ├── AdminApplication.java │ │ └── config │ │ └── SecuritySecureConfig.java ├── readme.md └── pom.xml ├── mini-config ├── config-common │ ├── common-redis-dev.yml │ ├── common-dev.yml │ ├── common-discovery-dev.yml │ └── common-mysql-dev.yml ├── src │ ├── main │ │ ├── resources │ │ │ ├── bootstrap-dev.yml │ │ │ └── application.yml │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── hiling │ │ │ └── config │ │ │ └── ConfigApplication.java │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── hiling │ │ └── config │ │ └── DemoApplicationTests.java ├── config-repo │ ├── mini-admin-dev.yml │ ├── item-service-dev.yml │ ├── user-service-dev.yml │ ├── mini-discovery-dev.yml │ ├── mini-gateway-dev.yml │ └── auth-service-dev.yml └── pom.xml ├── mini-common ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── spring.factories │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── hiling │ │ │ └── common │ │ │ ├── exception │ │ │ ├── ExceptionMessage.java │ │ │ ├── BusinessException.java │ │ │ └── ExceptionResult.java │ │ │ ├── utils │ │ │ ├── UuidUtils.java │ │ │ ├── StringUtils.java │ │ │ ├── json │ │ │ │ ├── serializer │ │ │ │ │ ├── LocalDateSerializer.java │ │ │ │ │ ├── LocalTimeSerializer.java │ │ │ │ │ ├── LocalDateTimeSerializer.java │ │ │ │ │ ├── DateTimeSerializer.java │ │ │ │ │ └── DateSerializer.java │ │ │ │ └── JsonUtils.java │ │ │ ├── DateTimeUtils.java │ │ │ └── AddressUtils.java │ │ │ ├── constant │ │ │ └── ServiceNames.java │ │ │ ├── config │ │ │ ├── SwaggerProperties.java │ │ │ ├── BaseConfig.java │ │ │ ├── CorsConfig.java │ │ │ ├── SwaggerConfig.java │ │ │ └── ExceptionHandler.java │ │ │ ├── web │ │ │ └── DemoController.java │ │ │ └── interceptor │ │ │ ├── ApiFilter.java │ │ │ ├── RequestWrapper.java │ │ │ └── ApiLogAspect.java │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── hiling │ │ └── common │ │ ├── ApplicationTests.java │ │ └── utils │ │ ├── TimeTest.java │ │ ├── AddressUtilsTest.java │ │ └── SnowflakeIdWorkerTest.java └── pom.xml ├── modules ├── mini-user │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── github │ │ │ │ │ └── hiling │ │ │ │ │ └── user │ │ │ │ │ ├── service │ │ │ │ │ ├── UserService.java │ │ │ │ │ └── DirectReceiver.java │ │ │ │ │ ├── mapper │ │ │ │ │ └── UserMapper.java │ │ │ │ │ ├── config │ │ │ │ │ ├── MyBatisPlusConfig.java │ │ │ │ │ ├── ExceptionConfig.java │ │ │ │ │ └── DirectRabbitConfig.java │ │ │ │ │ ├── model │ │ │ │ │ └── User.java │ │ │ │ │ ├── UserApplication.java │ │ │ │ │ └── controller │ │ │ │ │ └── UserController.java │ │ │ └── resources │ │ │ │ ├── mapper │ │ │ │ └── UserMapper.xml │ │ │ │ └── bootstrap.yml │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── hiling │ │ │ └── user │ │ │ └── UserApplicationTests.java │ └── pom.xml ├── mini-auth │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── github │ │ │ │ │ └── hiling │ │ │ │ │ └── auth │ │ │ │ │ ├── service │ │ │ │ │ ├── AccountService.java │ │ │ │ │ ├── RefreshTokenService.java │ │ │ │ │ ├── AccessTokenService.java │ │ │ │ │ └── impl │ │ │ │ │ │ ├── RefreshTokenServiceImpl.java │ │ │ │ │ │ └── AccountServiceImpl.java │ │ │ │ │ ├── modules │ │ │ │ │ ├── user │ │ │ │ │ │ ├── service │ │ │ │ │ │ │ ├── PasswordHash.java │ │ │ │ │ │ │ ├── UserService.java │ │ │ │ │ │ │ └── impl │ │ │ │ │ │ │ │ ├── PasswordPlaintext.java │ │ │ │ │ │ │ │ ├── PasswordBCrypt.java │ │ │ │ │ │ │ │ ├── PasswordMD5.java │ │ │ │ │ │ │ │ ├── PasswordHashFactory.java │ │ │ │ │ │ │ │ ├── UserServiceImpl.java │ │ │ │ │ │ │ │ └── PasswordPBKDF2.java │ │ │ │ │ │ └── mapper │ │ │ │ │ │ │ └── UserMapper.java │ │ │ │ │ └── client │ │ │ │ │ │ ├── service │ │ │ │ │ │ ├── ClientService.java │ │ │ │ │ │ └── impl │ │ │ │ │ │ │ └── ClientServiceImpl.java │ │ │ │ │ │ ├── mapper │ │ │ │ │ │ └── ClientMapper.java │ │ │ │ │ │ ├── model │ │ │ │ │ │ └── Client.java │ │ │ │ │ │ └── controller │ │ │ │ │ │ └── ClientController.java │ │ │ │ │ ├── model │ │ │ │ │ ├── Account.java │ │ │ │ │ ├── RevokeToken.java │ │ │ │ │ ├── RefreshToken.java │ │ │ │ │ └── AccessToken.java │ │ │ │ │ ├── constant │ │ │ │ │ ├── RedisNamespaces.java │ │ │ │ │ ├── GrantType.java │ │ │ │ │ └── ErrorMessage.java │ │ │ │ │ ├── mapper │ │ │ │ │ ├── AccessTokenMapper.java │ │ │ │ │ └── RefreshTokenMapper.java │ │ │ │ │ ├── AuthApplication.java │ │ │ │ │ ├── task │ │ │ │ │ ├── RemoveTokenRunner.java │ │ │ │ │ ├── RemoveRefreshTokenThread.java │ │ │ │ │ ├── BaseRemoveTokenThread.java │ │ │ │ │ └── RemoveAccessTokenThread.java │ │ │ │ │ ├── config │ │ │ │ │ ├── MyBatisUserConfig.java │ │ │ │ │ ├── MyBatisOAuthConfig.java │ │ │ │ │ └── MyBatisClientConfig.java │ │ │ │ │ └── controller │ │ │ │ │ └── TokenController.java │ │ │ └── resources │ │ │ │ ├── mapper │ │ │ │ ├── UserMapper.xml │ │ │ │ ├── AccessTokenMapper.xml │ │ │ │ ├── RefreshTokenMapper.xml │ │ │ │ └── ClientMapper.xml │ │ │ │ └── bootstrap.yml │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── hiling │ │ │ └── auth │ │ │ └── AuthApplicationTests.java │ ├── doc │ │ ├── getJwtToken.puml │ │ └── createToken.puml │ └── pom.xml ├── mini-item │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── github │ │ │ │ │ └── hiling │ │ │ │ │ └── item │ │ │ │ │ ├── config │ │ │ │ │ ├── MyBatisPlusConfig.java │ │ │ │ │ └── DirectRabbitConfig.java │ │ │ │ │ ├── ItemApplication.java │ │ │ │ │ └── controller │ │ │ │ │ └── ItemController.java │ │ │ └── resources │ │ │ │ └── bootstrap.yml │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── hiling │ │ │ └── item │ │ │ └── ItemApplicationTests.java │ └── pom.xml └── pom.xml ├── mini-gateway ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── hiling │ │ │ │ └── gateway │ │ │ │ ├── config │ │ │ │ ├── ExceptionControllerAdvice.java │ │ │ │ └── RedisConfig.java │ │ │ │ ├── GatewayApplication.java │ │ │ │ └── filter │ │ │ │ ├── PostFilter.java │ │ │ │ ├── UriRedirectFilter.java │ │ │ │ └── AuthFilter.java │ │ └── resources │ │ │ └── bootstrap.yml │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── hiling │ │ └── gateway │ │ └── GatewayApplicationTests.java ├── run │ └── run.sh └── pom.xml ├── mini-discovery ├── src │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── hiling │ │ │ └── discovery │ │ │ └── DiscoveryApplicationTests.java │ └── main │ │ ├── resources │ │ └── bootstrap.yml │ │ └── java │ │ └── com │ │ └── github │ │ └── hiling │ │ └── discovery │ │ └── DiscoveryApplication.java ├── readme.md └── pom.xml ├── .gitignore ├── LICENSE ├── mini-oauth-client ├── src │ └── main │ │ └── java │ │ └── com │ │ └── github │ │ └── hiling │ │ └── oauth │ │ ├── client │ │ └── web │ │ │ ├── UserInfo.java │ │ │ └── BaseController.java │ │ └── JwtUtils.java └── pom.xml └── readme.md /docs/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | mvn clean package -Puat -Dmaven.test.skip=true -------------------------------------------------------------------------------- /mini-admin/doc/admin_uri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/mini-admin/doc/admin_uri.png -------------------------------------------------------------------------------- /mini-admin/doc/admin_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/mini-admin/doc/admin_login.png -------------------------------------------------------------------------------- /mini-config/config-common/common-redis-dev.yml: -------------------------------------------------------------------------------- 1 | 2 | spring: 3 | redis: 4 | host: 127.0.0.1 5 | port: 6379 -------------------------------------------------------------------------------- /docs/images/create_token_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/docs/images/create_token_test.png -------------------------------------------------------------------------------- /docs/images/oauth-client-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/docs/images/oauth-client-flow.png -------------------------------------------------------------------------------- /mini-admin/doc/admin_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/mini-admin/doc/admin_details.png -------------------------------------------------------------------------------- /docs/images/oauth-password-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/docs/images/oauth-password-flow.png -------------------------------------------------------------------------------- /mini-admin/doc/admin_threaddump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/mini-admin/doc/admin_threaddump.png -------------------------------------------------------------------------------- /mini-admin/doc/admin_wallboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/mini-admin/doc/admin_wallboard.png -------------------------------------------------------------------------------- /mini-config/src/main/resources/bootstrap-dev.yml: -------------------------------------------------------------------------------- 1 | # bootstrap优先于application加载,因此密钥需要在bootstrap中配置 2 | encrypt: 3 | key: hiling -------------------------------------------------------------------------------- /mini-admin/doc/admin_applications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/mini-admin/doc/admin_applications.png -------------------------------------------------------------------------------- /docs/images/create_token_test_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/docs/images/create_token_test_result.png -------------------------------------------------------------------------------- /docs/images/oauth-multi-services-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiling/mini-platform/HEAD/docs/images/oauth-multi-services-flow.png -------------------------------------------------------------------------------- /mini-common/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.github.hiling.common.web.DemoController -------------------------------------------------------------------------------- /modules/mini-user/src/main/java/com/github/hiling/user/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user.service; 2 | 3 | public interface UserService { 4 | } 5 | -------------------------------------------------------------------------------- /docs/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #启动服务 3 | rm -f tpid 4 | java -Dloader.path="libs/" -jar /opt/miniapi/discovery/discovery.jar --spring.profiles.active=discovery1 > /opt/miniapi/discovery/discovery1.log 5 | echo $! > tpid 6 | echo Start Success! -------------------------------------------------------------------------------- /mini-config/config-common/common-dev.yml: -------------------------------------------------------------------------------- 1 | #线程数默认为200,开发环境不需要这么高,节省内存 2 | server: 3 | tomcat: 4 | max-threads: 5 5 | 6 | management: 7 | endpoint: 8 | health: 9 | show-details: ALWAYS 10 | endpoints: 11 | web: 12 | exposure: 13 | include: '*' -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/service/AccountService.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.service; 2 | 3 | import com.github.hiling.auth.model.Account; 4 | 5 | public interface AccountService { 6 | Account login(String username, String password); 7 | } 8 | -------------------------------------------------------------------------------- /modules/mini-user/src/main/java/com/github/hiling/user/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.github.hiling.user.model.User; 5 | 6 | public interface UserMapper extends BaseMapper { 7 | } 8 | -------------------------------------------------------------------------------- /modules/mini-user/src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/exception/ExceptionMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.exception; 2 | 3 | /** 4 | * Author by hiling, Email admin@mn-soft.com, Date on 10/11/2018. 5 | */ 6 | public interface ExceptionMessage { 7 | int value(); 8 | String getMessage(); 9 | } 10 | -------------------------------------------------------------------------------- /modules/mini-auth/src/test/java/com/github/hiling/auth/AuthApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth; 2 | 3 | import org.junit.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class AuthApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mini-admin/src/test/java/com/github/hiling/admin/AdminApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.admin; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class AdminApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mini-config/src/test/java/com/github/hiling/config/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.config; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DemoApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/user/service/PasswordHash.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.user.service; 2 | 3 | /** 4 | * Author by hiling, Email admin@mn-soft.com, Date on 12/30/2018. 5 | */ 6 | public interface PasswordHash { 7 | boolean validate(String password, String salt, String hashPassword); 8 | } 9 | -------------------------------------------------------------------------------- /modules/mini-user/src/main/java/com/github/hiling/user/config/MyBatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user.config; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @MapperScan("com.github.hiling.user.mapper") 8 | public class MyBatisPlusConfig { 9 | } 10 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/user/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.user.service; 2 | 3 | import com.github.hiling.auth.model.Account; 4 | 5 | /** 6 | * Author by hiling, Email admin@mn-soft.com, Date on 11/29/2018. 7 | */ 8 | public interface UserService { 9 | Account login(String username, String password); 10 | } 11 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/UuidUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * Author by hiling, Email admin@mn-soft.com, Date on 10/7/2018. 7 | */ 8 | public class UuidUtils { 9 | 10 | public static synchronized String getUuid() { 11 | return UUID.randomUUID().toString().replace("-", ""); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/model/Account.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Account { 7 | private Long userId; 8 | private String username; 9 | private String password; 10 | 11 | /** 12 | * 授权范围 13 | */ 14 | private String scope; 15 | 16 | /** 17 | * 密码加盐 18 | */ 19 | private String salt; 20 | } 21 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /modules/mini-user/src/main/java/com/github/hiling/user/config/ExceptionConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user.config; 2 | 3 | import com.github.hiling.common.config.ExceptionHandler; 4 | import org.springframework.web.bind.annotation.RestControllerAdvice; 5 | 6 | /** 7 | * Author by hiling, Email admin@mn-soft.com, Date on 12/13/2018. 8 | */ 9 | @RestControllerAdvice 10 | public class ExceptionConfig extends ExceptionHandler { 11 | } 12 | -------------------------------------------------------------------------------- /modules/mini-item/src/main/java/com/github/hiling/item/config/MyBatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.item.config; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | /** 7 | * Author by hiling, Email admin@mn-soft.com, Date on 10/12/2018. 8 | */ 9 | @Configuration 10 | @MapperScan("com.github.hiling.item.mapper") 11 | public class MyBatisPlusConfig { 12 | } 13 | -------------------------------------------------------------------------------- /modules/mini-user/src/main/java/com/github/hiling/user/model/User.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user.model; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableField; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author hiling 8 | */ 9 | @Data 10 | public class User { 11 | 12 | private Integer id; 13 | private String username; 14 | private String nickName; 15 | 16 | @TableField(select = false) 17 | private String password; 18 | } -------------------------------------------------------------------------------- /mini-gateway/src/main/java/com/github/hiling/gateway/config/ExceptionControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.gateway.config; 2 | 3 | import com.github.hiling.common.config.ExceptionHandler; 4 | import org.springframework.web.bind.annotation.RestControllerAdvice; 5 | 6 | /** 7 | * Author by hiling, Email admin@mn-soft.com, Date on 12/11/2018. 8 | */ 9 | @RestControllerAdvice 10 | public class ExceptionControllerAdvice extends ExceptionHandler{ 11 | 12 | } 13 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/model/RevokeToken.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.model; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | /** 10 | * Author by hiling, Email admin@mn-soft.com, Date on 10/12/2018. 11 | */ 12 | @Getter 13 | @Setter 14 | @Builder 15 | public class RevokeToken { 16 | String clientId; 17 | Long userId; 18 | LocalDateTime time; 19 | } -------------------------------------------------------------------------------- /mini-admin/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: mini-admin 4 | profiles: 5 | active: dev 6 | 7 | --- 8 | # 如果使用本地文件,profiles.active需要是native,且searchLocations不能使用通配符 9 | spring: 10 | profiles: dev 11 | cloud: 12 | config: 13 | label: master 14 | name: mini-admin,common-discovery #对应的是配置文件规则中的{application}部分,多个用逗号隔开 15 | profile: dev #对应的是配置文件规则中的{profile}部分,多个用逗号隔开 16 | uri: http://127.0.0.1:8763/ #配置中心的地址 17 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/constant/RedisNamespaces.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.constant; 2 | 3 | /** 4 | * Author by hiling, Email admin@mn-soft.com, Date on 10/12/2018. 5 | */ 6 | public class RedisNamespaces { 7 | 8 | /** 9 | * oauth access token 10 | */ 11 | public static final String ACCESS_TOKEN = "oat:"; 12 | 13 | /** 14 | * oauth refresh token 15 | */ 16 | public static final String REFRESH_TOKEN = "ort:"; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /mini-discovery/src/test/java/com/github/hiling/discovery/DiscoveryApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.discovery; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class DiscoveryApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mini-config/config-repo/mini-admin-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8769 3 | spring: 4 | security: 5 | user: 6 | name: admin 7 | password: hiling 8 | 9 | eureka: 10 | instance: 11 | metadata-map: 12 | user: 13 | name: ${spring.security.user.name} 14 | password: ${spring.security.user.password} 15 | 16 | # 钉钉通知 17 | notifier: 18 | webhook: 19 | webhookUrl: 20 | - "https://oapi.dingtalk.com/robot/send?access_token=xxx" 21 | adminServerUrl: "http://127.0.0.1:8769" -------------------------------------------------------------------------------- /mini-discovery/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: mini-discovery 4 | profiles: 5 | active: dev 6 | 7 | --- 8 | # 如果使用本地文件,profiles.active需要是native,且searchLocations不能使用通配符 9 | spring: 10 | profiles: dev 11 | cloud: 12 | config: 13 | label: master 14 | #对应的是配置文件规则中的{application}部分,多个用逗号隔开 15 | name: mini-discovery,common,common-discovery 16 | profile: dev #对应的是配置文件规则中的{profile}部分,多个用逗号隔开 17 | uri: http://127.0.0.1:8763/ #配置中心的地址 18 | -------------------------------------------------------------------------------- /mini-gateway/run/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #启动服务 3 | APP_NAME=gateway.jar 4 | rm -f tpid 5 | nohup java -Dloader.path="lib/" -jar /opt/miniapi/gateway/$APP_NAME --spring.profiles.active=node1 > /opt/miniapi/gateway/node1.log 6 | nohup java -Dloader.path="lib/" -jar /opt/miniapi/gateway/$APP_NAME --spring.profiles.active=node2 > /opt/miniapi/gateway/node2.log 7 | nohup java -Dloader.path="lib/" -jar /opt/miniapi/gateway/$APP_NAME --spring.profiles.active=node3 > /opt/miniapi/gateway/node3.log 8 | echo $! > tpid 9 | echo Start Success! -------------------------------------------------------------------------------- /mini-config/src/main/java/com/github/hiling/config/ConfigApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.config; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.config.server.EnableConfigServer; 6 | 7 | @EnableConfigServer 8 | @SpringBootApplication 9 | public class ConfigApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ConfigApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /mini-common/src/test/java/com/github/hiling/common/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | 9 | /** 10 | * Author by hiling, Email admin@mn-soft.com, Date on 10/8/2018. 11 | */ 12 | @Slf4j 13 | @RunWith(SpringRunner.class) 14 | public class ApplicationTests { 15 | 16 | @Test 17 | public void contextLoads() { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: auth-service 4 | profiles: 5 | active: dev 6 | 7 | --- 8 | # 如果使用本地文件,profiles.active需要是native,且searchLocations不能使用通配符 9 | spring: 10 | profiles: dev 11 | cloud: 12 | config: 13 | label: master 14 | #对应的是配置文件规则中的{application}部分,多个用逗号隔开 15 | name: auth-service,common,common-redis,common-mysql,common-discovery 16 | profile: dev #对应的是配置文件规则中的{profile}部分,多个用逗号隔开 17 | uri: http://127.0.0.1:8763/ #配置中心的地址 18 | -------------------------------------------------------------------------------- /modules/mini-item/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: item-service 4 | profiles: 5 | active: dev 6 | 7 | --- 8 | # 如果使用本地文件,profiles.active需要是native,且searchLocations不能使用通配符 9 | spring: 10 | profiles: dev 11 | cloud: 12 | config: 13 | #对应的是配置文件规则中的{application}部分,多个用逗号隔开 14 | name: item-service,common,common-redis,common-mysql,common-discovery 15 | profile: dev #对应的是配置文件规则中的{profile}部分,多个用逗号隔开 16 | uri: http://127.0.0.1:8763/ #配置中心的地址 17 | label: master 18 | -------------------------------------------------------------------------------- /modules/mini-auth/doc/getJwtToken.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | |客户端| 3 | start 4 | repeat 5 | : 获取AccessToken; 6 | 7 | |#AntiqueWhite|认证服务器| 8 | 9 | :验证AccessToken; 10 | note right 11 | 验证AccessToken是否**有效**; 12 | 有效时说明是正常登陆用户; 13 | ---- 14 | 当返回JWT时说明该AccessToken有效; 15 | end note 16 | 17 | if (Token是否为空) then (空) 18 | :返回Null; 19 | else (非空) 20 | if(在Redis中获取该Token) then (存在) 21 | : 返回JWT Token; 22 | else (不存在) 23 | :返回Null; 24 | endif 25 | endif 26 | |客户端| 27 | :授权结束; 28 | repeat while (重新授权?) 29 | stop 30 | @enduml -------------------------------------------------------------------------------- /modules/mini-user/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: user-service 4 | profiles: 5 | active: dev 6 | 7 | --- 8 | # 如果使用本地文件,profiles.active需要是native,且searchLocations不能使用通配符 9 | spring: 10 | profiles: dev 11 | cloud: 12 | config: 13 | #对应的是配置文件规则中的{application}部分,多个用逗号隔开 14 | name: user-service,common,common-redis,common-mysql,common-discovery 15 | #对应的是配置文件规则中的{profile}部分,多个用逗号隔开 16 | profile: dev 17 | uri: http://127.0.0.1:8763/ #配置中心的地址 18 | label: master 19 | -------------------------------------------------------------------------------- /mini-gateway/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: mini-gateway 4 | profiles: 5 | active: dev 6 | 7 | --- 8 | # 如果使用本地文件,profiles.active需要是native,且searchLocations不能使用通配符 9 | spring: 10 | profiles: dev 11 | cloud: 12 | config: 13 | label: master 14 | # 对应的是配置文件规则中的{application}部分,多个用逗号隔开 15 | # 网关注册到注册中心是为了统一监控,网关通过nginx做负载均衡 16 | name: mini-gateway,common,common-redis,common-discovery 17 | profile: dev #对应的是配置文件规则中的{profile}部分,多个用逗号隔开 18 | uri: http://127.0.0.1:8763/ #配置中心的地址 -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/service/RefreshTokenService.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.service; 2 | 3 | import com.github.hiling.auth.model.RevokeToken; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Author by hiling, Email admin@mn-soft.com, Date on 10/12/2018. 9 | */ 10 | public interface RefreshTokenService { 11 | 12 | List getRevokeRefreshToken(List list); 13 | 14 | void batchDeleteByRefreshToken(List refreshTokenList); 15 | 16 | void deleteExpiredRefreshToken(); 17 | } 18 | -------------------------------------------------------------------------------- /mini-admin/readme.md: -------------------------------------------------------------------------------- 1 | # Spring Boot Admin使用说明 2 | 3 | * 启动admin项目后, 浏览器输入:http://localhost:8769/ 4 | ![admin-login](./doc/admin_login.png "登陆") 5 | 6 | * 用户名: admin 密码:hiling(注:用户名密码可在application-dev.properties中修改) 7 | 8 | * 应用墙 9 | ![admin-wallboard](./doc/admin_wallboard.png "应用墙") 10 | 11 | * 应用 12 | ![admin-applications](./doc/admin_applications.png "应用") 13 | 14 | * 应用详情 15 | ![admin-details](./doc/admin_details.png "应用详情") 16 | 17 | * 性能监控 18 | ![admin-uri](./doc/admin_uri.png "性能监控") 19 | 20 | * 线程转储 21 | ![admin_threaddump](./doc/admin_threaddump.png "线程转储") -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/constant/ServiceNames.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.constant; 2 | 3 | /** 4 | * Author by hiling, Email admin@mn-soft.com, Date on 10/16/2018. 5 | */ 6 | public class ServiceNames { 7 | 8 | private ServiceNames() { 9 | } 10 | 11 | public static final String AUTH_SERVICE = "auth-service"; 12 | public static final String GATEWAY_SERVICE = "gateway-service"; 13 | public static final String USER_SERVICE = "user-service"; 14 | public static final String ITEM_SERVICE = "item-service"; 15 | } -------------------------------------------------------------------------------- /mini-discovery/src/main/java/com/github/hiling/discovery/DiscoveryApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.discovery; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class DiscoveryApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DiscoveryApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/user/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.user.mapper; 2 | 3 | import com.github.hiling.auth.model.Account; 4 | import org.apache.ibatis.annotations.Param; 5 | import org.springframework.stereotype.Repository; 6 | 7 | /** 8 | * Author by hiling, Email admin@mn-soft.com, Date on 11/29/2018. 9 | */ 10 | @Repository 11 | public interface UserMapper { 12 | Account login(@Param("sql") String sql, @Param("username") String username, @Param("password") String password); 13 | } 14 | -------------------------------------------------------------------------------- /mini-common/src/test/java/com/github/hiling/common/utils/TimeTest.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | import java.time.LocalDate; 9 | 10 | @Slf4j 11 | @RunWith(SpringRunner.class) 12 | public class TimeTest { 13 | 14 | @Test 15 | public void contextLoads() { 16 | log.info("月同比:{}", LocalDate.now().minusYears(1).minusMonths(1)); 17 | //log.info("HostName:{}", AddressUtils.getHostName()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mini-admin/src/main/java/com/github/hiling/admin/AdminApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.admin; 2 | 3 | import de.codecentric.boot.admin.server.config.EnableAdminServer; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | 8 | @SpringBootApplication 9 | @EnableAdminServer 10 | @EnableDiscoveryClient 11 | public class AdminApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(AdminApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/user/service/impl/PasswordPlaintext.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.user.service.impl; 2 | 3 | import com.github.hiling.common.utils.StringUtils; 4 | import com.github.hiling.auth.modules.user.service.PasswordHash; 5 | 6 | /** 7 | * Author by hiling, Email admin@mn-soft.com, Date on 12/31/2018. 8 | */ 9 | public class PasswordPlaintext implements PasswordHash { 10 | 11 | @Override 12 | public boolean validate(String password, String salt, String hashPassword) { 13 | return StringUtils.equals(password, hashPassword); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /modules/mini-user/src/main/java/com/github/hiling/user/service/DirectReceiver.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user.service; 2 | 3 | import org.springframework.amqp.rabbit.annotation.RabbitHandler; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.Map; 8 | 9 | @Component 10 | @RabbitListener(queues = "TestDirectQueue")//监听的队列名称 TestDirectQueue 11 | public class DirectReceiver { 12 | @RabbitHandler 13 | public void process(Map testMessage) { 14 | System.out.println("DirectReceiver消费者收到消息 : " + testMessage.toString()); 15 | } 16 | } -------------------------------------------------------------------------------- /mini-common/src/test/java/com/github/hiling/common/utils/AddressUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | 9 | @Slf4j 10 | @RunWith(SpringRunner.class) 11 | public class AddressUtilsTest { 12 | @Test 13 | public void contextLoads() { 14 | log.info("HostAddress:{}", AddressUtils.getHostAddress()); 15 | log.info("HostIp:{}", AddressUtils.getHostIp()); 16 | //log.info("HostName:{}", AddressUtils.getHostName()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | /** 7 | * Author by hiling, Email admin@mn-soft.com, Date on 12/13/2018. 8 | */ 9 | public class StringUtils extends org.apache.commons.lang3.StringUtils { 10 | public static List stringToList(String string) { 11 | 12 | String[] scopes = org.apache.commons.lang3.StringUtils.split(string, ","); 13 | if (scopes == null) { 14 | return null; 15 | } 16 | List scopeList = Arrays.asList(scopes); 17 | return scopeList; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/model/RefreshToken.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.model; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * Author by hiling, Email admin@mn-soft.com, Date on 10/11/2018. 10 | */ 11 | @Data 12 | @Accessors(chain = true) 13 | public class RefreshToken { 14 | 15 | private Long id; 16 | 17 | private String clientId; 18 | 19 | private Long userId; 20 | 21 | private String refreshToken; 22 | 23 | private Integer expiresIn; 24 | 25 | private LocalDateTime createTime; 26 | 27 | private LocalDateTime lastUsedTime; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | wsl_share/ 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | *.cmd 18 | */target/ 19 | 20 | # Development Properties 21 | #*-dev.properties 22 | 23 | # Feature Acceptance Test Properties 24 | *-fat*.yaml 25 | *-fat*.properties 26 | 27 | # User Acceptance Test Properties 28 | *-uat*.yaml 29 | *-uat*.properties 30 | 31 | # Production Properties 32 | *-prd*.yaml 33 | *-prd*.properties 34 | 35 | ### NetBeans ### 36 | nbproject/private/ 37 | build/ 38 | nbbuild/ 39 | dist/ 40 | nbdist/ 41 | .nb-gradle/ 42 | 43 | # Mac 44 | .DS_Store 45 | 46 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/mapper/AccessTokenMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.mapper; 2 | 3 | import com.github.hiling.auth.model.AccessToken; 4 | import com.github.hiling.auth.model.RevokeToken; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | public interface AccessTokenMapper { 10 | String getRefreshToken(@Param("accessToken") String accessToken); 11 | 12 | List getRevokeAccessToken(List list); 13 | 14 | Long insert(AccessToken accessToken); 15 | 16 | void batchDeleteByAccessToken(@Param("accessTokenList") List accessTokenList); 17 | 18 | void deleteExpiredAccessToken(); 19 | } 20 | -------------------------------------------------------------------------------- /modules/mini-item/src/main/java/com/github/hiling/item/ItemApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.item; 2 | 3 | import com.github.hiling.common.web.DemoController; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Import; 7 | import springfox.documentation.oas.annotations.EnableOpenApi; 8 | 9 | /** 10 | * @author hiling 11 | */ 12 | @EnableOpenApi 13 | @SpringBootApplication 14 | @Import(value = {DemoController.class}) 15 | public class ItemApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(ItemApplication.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mini-gateway/src/main/java/com/github/hiling/gateway/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 7 | import org.springframework.cloud.openfeign.EnableFeignClients; 8 | 9 | @SpringBootApplication 10 | @EnableZuulProxy 11 | @EnableDiscoveryClient 12 | @EnableFeignClients 13 | public class GatewayApplication { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(GatewayApplication.class, args); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /mini-config/config-repo/item-service-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8089 3 | 4 | spring: 5 | datasource: 6 | driver-class-name: com.mysql.cj.jdbc.Driver 7 | url: jdbc:mysql://127.0.0.1/mini_api_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true 8 | username: root 9 | password: hiling11 10 | #配置rabbitMq 服务器 11 | rabbitmq: 12 | host: 172.16.164.2 13 | port: 5672 14 | username: hiling 15 | password: 123456 16 | #虚拟host 可以不设置,使用server默认host 17 | virtual-host: / 18 | 19 | swagger: 20 | enable: true 21 | application-name: ${spring.application.name} 22 | application-version: 1.0 23 | application-description: 商品服务 24 | try-host: http://127.0.0.1:${server.port} -------------------------------------------------------------------------------- /mini-config/config-repo/user-service-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8088 3 | 4 | spring: 5 | datasource: 6 | driver-class-name: com.mysql.cj.jdbc.Driver 7 | url: jdbc:mysql://127.0.0.1/mini_api_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true 8 | username: root 9 | password: hiling11 10 | #配置rabbitMq 服务器 11 | rabbitmq: 12 | host: 172.16.164.2 13 | port: 5672 14 | username: hiling 15 | password: 123456 16 | #虚拟host 可以不设置,使用server默认host 17 | virtual-host: / 18 | 19 | swagger: 20 | enable: true 21 | application-name: ${spring.application.name} 22 | application-version: 1.0 23 | application-description: 用户服务 24 | try-host: http://127.0.0.1:${server.port} -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/AuthApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.PropertySource; 7 | import org.springframework.web.client.RestTemplate; 8 | import springfox.documentation.oas.annotations.EnableOpenApi; 9 | 10 | @EnableOpenApi 11 | @SpringBootApplication 12 | public class AuthApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(AuthApplication.class, args); 16 | } 17 | 18 | @Bean 19 | RestTemplate restTemplate() { 20 | return new RestTemplate(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/client/service/ClientService.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.client.service; 2 | 3 | import com.github.hiling.auth.modules.client.model.Client; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Author by hiling, Email admin@mn-soft.com, Date on 12/1/2018. 9 | */ 10 | public interface ClientService { 11 | 12 | List getList(String clientId, String clientName, String clientSecret, Integer status, List scope); 13 | 14 | int insert(Client client, String loginUserId, List scope); 15 | 16 | int updateStatus(String clientId, Integer status, String loginUserId, List scope); 17 | 18 | String refreshSecret(String clientId, String clientSecret, String loginUserId, List scope); 19 | } 20 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/user/service/impl/PasswordBCrypt.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.user.service.impl; 2 | 3 | import com.github.hiling.auth.modules.user.service.PasswordHash; 4 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 5 | 6 | /** 7 | * Author by hiling, Email admin@mn-soft.com, Date on 12/30/2018. 8 | */ 9 | public class PasswordBCrypt implements PasswordHash { 10 | 11 | @Override 12 | public boolean validate(String password, String salt, String hashPassword) { 13 | BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); 14 | //加密时使用 15 | //String hashPassword = passwordEncoder.encode(password + salt); 16 | return passwordEncoder.matches(password + salt, hashPassword); 17 | } 18 | } -------------------------------------------------------------------------------- /mini-discovery/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## Apollo配置中心使用该eureka服务的配置: 3 | 1. 确保项目中metaservice与Apollo对应版本的ConfigService中metaservice的逻辑与接口一致; 4 | 2. 修改Apollo中ConfigService的@EnableEurekaServer为@EnableEurekaClient; 5 | 3. 修改Apollo中的ConfigService和AdminService的eureka.client.service-url.default-zone为该eureka服务地址; 6 | 4. 修改Apollo中的Portal的MetaService地址为该eureka服务地址,如:dev.meta=http://localhost:8761; 7 | 5. 修改ApolloConfigDB.serverconfig表中eureka.service.url的值为该eureka服务地址; 8 | 9 | ## Apollo项目说明 10 | ### 项目地址: 11 | https://github.com/ctripcorp/apollo 12 | 13 | ### 服务启动: 14 | 1. 使用Apollo项目scripts/sql中脚本创建数据库; 15 | 2. 修改ConfigService和AdminService中config/application-github.properties中的DB连接字符串,DB为ApolloConfigDB; 16 | 3. 修改Portal中config/application-github.properties中的DB连接字符串,DB为ApolloPortalDB; 17 | 4. 依次启动ConfigService、AdminService、Portal,注意-Dapollo_profile=github及端口占用; -------------------------------------------------------------------------------- /mini-config/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8763 3 | spring: 4 | application: 5 | name: mini-config 6 | profiles: 7 | active: native 8 | 9 | #刷新时,关闭安全验证 10 | management: 11 | security: 12 | enabled: false 13 | 14 | --- 15 | # 如果使用本地文件,profiles.active需要是native,且searchLocations不能使用通配符 16 | spring: 17 | profiles: native 18 | cloud: 19 | config: 20 | server: 21 | native: 22 | #申明本地配置文件的存放位置 23 | searchLocations: /Users/hiling/IdeaProjects/mini-platform/mini-config/config-repo,/Users/hiling/IdeaProjects/mini-platform/mini-config/config-common 24 | 25 | # 配置中心注册到eureka的目的是为了admin可以正常监控,其他服务通过url访问配置中心,配置中心需要通过nginx做负载均衡 26 | eureka: 27 | client: 28 | service-url: 29 | defaultZone: http://127.0.0.1:8761/eureka/ 30 | instance: 31 | instance-id: ${spring.cloud.client.ip-address}:${server.port} -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/service/AccessTokenService.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.service; 2 | 3 | import com.github.hiling.auth.model.AccessToken; 4 | import com.github.hiling.auth.model.RevokeToken; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Author by hiling, Email admin@mn-soft.com, Date on 10/9/2018. 10 | */ 11 | public interface AccessTokenService{ 12 | 13 | String getJwtToken(String accessToken); 14 | 15 | boolean deleteToken(String accessToken); 16 | 17 | List getRevokeAccessToken(List list); 18 | 19 | void deleteExpiredAccessToken(); 20 | 21 | void batchDeleteByAccessToken(List accessTokenList); 22 | 23 | AccessToken createAccessToken(String accessIp, String clientId, String clientSecret, String grantType, String username, String password, String refreshToken); 24 | 25 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/config/SwaggerProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.config; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Getter 9 | @Setter 10 | @Component 11 | @ConfigurationProperties("swagger") 12 | public class SwaggerProperties { 13 | /** 14 | * 是否开启swagger,生产环境一般关闭,所以这里定义一个变量 15 | */ 16 | private Boolean enable; 17 | 18 | /** 19 | * 项目应用名 20 | */ 21 | private String applicationName; 22 | 23 | /** 24 | * 项目版本信息 25 | */ 26 | private String applicationVersion; 27 | 28 | /** 29 | * 项目描述信息 30 | */ 31 | private String applicationDescription; 32 | 33 | /** 34 | * 接口调试地址 35 | */ 36 | private String tryHost; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /modules/mini-user/src/main/java/com/github/hiling/user/UserApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | // import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.cloud.openfeign.EnableFeignClients; 8 | import org.springframework.context.annotation.Import; 9 | import springfox.documentation.oas.annotations.EnableOpenApi; 10 | 11 | @EnableOpenApi 12 | @SpringBootApplication 13 | @EnableDiscoveryClient 14 | @EnableFeignClients 15 | @Import(value = {com.github.hiling.common.web.DemoController.class}) 16 | public class UserApplication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(UserApplication.class, args); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/json/serializer/LocalDateSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils.json.serializer; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | 7 | import java.io.IOException; 8 | import java.time.LocalDate; 9 | import java.time.format.DateTimeFormatter; 10 | 11 | /** 12 | * Author by hiling, Email admin@mn-soft.com, Date on 11/26/2018. 13 | * 适用于jdk1.8及以上 14 | */ 15 | public class LocalDateSerializer extends JsonSerializer { 16 | 17 | private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); 18 | 19 | @Override 20 | public void serialize(LocalDate value, JsonGenerator jgen, SerializerProvider provider) 21 | throws IOException { 22 | jgen.writeString(DATE_FORMATTER.format(value)); 23 | } 24 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/json/serializer/LocalTimeSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils.json.serializer; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | 7 | import java.io.IOException; 8 | import java.time.LocalTime; 9 | import java.time.format.DateTimeFormatter; 10 | 11 | /** 12 | * Author by hiling, Email admin@mn-soft.com, Date on 11/26/2018. 13 | * 适用于jdk1.8及以上 14 | */ 15 | public class LocalTimeSerializer extends JsonSerializer { 16 | 17 | private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); 18 | 19 | @Override 20 | public void serialize(LocalTime value, JsonGenerator jgen, SerializerProvider provider) 21 | throws IOException { 22 | jgen.writeString(TIME_FORMATTER.format(value)); 23 | } 24 | } -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/mapper/RefreshTokenMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.mapper; 2 | 3 | import com.github.hiling.auth.model.RevokeToken; 4 | import com.github.hiling.auth.model.RefreshToken; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | /** 11 | * Author by hiling, Email admin@mn-soft.com, Date on 10/12/2018. 12 | */ 13 | public interface RefreshTokenMapper { 14 | 15 | RefreshToken getByRefreshToken(@Param("refreshToken") String refreshToken); 16 | 17 | List getRevokeRefreshToken(List list); 18 | 19 | Long insert(RefreshToken refreshToken); 20 | 21 | int updateLastUsedTimeById(@Param("id") Long id, @Param("lastUsedTime") LocalDateTime lastUsedTime); 22 | 23 | void batchDeleteByRefreshToken(@Param("refreshTokenList") List refreshTokenList); 24 | 25 | void deleteExpiredRefreshToken(); 26 | } 27 | -------------------------------------------------------------------------------- /mini-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | mini-platform 9 | com.github.hiling 10 | 0.0.1-SNAPSHOT 11 | ../pom.xml 12 | 13 | 14 | mini-common 15 | 0.0.4-SNAPSHOT 16 | jar 17 | MiniCommon 18 | common 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/json/serializer/LocalDateTimeSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils.json.serializer; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | 7 | import java.io.IOException; 8 | import java.time.LocalDateTime; 9 | import java.time.format.DateTimeFormatter; 10 | 11 | /** 12 | * Author by hiling, Email admin@mn-soft.com, Date on 11/26/2018. 13 | * 适用于jdk1.8及以上 14 | */ 15 | public class LocalDateTimeSerializer extends JsonSerializer { 16 | 17 | private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 18 | 19 | @Override 20 | public void serialize(LocalDateTime value, JsonGenerator jgen, SerializerProvider provider) 21 | throws IOException { 22 | jgen.writeString(DATE_TIME_FORMATTER.format(value)); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/web/DemoController.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.web; 2 | 3 | // import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.ResponseBody; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import java.time.LocalDateTime; 13 | 14 | /** 15 | * @author hiling 16 | * @date 2019/10/30 16:33 17 | */ 18 | @Configuration 19 | @RequestMapping("/common/demo") 20 | public class DemoController { 21 | 22 | @GetMapping 23 | @ResponseBody 24 | public ResponseEntity error(HttpServletRequest request) { 25 | return new ResponseEntity<>("Demo:"+ LocalDateTime.now(), HttpStatus.OK); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /mini-config/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.hiling 7 | mini-platform 8 | 0.0.1-SNAPSHOT 9 | ../pom.xml 10 | 11 | mini-config 12 | 0.0.1-SNAPSHOT 13 | MiniConfig 14 | Spring Boot Config 15 | 16 | 17 | 18 | org.springframework.cloud 19 | spring-cloud-config-server 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-netflix-eureka-client 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /modules/mini-user/src/main/java/com/github/hiling/user/config/DirectRabbitConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user.config; 2 | 3 | 4 | import org.springframework.amqp.core.Binding; 5 | import org.springframework.amqp.core.BindingBuilder; 6 | import org.springframework.amqp.core.DirectExchange; 7 | import org.springframework.amqp.core.Queue; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class DirectRabbitConfig { 13 | //队列 起名:TestDirectQueue 14 | @Bean 15 | public Queue TestDirectQueue() { 16 | return new Queue("TestDirectQueue",true); 17 | } 18 | 19 | //Direct交换机 起名:TestDirectExchange 20 | @Bean 21 | DirectExchange TestDirectExchange() { 22 | return new DirectExchange("TestDirectExchange"); 23 | } 24 | 25 | //绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting 26 | @Bean 27 | Binding bindingDirect() { 28 | return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/json/serializer/DateTimeSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils.json.serializer; 2 | 3 | import java.io.IOException; 4 | import java.text.DateFormat; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | 8 | import com.fasterxml.jackson.core.JsonGenerator; 9 | import com.fasterxml.jackson.databind.JsonSerializer; 10 | import com.fasterxml.jackson.databind.SerializerProvider; 11 | 12 | /** 13 | * Author by hiling, Email admin@mn-soft.com, Date on 11/26/2018. 14 | * 适用于jdk1.7以下 15 | */ 16 | public class DateTimeSerializer extends JsonSerializer { 17 | 18 | private static final String PATTERN = "yyyy-MM-dd HH:mm:ss"; 19 | 20 | @Override 21 | public void serialize(Date date, JsonGenerator jgen, SerializerProvider provider) { 22 | try { 23 | DateFormat dateFormat = new SimpleDateFormat(PATTERN); 24 | jgen.writeString(dateFormat.format(date)); 25 | } catch (IOException e) { 26 | throw new RuntimeException("Date转换json异常,格式:" + PATTERN); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/model/AccessToken.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.*; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | 10 | /** 11 | * ActiveRecord 模式实现(开发效率高,但容易在controller层或service层直接写业务逻辑,所以不建议生产代码使用,可以用于测试代码) 12 | * 1、继承 Model 13 | * 2、通过pkVal()方法指定主键 14 | * 3、创建UserMapper 15 | * 4、使用示例: 16 | * new AccessToken().setId(1L).selectById().getAccessToken() 17 | * new AccessToken().setId(1L).setAccessToken("abc").updateById() 18 | */ 19 | @Data 20 | @Accessors(chain = true) 21 | public class AccessToken { 22 | 23 | @JsonIgnore 24 | private Long id; 25 | 26 | @JsonIgnore 27 | private String clientId; 28 | 29 | @JsonIgnore 30 | private Long userId; 31 | 32 | private String accessToken; 33 | 34 | @JsonIgnore 35 | private String jwtToken; 36 | 37 | private String refreshToken; 38 | 39 | private Integer expiresIn; 40 | 41 | //@JsonIgnore 42 | private LocalDateTime createTime; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hiling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mini-config/config-repo/mini-discovery-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 3 | 4 | eureka: 5 | environment: dev 6 | #### Eureka Server 配置 #### 7 | server: 8 | # 测试环境关闭自我保护机制,生产环境建议开启, 9 | # Eureka Server会统计15分钟之内心跳失败的比例低于一定比例(默认85%)将触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除 10 | enable-self-preservation: false 11 | # 开启自我保护的阈值,默认0.85 12 | renewal-percent-threshold: 0.5 13 | 14 | # EurekaServer定时把数据从ReadWriteMap更新到ReadOnlyMap中,服务调用者再缓存到本地,三个缓存点都有延迟; 15 | # EurekaServer刷新ReadOnlyMap的时间,默认0; 16 | response-cache-update-interval-ms: 30000 17 | 18 | # EurekaServer中ReadWriteMap的过期时间(过期后会从Registry中重新读取注册信息, 19 | # ReadWriteMap是Guava Cache,Registry是ConcurrentHashMap), 默认180 20 | response-cache-auto-expiration-in-seconds: 180 21 | 22 | # 启用EurekaServer主动失效检测间隔为30s,默认为0(不启用) 23 | # 每个服务提供者会发送自己服务过期时间上去,EurekaServer会定时检查每个服务过期时间和上次心跳时间, 24 | # 如果在过期时间内没有收到过任何一次心跳,同时没有处于保护模式下,则会将这个实例从ReadWriteMap中去掉。 25 | eviction-interval-timer-in-ms: 30000 26 | client: 27 | # 表示将自身注册到eureka服务器 28 | register-with-eureka: false 29 | # 启用从EurekaServer上获取注册信息 30 | fetch-registry: false 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /mini-oauth-client/src/main/java/com/github/hiling/oauth/client/web/UserInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.oauth.client.web; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Author by hiling, Email admin@mn-soft.com, Date on 12/14/2018. 7 | */ 8 | public class UserInfo { 9 | Long userId; 10 | String userName; 11 | String clientId; 12 | List scopeList; 13 | 14 | public Long getUserId(){ 15 | return userId; 16 | } 17 | 18 | public void setUserId(Long userId){ 19 | this.userId = userId; 20 | } 21 | 22 | public String getUserName() { 23 | return userName; 24 | } 25 | 26 | public void setUserName(String userName) { 27 | this.userName = userName; 28 | } 29 | 30 | public String getClientId(){ 31 | return clientId; 32 | } 33 | 34 | public void setClientId(String clientId){ 35 | this.clientId = clientId; 36 | } 37 | 38 | public List getScopeList(){ 39 | return scopeList; 40 | } 41 | 42 | public void setScopeList(List scopeList){ 43 | this.scopeList = scopeList; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /mini-oauth-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mini-platform 7 | com.github.hiling 8 | 0.0.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | mini-oauth-client 13 | MiniOAuthClient 14 | 15 | 1.7 16 | 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-web 22 | 23 | 24 | 25 | io.jsonwebtoken 26 | jjwt 27 | 0.9.1 28 | 29 | 30 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.exception; 2 | 3 | public class BusinessException extends RuntimeException { 4 | private static final long serialVersionUID = 5468122937751046392L; 5 | 6 | public BusinessException(Throwable cause) { 7 | super(cause); 8 | } 9 | 10 | public BusinessException(String message) { 11 | super(message); 12 | } 13 | 14 | public BusinessException(ExceptionMessage message) { 15 | super(message.getMessage()); 16 | this.code = message.value(); 17 | } 18 | 19 | public BusinessException(Integer code, String message) { 20 | super(message); 21 | this.code = code; 22 | } 23 | 24 | public BusinessException(Integer code, String message, Throwable cause) { 25 | super(message, cause); 26 | this.code = code; 27 | } 28 | 29 | /** 30 | * 业务定义的异常错误码 31 | */ 32 | private Integer code; 33 | 34 | 35 | public Integer getCode() { 36 | return code; 37 | } 38 | 39 | public void setCode(Integer code) { 40 | this.code = code; 41 | } 42 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/json/serializer/DateSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils.json.serializer; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | // import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.JsonSerializer; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | 8 | import java.io.IOException; 9 | import java.text.DateFormat; 10 | import java.text.SimpleDateFormat; 11 | import java.util.Date; 12 | 13 | /** 14 | * Author by hiling, Email admin@mn-soft.com, Date on 11/26/2018. 15 | * 适用于jdk1.7以下 16 | */ 17 | public class DateSerializer extends JsonSerializer { 18 | 19 | private static final String PATTERN = "yyyy-MM-dd"; 20 | 21 | @Override 22 | public void serialize(Date date, JsonGenerator jgen, SerializerProvider provider) { 23 | try { 24 | DateFormat dateFormat = new SimpleDateFormat(PATTERN); 25 | jgen.writeString(dateFormat.format(date)); 26 | } catch (IOException e) { 27 | throw new RuntimeException("Date转换json异常,格式:" + PATTERN); 28 | } 29 | // log.debug("日期类型序列化"); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/config/BaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | 5 | public class BaseConfig { 6 | @Value("${spring.application.name}") 7 | public String applicationName; 8 | 9 | @Value("${spring.application.description:API服务}") 10 | public String applicationDescription; 11 | 12 | @Value("${spring.application.url:https://api.mn-soft.com}") 13 | public String applicationServiceUrl; 14 | 15 | @Value("${spring.application.version:0.0.1") 16 | public String applicationVersion; 17 | 18 | /** 19 | * 是否打印http request日志, 默认:true 20 | */ 21 | @Value("${config.logging.httpRequest.enabled:true}") 22 | public Boolean loggingHttpRequest; 23 | 24 | /** 25 | * 是否打印http request的body日志,默认: true 26 | * 27 | * 只打印ContentType包含application/json的request 28 | */ 29 | @Value("${config.logging.httpRequest.includeBody:true}") 30 | public Boolean loggingHttpRequestBody; 31 | 32 | /** 33 | * 是否打印http response日志, 默认:false 34 | */ 35 | @Value("${config.logging.httpResponse.enabled:false}") 36 | public Boolean loggingHttpResponse; 37 | } 38 | -------------------------------------------------------------------------------- /mini-common/src/test/java/com/github/hiling/common/utils/SnowflakeIdWorkerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.IntStream; 13 | 14 | /** 15 | * @author hiling 16 | * @date 2019/7/13 9:49 17 | */ 18 | @Slf4j 19 | @RunWith(SpringRunner.class) 20 | public class SnowflakeIdWorkerTest { 21 | @Test 22 | public void contextLoads() { 23 | List ids = Collections.synchronizedList(new ArrayList<>()); 24 | 25 | long s = System.currentTimeMillis(); 26 | IntStream.range(0, 100000).parallel().forEach(i -> { 27 | ids.add(SnowflakeIdWorker.getSingleton().nextId()); 28 | }); 29 | log.info("系统花费时间:{}毫秒", System.currentTimeMillis() - s); 30 | List filterIds = ids.stream().distinct().collect(Collectors.toList()); 31 | log.info("生产ID数:{}", ids.size()); 32 | log.info("过滤重复ID数:{}", filterIds.size()); 33 | log.info("重复ID数:{}", ids.size() - filterIds.size()); 34 | } 35 | } -------------------------------------------------------------------------------- /mini-config/config-common/common-discovery-dev.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | service-url: 4 | # 指定Eureka Server地址,多个用逗号隔开 5 | defaultZone: http://127.0.0.1:8761/eureka/ 6 | # 表示将自身注册到eureka服务器 7 | register-with-eureka: true 8 | # 启用从EurekaServer上获取注册信息 9 | fetch-registry: true 10 | # 刷新本地缓存时间,默认30s 11 | registry-fetch-interval-seconds: 5 12 | # Eureka 服务端连接空闲关闭时间,默认30 13 | eureka-connection-idle-timeout-seconds: 30 14 | # 连接 Eureka Server 的超时时间,默认5秒 15 | eureka-server-connect-timeout-seconds: 5 16 | # 读取 Eureka Server 信息的超时时间,默认8秒 17 | eureka-server-read-timeout-seconds: 8 18 | # 从Eureka 客户端到所有Eureka服务端的连接总数,默认200 19 | eureka-server-total-connections: 200 20 | # 从Eureka客户端到每个Eureka服务主机的连接总数,默认50 21 | eureka-server-total-connections-per-host: 50 22 | 23 | instance: 24 | instance-id: ${spring.cloud.client.ip-address}:${server.port} 25 | # 服务刷新时间,每隔这个时间会主动向Eureka Server发起心跳一次,默认30s 26 | lease-renewal-interval-in-seconds: 5 27 | # 服务过期时间,超过这个时间没有接收到心跳,EurekaServer剔除这个实例 28 | # 注意:EurekaServer一定要设置eureka.server.eviction-interval-timer-in-ms否则这个配置无效,这个配置一般为服务刷新时间配置的三倍 29 | # 默认90s 30 | lease-expiration-duration-in-seconds: 15 31 | 32 | # Eureka客户端Ribbon刷新时间,默认30s 33 | ribbon: 34 | ServerListRefreshInterval: 5000 -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/client/mapper/ClientMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.client.mapper; 2 | 3 | import com.github.hiling.auth.modules.client.model.Client; 4 | import org.apache.ibatis.annotations.Param; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Author by hiling, Email admin@mn-soft.com, Date on 12/1/2018. 11 | */ 12 | @Repository 13 | public interface ClientMapper { 14 | 15 | List getList(@Param("clientId") String clientId, 16 | @Param("clientName") String clientName, 17 | @Param("clientSecret") String clientSecret, 18 | @Param("status") Integer status); 19 | 20 | Client getForVerify(@Param("clientId") String clientId); 21 | 22 | int refreshSecret(@Param("clientId") String clientId, 23 | @Param("currentSecret") String currentSecret, 24 | @Param("newSecret") String newSecret, 25 | @Param("userId") String userId); 26 | 27 | int updateStatus(@Param("clientId") String clientId, 28 | @Param("status") Integer status, 29 | @Param("userId") String userId); 30 | 31 | int insert(Client client); 32 | } 33 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/client/model/Client.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.client.model; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | import javax.validation.constraints.*; 7 | import java.time.LocalDateTime; 8 | 9 | @Data 10 | @Accessors(chain = true) 11 | public class Client { 12 | 13 | //@NotEmpty(message = "客户编号不能为空。") 14 | @Size(min = 4, max = 16, message = "客户编号长度必须为3至16位") 15 | @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "客户编号只能为数字和字母的组合。") 16 | private String clientId; 17 | 18 | @NotEmpty(message = "客户名称不能为空。") 19 | @Size(min = 4, max = 16, message = "客户名称的长度需要在4至16位。") 20 | private String clientName; 21 | 22 | //@NotEmpty(message = "密钥不能为空") 23 | private String clientSecret; 24 | 25 | /** 26 | * 该客户端能够获取授权的IP白名单 27 | */ 28 | private String ipWhitelist; 29 | 30 | /** 31 | * 该客户端的授权范围,用逗号分隔 32 | */ 33 | private String scope; 34 | 35 | /** 36 | * 1:启用,0:禁用 37 | */ 38 | private Integer status; 39 | 40 | //@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8", locale = "zh") 41 | private String createUser; 42 | private LocalDateTime createTime; 43 | 44 | private String updateUser; 45 | private LocalDateTime updateTime; 46 | 47 | private String remark; 48 | } 49 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/service/impl/RefreshTokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.service.impl; 2 | 3 | import com.github.hiling.auth.mapper.RefreshTokenMapper; 4 | import com.github.hiling.auth.model.RevokeToken; 5 | import com.github.hiling.auth.service.RefreshTokenService; 6 | import org.springframework.stereotype.Service; 7 | 8 | import javax.annotation.Resource; 9 | import java.util.*; 10 | 11 | /** 12 | * Author by hiling, Email admin@mn-soft.com, Date on 10/12/2018. 13 | */ 14 | @Service 15 | public class RefreshTokenServiceImpl implements RefreshTokenService { 16 | 17 | @Resource 18 | private RefreshTokenMapper refreshTokenMapper; 19 | 20 | @Override 21 | public List getRevokeRefreshToken(List list){ 22 | if (list.isEmpty()){ 23 | return Arrays.asList(); 24 | } 25 | return refreshTokenMapper.getRevokeRefreshToken(list); 26 | } 27 | 28 | @Override 29 | public void batchDeleteByRefreshToken(List refreshTokenList){ 30 | if (refreshTokenList.isEmpty()){ 31 | return; 32 | } 33 | refreshTokenMapper.batchDeleteByRefreshToken(refreshTokenList); 34 | } 35 | 36 | @Override 37 | public void deleteExpiredRefreshToken(){ 38 | refreshTokenMapper.deleteExpiredRefreshToken(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/exception/ExceptionResult.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.exception; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.experimental.Accessors; 7 | import java.util.Date; 8 | 9 | @Getter 10 | @Setter 11 | @Accessors(chain = true) 12 | public class ExceptionResult { 13 | 14 | public ExceptionResult() { 15 | this.timestamp = new Date(); 16 | } 17 | 18 | public ExceptionResult(Integer code, String message, Integer status, String error, String path) { 19 | this.code = code; 20 | this.message = message; 21 | this.status = status; 22 | this.error = error; 23 | this.path = path; 24 | this.timestamp = new Date(); 25 | } 26 | 27 | /** 28 | * 业务异常Code 29 | */ 30 | private Integer code; 31 | 32 | /** 33 | * 业务异常信息 34 | */ 35 | private String message; 36 | 37 | /** 38 | * HttpStatus 39 | */ 40 | private Integer status; 41 | /** 42 | * 默认异常信息 43 | */ 44 | private String error; 45 | 46 | /** 47 | * 异常路径 48 | */ 49 | private String path; 50 | 51 | /** 52 | * 异常时间 53 | */ 54 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8") 55 | private Date timestamp; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/constant/GrantType.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.constant; 2 | 3 | import org.springframework.lang.Nullable; 4 | 5 | public enum GrantType { 6 | 7 | /** 8 | * 暂不支持 9 | */ 10 | // AUTHORIZATION_CODE("authorization_code"), 11 | 12 | /** 13 | * 暂不支持 14 | */ 15 | // IMPLICIT("implicit"), 16 | 17 | PASSWORD("password"), 18 | CLIENT_CREDENTIALS("client_credentials"), 19 | REFRESH_TOKEN("refresh_token"); 20 | 21 | private final String type; 22 | 23 | GrantType(String type) { 24 | this.type = type; 25 | } 26 | 27 | public String getType() { 28 | return this.type; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return this.type; 34 | } 35 | 36 | public static GrantType typeOf(String type) { 37 | GrantType grantType = resolve(type); 38 | if (grantType == null) { 39 | throw new IllegalArgumentException("没有找到 [" + type + "]"); 40 | } else { 41 | return grantType; 42 | } 43 | } 44 | 45 | @Nullable 46 | public static GrantType resolve(String typeString) { 47 | GrantType[] grantTypes = values(); 48 | for (GrantType grantType : grantTypes) { 49 | if (grantType.type.equalsIgnoreCase(typeString)) { 50 | return grantType; 51 | } 52 | } 53 | return null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/task/RemoveTokenRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.task; 2 | 3 | import com.github.hiling.auth.service.AccessTokenService; 4 | import com.github.hiling.auth.service.RefreshTokenService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.ApplicationArguments; 8 | import org.springframework.boot.ApplicationRunner; 9 | import org.springframework.data.redis.core.StringRedisTemplate; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.Resource; 13 | 14 | /** 15 | * Author by hiling, Email admin@mn-soft.com, Date on 12/16/2018. 16 | */ 17 | @Slf4j 18 | @Component 19 | public class RemoveTokenRunner implements ApplicationRunner { 20 | 21 | @Resource 22 | private AccessTokenService accessTokenService; 23 | 24 | @Resource 25 | private RefreshTokenService refreshTokenService; 26 | 27 | @Autowired 28 | private StringRedisTemplate stringRedisTemplate; 29 | 30 | @Override 31 | public void run(ApplicationArguments args) { 32 | log.info("启动清除Token任务!"); 33 | RemoveAccessTokenThread accessTokenExpiredThread = new RemoveAccessTokenThread(accessTokenService, stringRedisTemplate); 34 | accessTokenExpiredThread.start(); 35 | 36 | RemoveRefreshTokenThread refreshTokenExpiredThread = new RemoveRefreshTokenThread(refreshTokenService); 37 | refreshTokenExpiredThread.start(); 38 | } 39 | } -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/user/service/impl/PasswordMD5.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.user.service.impl; 2 | 3 | import com.github.hiling.common.utils.StringUtils; 4 | import com.github.hiling.auth.modules.user.service.PasswordHash; 5 | import org.springframework.beans.factory.annotation.Value; 6 | 7 | import java.nio.charset.StandardCharsets; 8 | import java.security.MessageDigest; 9 | import java.security.NoSuchAlgorithmException; 10 | 11 | /** 12 | * Author by hiling, Email admin@mn-soft.com, Date on 12/30/2018. 13 | */ 14 | public class PasswordMD5 implements PasswordHash { 15 | 16 | /** 17 | * 秘密密钥算法 18 | * 支持类型:MD2、MD5、SHA-1、SHA-224、SHA-256、SHA-384、SHA-512 19 | * 参考文档:https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest 20 | */ 21 | @Value("${oauth.user.password.md5.algorithm:MD5}") 22 | private String algorithm; 23 | 24 | @Override 25 | public boolean validate(String password, String salt, String hashPassword) { 26 | try { 27 | 28 | MessageDigest md = MessageDigest.getInstance(algorithm); 29 | md.update(salt.getBytes()); 30 | byte[] bytes = md.digest(password.getBytes(StandardCharsets.UTF_8)); 31 | String md5Password = StringUtils.toEncodedString(bytes, StandardCharsets.UTF_8); 32 | return StringUtils.equals(hashPassword, md5Password); 33 | } catch (NoSuchAlgorithmException e) { 34 | return false; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /modules/mini-item/src/main/java/com/github/hiling/item/config/DirectRabbitConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.item.config; 2 | 3 | import org.springframework.amqp.core.Binding; 4 | import org.springframework.amqp.core.BindingBuilder; 5 | import org.springframework.amqp.core.DirectExchange; 6 | import org.springframework.amqp.core.Queue; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | 11 | @Configuration 12 | public class DirectRabbitConfig { 13 | //队列 起名:TestDirectQueue 14 | @Bean 15 | public Queue TestDirectQueue() { 16 | // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 17 | // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable 18 | // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。 19 | // return new Queue("TestDirectQueue",true,true,false); 20 | 21 | //一般设置一下队列的持久化就好,其余两个就是默认false 22 | return new Queue("TestDirectQueue",true); 23 | } 24 | 25 | //Direct交换机 起名:TestDirectExchange 26 | @Bean 27 | DirectExchange TestDirectExchange() { 28 | // return new DirectExchange("TestDirectExchange",true,true); 29 | return new DirectExchange("TestDirectExchange",true,false); 30 | } 31 | 32 | //绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting 33 | @Bean 34 | Binding bindingDirect() { 35 | return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting"); 36 | } 37 | 38 | @Bean 39 | DirectExchange lonelyDirectExchange() { 40 | return new DirectExchange("lonelyDirectExchange"); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/user/service/impl/PasswordHashFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.user.service.impl; 2 | 3 | import com.github.hiling.auth.modules.user.service.PasswordHash; 4 | 5 | import javax.validation.constraints.NotNull; 6 | 7 | /** 8 | * Author by hiling, Email admin@mn-soft.com, Date on 12/30/2018. 9 | */ 10 | public class PasswordHashFactory { 11 | 12 | private PasswordHashFactory() { 13 | } 14 | 15 | private static class PlaintextSingletonHolder { 16 | private static final PasswordPlaintext INSTANCE = new PasswordPlaintext(); 17 | } 18 | 19 | private static class MD5SingletonHolder { 20 | private static final PasswordMD5 INSTANCE = new PasswordMD5(); 21 | } 22 | 23 | private static class PBKDF2SingletonHolder { 24 | private static final PasswordPBKDF2 INSTANCE = new PasswordPBKDF2(); 25 | } 26 | 27 | private static class BCryptSingletonHolder { 28 | private static final PasswordBCrypt INSTANCE = new PasswordBCrypt(); 29 | } 30 | 31 | public static final PasswordHash getInstance(@NotNull String hashType) { 32 | switch (hashType.toLowerCase()) { 33 | case "md5": { 34 | return MD5SingletonHolder.INSTANCE; 35 | } 36 | case "bcrypt": { 37 | return BCryptSingletonHolder.INSTANCE; 38 | } 39 | case "pbkdf2": { 40 | return PBKDF2SingletonHolder.INSTANCE; 41 | } 42 | default: { 43 | return PlaintextSingletonHolder.INSTANCE; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /modules/mini-auth/doc/createToken.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | |客户端| 4 | start 5 | : 获取授权信息; 6 | note left 7 | 所需信息: 8 | client_id,client_secret 9 | grant_type,username,password 10 | end note 11 | 12 | |#AntiqueWhite|认证服务器| 13 | 14 | :创建AccessToken; 15 | note right 16 | 通过用户名密码等认证信息创建AccessToken; 17 | 创建成功说明认证信息正确,登陆成功; 18 | ---- 19 | 返回AccessToken; 20 | end note 21 | 22 | if (client_id是否为空) then (空) 23 | :返回异常; 24 | else (非空) 25 | if(请求IP是否在白名单) then (不存在) 26 | : 返回异常; 27 | else (存在) 28 | if(grant_type是否有效) then (有效) 29 | fork 30 | :Password; 31 | if(用户名密码是否正确) then (正确) 32 | :获取userId; 33 | :创建RefreshToken; 34 | else (错误) 35 | :返回异常; 36 | end 37 | endif 38 | fork again 39 | :Client; 40 | :设置userId=0; 41 | :创建RefreshToken; 42 | fork again 43 | :Refresh; 44 | if(RefreshTken是否正确) then (正确) 45 | :获取userId; 46 | :更新RefreshToken的过期时间; 47 | else (错误) 48 | :返回异常; 49 | end 50 | endif 51 | end fork 52 | :创新AccessToken; 53 | :创建JWT Token; 54 | :存储Token信息到DB; 55 | if(是否为Password或Client模式) then (是) 56 | :存储RefreshToken到DB; 57 | :添加RefreshToken到过期队列,到期清除; 58 | endif 59 | :添加AccessToken到Redis缓存; 60 | :添加AccessToken到过期队列,到期后清除; 61 | :返回AccessToken; 62 | else (无效) 63 | :返回异常; 64 | endif 65 | endif 66 | endif 67 | 68 | |客户端| 69 | :授权结束; 70 | stop 71 | @enduml -------------------------------------------------------------------------------- /modules/mini-item/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | mini-item 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | MiniItem 11 | item services 12 | 13 | 14 | com.github.hiling 15 | modules 16 | 0.0.1-SNAPSHOT 17 | ../pom.xml 18 | 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-config 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-maven-plugin 32 | 33 | 34 | true 35 | com.github.hiling.item.ItemApplication 36 | ZIP 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.HttpMethod; 6 | import org.springframework.web.cors.CorsConfiguration; 7 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 8 | import org.springframework.web.filter.CorsFilter; 9 | 10 | @Configuration 11 | public class CorsConfig { 12 | 13 | private CorsConfiguration buildConfig() { 14 | CorsConfiguration corsConfiguration = new CorsConfiguration(); 15 | corsConfiguration.addAllowedOrigin("*"); // 1 16 | corsConfiguration.addAllowedHeader("*"); // 2 17 | //corsConfiguration.addAllowedMethod("*"); // 3 18 | // 请求方法 19 | corsConfiguration.addAllowedMethod(HttpMethod.DELETE); 20 | corsConfiguration.addAllowedMethod(HttpMethod.POST); 21 | corsConfiguration.addAllowedMethod(HttpMethod.GET); 22 | corsConfiguration.addAllowedMethod(HttpMethod.PUT); 23 | corsConfiguration.addAllowedMethod(HttpMethod.DELETE); 24 | corsConfiguration.addAllowedMethod(HttpMethod.OPTIONS); 25 | // 预检请求的有效期,单位为秒。 26 | corsConfiguration.setMaxAge(3600L); 27 | // 是否支持安全证书 28 | corsConfiguration.setAllowCredentials(true); 29 | return corsConfiguration; 30 | } 31 | 32 | @Bean 33 | public CorsFilter corsFilter() { 34 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 35 | source.registerCorsConfiguration("/**", buildConfig()); // 4 36 | return new CorsFilter(source); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mini-config/config-common/common-mysql-dev.yml: -------------------------------------------------------------------------------- 1 | 2 | spring: 3 | datasource: 4 | driver-class-name: com.mysql.cj.jdbc.Driver 5 | type: com.alibaba.druid.pool.DruidDataSource 6 | # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 7 | connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 8 | # 初始化大小,最小,最大 9 | initialSize: 5 10 | minIdle: 1 11 | maxActive: 50 12 | 13 | # 配置获取连接等待超时的时间 14 | maxWait: 60000 15 | # 配置一个连接在池中最小生存的时间,单位是毫秒 16 | minEvictableIdleTimeMillis: 300000 17 | 18 | # 打开PSCache,并且指定每个连接上PSCache的大小 19 | poolPreparedStatements: false 20 | 21 | # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 22 | timeBetweenEvictionRunsMillis: 60000 23 | 24 | # 合并多个DruidDataSource的监控数据 25 | useGlobalDataSourceStat: true 26 | 27 | validationQuery: SELECT 1 FROM DUAL 28 | testOnBorrow: false 29 | testOnReturn: false 30 | testWhileIdle: true 31 | 32 | # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 33 | filters: stat,wall,log4j 34 | 35 | # StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置 36 | stat-view-servlet: 37 | allow: 127.0.0.1 38 | enabled: true 39 | login-password: admin 40 | login-username: admin 41 | reset-enable: false 42 | url-pattern: /druid/* 43 | web-stat-filter: 44 | enabled: true 45 | exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' 46 | url-pattern: /* 47 | filter: 48 | # 配置StatFilter 49 | stat: 50 | db-type: mysql 51 | log-slow-sql: true 52 | slow-sql-millis: 5000 53 | # 配置WallFilter 54 | wall: 55 | config: 56 | delete-allow: false 57 | drop-table-allow: false 58 | db-type: mysql 59 | enabled: true 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/user/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.user.service.impl; 2 | 3 | import com.github.hiling.common.utils.StringUtils; 4 | import com.github.hiling.auth.model.Account; 5 | import com.github.hiling.auth.modules.user.service.PasswordHash; 6 | import com.github.hiling.auth.modules.user.service.UserService; 7 | import com.github.hiling.auth.modules.user.mapper.UserMapper; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.stereotype.Service; 12 | 13 | 14 | /** 15 | * Author by hiling, Email admin@mn-soft.com, Date on 11/29/2018. 16 | */ 17 | @Slf4j 18 | @Service 19 | public class UserServiceImpl implements UserService { 20 | @Autowired 21 | UserMapper userMapper; 22 | 23 | @Value("${oauth.user.login.sql}") 24 | String loginSql; 25 | 26 | @Value("${oauth.user.password.hash.type}") 27 | String passwordHashType; 28 | 29 | @Override 30 | public Account login(String username, String password) { 31 | 32 | // String sql = "select id as userId,username, password, '' as salt from user where username=#{username};"; 33 | String sql = StringUtils.replace(loginSql, "{", "#{"); 34 | log.debug("login sql: " + sql); 35 | Account user = userMapper.login(sql, username, password); 36 | 37 | if (user != null) { 38 | PasswordHash passwordHash = PasswordHashFactory.getInstance(passwordHashType); 39 | log.warn("PasswordHashFactory:{}", passwordHash); 40 | if (passwordHash.validate(password, user.getSalt() == null ? "" : user.getSalt(), user.getPassword())) { 41 | return user; 42 | } 43 | } 44 | 45 | return null; 46 | } 47 | } -------------------------------------------------------------------------------- /mini-gateway/src/main/java/com/github/hiling/gateway/filter/PostFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.gateway.filter; 2 | 3 | import com.netflix.zuul.ZuulFilter; 4 | import com.netflix.zuul.context.RequestContext; 5 | import lombok.extern.slf4j.Slf4j; 6 | // import org.apache.commons.lang3.StringUtils; 7 | // import org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter; 8 | import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | 13 | /** 14 | * Author by hiling, Email admin@mn-soft.com, Date on 10/15/2018. 15 | */ 16 | @Slf4j 17 | @Component 18 | public class PostFilter extends ZuulFilter { 19 | @Override 20 | public String filterType() { 21 | return FilterConstants.POST_TYPE; 22 | } 23 | 24 | /** 25 | * int值来定义过滤器的执行顺序,数值越小优先级越高 26 | * @return 27 | */ 28 | @Override 29 | public int filterOrder() { 30 | // Needs to run before SendErrorFilter which has filterOrder == 0 31 | return 2; 32 | } 33 | 34 | @Override 35 | public boolean shouldFilter() { 36 | // only forward to errorPath if it hasn't been forwarded to already 37 | return true; 38 | } 39 | 40 | @Override 41 | public Object run() { 42 | RequestContext ctx = RequestContext.getCurrentContext(); 43 | HttpServletRequest request = ctx.getRequest(); 44 | log.debug("------------------->post"); 45 | log.debug("------------------->post:request:{}",request.getRequestURI()); 46 | 47 | //如果出错,Zuul默认的 SendErrorFilter 会添加key并转发到/error请求路径 48 | if (ctx.containsKey("javax.servlet.error.status_code")){ 49 | log.error("error.status_code:"+ctx.get("javax.servlet.error.status_code").toString()); 50 | } 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /modules/mini-auth/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.github.hiling 8 | modules 9 | 0.0.1-SNAPSHOT 10 | ../pom.xml 11 | 12 | 13 | com.github.hiling 14 | mini-auth 15 | 0.0.1-SNAPSHOT 16 | MiniAuth 17 | OAuth2 services 18 | 19 | 20 | 21 | org.springframework.cloud 22 | spring-cloud-context 23 | 2.2.3.RELEASE 24 | 25 | 26 | jakarta.xml.bind 27 | jakarta.xml.bind-api 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-maven-plugin 36 | 37 | 38 | true 39 | com.github.hiling.auth.AuthApplication 40 | ZIP 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /modules/mini-user/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | mini-user 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | MiniUser 11 | user services 12 | 13 | 14 | com.github.hiling 15 | modules 16 | 0.0.1-SNAPSHOT 17 | ../pom.xml 18 | 19 | 20 | 21 | 22 | org.openjdk.jmh 23 | jmh-core 24 | 1.21 25 | 26 | 27 | org.openjdk.jmh 28 | jmh-generator-annprocess 29 | 1.21 30 | provided 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-maven-plugin 39 | 40 | 41 | true 42 | com.github.hiling.user.UserApplication 43 | ZIP 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/config/MyBatisUserConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.config; 2 | 3 | import org.apache.ibatis.session.SqlSessionFactory; 4 | import org.mybatis.spring.SqlSessionFactoryBean; 5 | import org.mybatis.spring.annotation.MapperScan; 6 | import org.mybatis.spring.mapper.MapperScannerConfigurer; 7 | import org.springframework.beans.factory.annotation.Qualifier; 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.boot.jdbc.DataSourceBuilder; 10 | import org.springframework.cloud.context.config.annotation.RefreshScope; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 14 | import org.springframework.core.io.support.ResourcePatternResolver; 15 | 16 | import javax.sql.DataSource; 17 | 18 | /** 19 | * Author by hiling, Email admin@mn-soft.com, Date on 11/29/2018. 20 | */ 21 | @Configuration 22 | @MapperScan(basePackages = "com.github.hiling.auth.modules.user.mapper", sqlSessionFactoryRef = "sqlSessionFactoryBeanForUser") 23 | public class MyBatisUserConfig { 24 | 25 | @RefreshScope 26 | @Bean(name = "dataSourceUser") 27 | @ConfigurationProperties(prefix = "spring.datasource.user") 28 | public DataSource dataSourceUser() { 29 | return DataSourceBuilder.create().build(); 30 | } 31 | 32 | @Bean 33 | public SqlSessionFactory sqlSessionFactoryBeanForUser(@Qualifier("dataSourceUser") DataSource dataSource) throws Exception { 34 | SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); 35 | factory.setDataSource(dataSource); 36 | factory.setTypeAliasesPackage("com.github.hiling.auth.modules.user.model"); 37 | 38 | ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 39 | factory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); 40 | return factory.getObject(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/config/MyBatisOAuthConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.config; 2 | 3 | import org.apache.ibatis.session.SqlSessionFactory; 4 | import org.mybatis.spring.SqlSessionFactoryBean; 5 | import org.mybatis.spring.annotation.MapperScan; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.boot.jdbc.DataSourceBuilder; 9 | import org.springframework.cloud.context.config.annotation.RefreshScope; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.context.annotation.Primary; 13 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 14 | import org.springframework.core.io.support.ResourcePatternResolver; 15 | 16 | import javax.sql.DataSource; 17 | 18 | /** 19 | * Author by hiling, Email admin@mn-soft.com, Date on 11/29/2018. 20 | */ 21 | @Configuration 22 | @MapperScan(basePackages = "com.github.hiling.auth.mapper", sqlSessionFactoryRef = "sqlSessionFactoryBeanForOAuth") 23 | public class MyBatisOAuthConfig { 24 | 25 | @Primary 26 | @RefreshScope 27 | @Bean(name = "dataSourceOAuth") 28 | @ConfigurationProperties(prefix = "spring.datasource.oauth") 29 | public DataSource dataSourceOAuth() { 30 | return DataSourceBuilder.create().build(); 31 | } 32 | 33 | @Bean 34 | @Primary 35 | public SqlSessionFactory sqlSessionFactoryBeanForOAuth(@Qualifier("dataSourceOAuth") DataSource dataSource) throws Exception { 36 | SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); 37 | factory.setDataSource(dataSource); 38 | factory.setTypeAliasesPackage("com.github.hiling.auth.model"); 39 | 40 | ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 41 | factory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); 42 | return factory.getObject(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/config/MyBatisClientConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.config; 2 | 3 | import org.apache.ibatis.session.SqlSessionFactory; 4 | import org.mybatis.spring.SqlSessionFactoryBean; 5 | import org.mybatis.spring.annotation.MapperScan; 6 | import org.mybatis.spring.mapper.MapperScannerConfigurer; 7 | import org.springframework.beans.factory.annotation.Qualifier; 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.boot.jdbc.DataSourceBuilder; 10 | import org.springframework.cloud.context.config.annotation.RefreshScope; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 14 | import org.springframework.core.io.support.ResourcePatternResolver; 15 | 16 | import javax.sql.DataSource; 17 | 18 | /** 19 | * Author by hiling, Email admin@mn-soft.com, Date on 12/1/2018. 20 | */ 21 | @Configuration 22 | @MapperScan(basePackages = "com.github.hiling.auth.modules.client.mapper", sqlSessionFactoryRef = "sqlSessionFactoryBeanForClient") 23 | public class MyBatisClientConfig { 24 | 25 | @RefreshScope 26 | @Bean(name = "dataSourceClient") 27 | @ConfigurationProperties(prefix = "spring.datasource.client") 28 | public DataSource dataSourceUser() { 29 | return DataSourceBuilder.create().build(); 30 | } 31 | 32 | @Bean 33 | public SqlSessionFactory sqlSessionFactoryBeanForClient(@Qualifier("dataSourceClient") DataSource dataSource) throws Exception { 34 | SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); 35 | factory.setDataSource(dataSource); 36 | factory.setTypeAliasesPackage("com.github.hiling.auth.modules.client.model"); 37 | 38 | ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 39 | factory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); 40 | return factory.getObject(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mini-admin/src/main/java/com/github/hiling/admin/config/SecuritySecureConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.admin.config; 2 | 3 | import de.codecentric.boot.admin.server.config.AdminServerProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 7 | import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; 8 | 9 | /** 10 | * @author hiling 11 | * @date 2020/6/30 12:26 12 | */ 13 | @Configuration 14 | public class SecuritySecureConfig extends WebSecurityConfigurerAdapter { 15 | 16 | private final String adminContextPath; 17 | 18 | public SecuritySecureConfig(AdminServerProperties adminServerProperties) { 19 | this.adminContextPath = adminServerProperties.getContextPath(); 20 | } 21 | 22 | @Override 23 | protected void configure(HttpSecurity http) throws Exception { 24 | // @formatter:off 25 | SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); 26 | successHandler.setTargetUrlParameter("redirectTo"); 27 | successHandler.setDefaultTargetUrl(adminContextPath + "/"); 28 | 29 | http.authorizeRequests() 30 | //授予对所有静态资产和登录页面的公共访问权限。 31 | .antMatchers(adminContextPath + "/assets/**").permitAll() 32 | .antMatchers(adminContextPath + "/login").permitAll() 33 | //必须对每个其他请求进行身份验证 34 | .anyRequest().authenticated() 35 | .and() 36 | //配置登录和注销 37 | .formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and() 38 | .logout().logoutUrl(adminContextPath + "/logout").and() 39 | //启用HTTP-Basic支持。这是Spring Boot Admin Client注册所必需的 40 | .httpBasic().and() 41 | .csrf().disable(); 42 | // @formatter:on 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/service/impl/AccountServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.service.impl; 2 | 3 | import com.github.hiling.auth.model.Account; 4 | import com.github.hiling.auth.modules.user.service.UserService; 5 | import com.github.hiling.common.exception.BusinessException; 6 | import com.github.hiling.auth.constant.ErrorMessage; 7 | import com.github.hiling.auth.service.AccountService; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.web.client.RestTemplate; 14 | 15 | @Service 16 | public class AccountServiceImpl implements AccountService { 17 | 18 | @Autowired 19 | RestTemplate restTemplate; 20 | 21 | @Autowired 22 | private UserService userService; 23 | 24 | /** 25 | * 用户登陆类型,支持sql和url 26 | */ 27 | @Value("${oauth.user.login.type:sql}") 28 | String loginType; 29 | 30 | /** 31 | * 用户登陆的服务地址 32 | */ 33 | @Value("${oauth.user.login.url}") 34 | String loginUrl; 35 | 36 | @Override 37 | public Account login(String username, String password) { 38 | if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { 39 | return null; 40 | } 41 | 42 | if ("sql".equalsIgnoreCase(loginType)) { 43 | return userService.login(username, password); 44 | } else { 45 | //url方式 46 | if (StringUtils.isEmpty(loginUrl)) { 47 | throw new BusinessException(ErrorMessage.USER_LOGIN_URL_EMPTY); 48 | } 49 | 50 | String url = StringUtils.replaceOnceIgnoreCase( 51 | StringUtils.replaceOnceIgnoreCase(loginUrl, "{username}", username), "{password}", password); 52 | 53 | ResponseEntity account = restTemplate.getForEntity(url, Account.class); 54 | return account.getBody(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /modules/mini-user/src/test/java/com/github/hiling/user/UserApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user; 2 | 3 | // import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.openjdk.jmh.annotations.*; 7 | import org.openjdk.jmh.runner.Runner; 8 | import org.openjdk.jmh.runner.options.Options; 9 | import org.openjdk.jmh.runner.options.OptionsBuilder; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | import java.util.concurrent.TimeUnit; 15 | 16 | @RunWith(SpringRunner.class) 17 | @SpringBootTest 18 | // @Slf4j 19 | 20 | //基准测试类型: 21 | // Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用”; 22 | // AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”; 23 | // SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内” 24 | // SingleShotTime: 以上模式都是默认一次iteration是1s,唯有SingleShotTime是只运行一次。往往同时把Warmup次数设为0,用于测试冷启动时的性能。 25 | // All(“all”, “All benchmark modes”); 26 | @BenchmarkMode(Mode.All) 27 | //预热轮数 28 | @Warmup(iterations = 3) 29 | //度量,iterations 进行测试的轮次;time 每轮进行的时长;timeUnit 时长单位 30 | @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) 31 | //基准测试结果的时间类型。一般选择秒、毫秒、微秒。 32 | @OutputTimeUnit(TimeUnit.MILLISECONDS) 33 | //状态的共享范围,有三个值:Thread: 该状态为每个线程独享。Group: 该状态为同一个组里面所有线程共享。Benchmark: 该状态在所有线程间共享。 34 | @State(Scope.Thread) 35 | //每个进程中的测试线程。 36 | @Threads(16) 37 | //进行 fork 的次数。如果 fork 数是2的话,则 JMH 会 fork 出两个进程来进行测试。 38 | @Fork(1) 39 | public class UserApplicationTests { 40 | 41 | //对要被测试性能的代码添加注解,说明该方法是要被测试性能的 42 | @Benchmark 43 | public void createToken() { 44 | String url = "http://localhost:8000/token?grant_type=password&username=hiling&password=12345&client_id=vendor"; 45 | RestTemplate restTemplate = new RestTemplate(); 46 | restTemplate.postForEntity(url, null, String.class); 47 | } 48 | 49 | //score的结果是xxx ± xxx,单位是每毫秒多少个操作。 50 | @Test 51 | public void contextLoads() throws Exception{ 52 | Options opt = new OptionsBuilder().include(UserApplicationTests.class.getSimpleName()).build(); 53 | new Runner(opt).run(); 54 | }} 55 | -------------------------------------------------------------------------------- /docs/scripts/sql/schema.sql: -------------------------------------------------------------------------------- 1 | /* 2 | DROP TABLE IF EXISTS `oauth_client`; 3 | DROP TABLE IF EXISTS `oauth_access_token`; 4 | DROP TABLE IF EXISTS `oauth_refresh_token`; 5 | */ 6 | 7 | CREATE TABLE `oauth_client` ( 8 | `client_id` CHAR(16) NOT NULL COLLATE 'utf8_bin', 9 | `client_name` VARCHAR(16) NOT NULL COLLATE 'utf8_bin', 10 | `client_secret` CHAR(32) NOT NULL COLLATE 'utf8_bin', 11 | `grant_types` VARCHAR(64) NOT NULL COLLATE 'utf8_bin', 12 | `ip_whitelist` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '该客户端能够获取授权的IP白名单' COLLATE 'utf8_bin', 13 | `scope` VARCHAR(255) NOT NULL COMMENT '该客户端的授权范围,用逗号分隔' COLLATE 'utf8_bin', 14 | `status` TINYINT(4) NOT NULL DEFAULT '1' COMMENT '1:启用、0:禁用', 15 | `create_user` VARCHAR(16) NOT NULL COLLATE 'utf8_bin', 16 | `create_time` DATETIME NOT NULL DEFAULT NOW(), 17 | `update_user` VARCHAR(16) NOT NULL COLLATE 'utf8_bin', 18 | `update_time` DATETIME NOT NULL DEFAULT NOW(), 19 | `remark` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_bin', 20 | PRIMARY KEY (`client_id`) 21 | ) 22 | COLLATE='utf8_bin' 23 | ENGINE=InnoDB; 24 | 25 | CREATE TABLE `oauth_access_token` ( 26 | `id` BIGINT(20) NOT NULL AUTO_INCREMENT, 27 | `client_id` VARCHAR(16) NOT NULL COLLATE 'utf8_bin', 28 | `user_id` BIGINT(20) NOT NULL, 29 | `access_token` CHAR(32) NOT NULL COLLATE 'utf8_bin', 30 | `jwt_token` VARCHAR(500) NOT NULL DEFAULT '' COLLATE 'utf8_bin', 31 | `refresh_token` VARCHAR(32) NOT NULL DEFAULT '' COLLATE 'utf8_bin', 32 | `expires_in` INT(11) NOT NULL DEFAULT '0', 33 | `create_time` DATETIME NOT NULL DEFAULT NOW(), 34 | PRIMARY KEY (`id`), 35 | INDEX `ix_client_id` (`client_id`), 36 | INDEX `ix_access_token` (`access_token`) 37 | ) 38 | COLLATE='utf8_bin' 39 | ENGINE=InnoDB; 40 | 41 | CREATE TABLE `oauth_refresh_token` ( 42 | `id` BIGINT(20) NOT NULL AUTO_INCREMENT, 43 | `client_id` VARCHAR(16) NOT NULL COLLATE 'utf8_bin', 44 | `user_id` BIGINT(20) NOT NULL, 45 | `refresh_token` CHAR(32) NOT NULL COLLATE 'utf8_bin', 46 | `expires_in` INT(11) NOT NULL DEFAULT '0', 47 | `create_time` DATETIME NOT NULL DEFAULT NOW(), 48 | `last_used_time` DATETIME NOT NULL DEFAULT NOW(), 49 | PRIMARY KEY (`id`), 50 | INDEX `ix_refresh_token` (`refresh_token`) 51 | ) 52 | COLLATE='utf8_bin' 53 | ENGINE=InnoDB; 54 | -------------------------------------------------------------------------------- /mini-gateway/src/main/java/com/github/hiling/gateway/filter/UriRedirectFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.gateway.filter; 2 | 3 | import com.github.hiling.common.utils.StringUtils; 4 | import com.netflix.zuul.ZuulFilter; 5 | import com.netflix.zuul.context.RequestContext; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.boot.context.properties.ConfigurationProperties; 10 | import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.util.ReflectionUtils; 13 | 14 | import java.util.Map; 15 | 16 | /** 17 | * Author by hiling, Email admin@mn-soft.com, Date on 10/15/2018. 18 | * @author hiling 19 | */ 20 | @Slf4j 21 | @Getter 22 | @Setter 23 | @Component 24 | @ConfigurationProperties("link") 25 | public class UriRedirectFilter extends ZuulFilter { 26 | @Override 27 | public String filterType() { 28 | return FilterConstants.ROUTE_TYPE; 29 | } 30 | 31 | /** 32 | * 值来定义过滤器的执行顺序,数值越小优先级越高 33 | * @return 34 | */ 35 | @Override 36 | public int filterOrder() { 37 | return FilterConstants.SEND_FORWARD_FILTER_ORDER - 1; 38 | } 39 | 40 | @Override 41 | public boolean shouldFilter() { 42 | final String serviceId = (String) RequestContext.getCurrentContext().get("proxy"); 43 | return "link".equals(serviceId); 44 | } 45 | 46 | private Map redirect; 47 | 48 | @Override 49 | public Object run() { 50 | try { 51 | RequestContext ctx = RequestContext.getCurrentContext(); 52 | 53 | //url格式必须为 /link/key 54 | String mapKey = ctx.getRequest().getRequestURI().split("/")[2].toLowerCase(); 55 | log.debug("Local URI:{}",mapKey); 56 | String path = redirect.get(mapKey); 57 | log.debug("Remote URI:{}",path); 58 | if (StringUtils.isNoneEmpty(path)) { 59 | ctx.getResponse().sendRedirect(path); 60 | } 61 | } catch (Exception ex) { 62 | ReflectionUtils.rethrowRuntimeException(ex); 63 | } 64 | return null; 65 | } 66 | } -------------------------------------------------------------------------------- /modules/mini-auth/src/main/resources/mapper/AccessTokenMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 22 | 23 | 24 | insert into oauth_access_token 25 | ( 26 | `client_id`, 27 | `user_id`, 28 | `access_token` , 29 | `jwt_token`, 30 | `refresh_token`, 31 | `expires_in` , 32 | `create_time` 33 | ) 34 | values 35 | ( 36 | #{clientId}, 37 | #{userId}, 38 | #{accessToken}, 39 | #{jwtToken}, 40 | #{refreshToken}, 41 | #{expiresIn}, 42 | #{createTime} 43 | ) 44 | 45 | 46 | 47 | delete from oauth_access_token where access_token in 48 | 50 | #{accessToken} 51 | 52 | 53 | 54 | 55 | delete from oauth_access_token 56 | where create_time date_sub(now(), INTERVAL expires_in second) 57 | 58 | 59 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/interceptor/ApiFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.interceptor; 2 | 3 | import com.github.hiling.common.config.BaseConfig; 4 | import com.github.hiling.common.utils.StringUtils; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.servlet.*; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | 15 | @Component 16 | @Slf4j 17 | public class ApiFilter extends BaseConfig implements Filter { 18 | 19 | @Override 20 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 21 | HttpServletResponse response = (HttpServletResponse) res; 22 | HttpServletRequest request = (HttpServletRequest) req; 23 | 24 | response.setHeader("Access-Control-Allow-Origin", "*"); 25 | response.setHeader("Access-Control-Allow-Credentials", "true"); 26 | response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH"); 27 | response.setHeader("Access-Control-Max-Age", "3600"); 28 | response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,keys"); 29 | response.setHeader("Access-Control-Expose-Headers", "Location"); 30 | 31 | // get和delete方法不处理body体 32 | String method = request.getMethod(); 33 | if (HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)){ 34 | chain.doFilter(req, res); 35 | return; 36 | } 37 | 38 | // 当打印body日志开启,且contentType=application/json时,需处理body体用于打印日志 39 | String contentType = request.getContentType(); 40 | if (loggingHttpRequestBody && StringUtils.isNotEmpty(contentType) 41 | && contentType.toLowerCase().contains(MediaType.APPLICATION_JSON_VALUE)) { 42 | req = new RequestWrapper(request); 43 | } 44 | chain.doFilter(req, res); 45 | } 46 | 47 | @Override 48 | public void init(FilterConfig filterConfig) { 49 | } 50 | 51 | @Override 52 | public void destroy() { 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/constant/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.constant; 2 | 3 | import com.github.hiling.common.exception.ExceptionMessage; 4 | 5 | /** 6 | * Author by hiling, Email admin@mn-soft.com, Date on 10/10/2018. 7 | * 8 | * 错误码分两类,公共错误和业务逻辑错误,公共错误码前面为100000,后两位从01开始,如:100000001 9 | * 业务错误规范:6位数字组成,每两位一段,分别代表,模块[2位]--功能[2位]--错误码[2位] 10 | * 公共错误规范:6位数字组成,每三位一段,分别代表,模块[业务模块2位后面补零]--错误码[3位,以HttpStatus码为准] 11 | * 模块: 12 | * gateway: 10 13 | * oauth: 11 14 | * token:10 15 | * client:11 16 | * user:12 17 | * discovery: 12 18 | * common:19 19 | * 20 | * user: 20 21 | * item: 21 22 | *TOKEN_REFRESH_CLIENT_ID_NOT_MATCH 23 | */ 24 | public enum ErrorMessage implements ExceptionMessage { 25 | 26 | TOKEN_GRANT_TYPE_NOT_SUPPORTED(111001, "grant_type仅支持password,client_credentials和refresh_token。"), 27 | TOKEN_AUTHORIZATION_ERROR(111002, "Authorization信息错误。"), 28 | TOKEN_CLIENT_ERROR(111003, "clientId或client_secret错误。"), 29 | TOKEN_USER_ERROR(111004, "用户名或密码错误。"), 30 | ACCESS_TOKEN_ERROR(111010, "access_token无效或已过期。"), 31 | TOKEN_REFRESH_TOKEN_REQUIRED(111005, "refresh_token不能为空。"), 32 | TOKEN_REFRESH_TOKEN_ERROR(111006, "refresh_token无效。"), 33 | TOKEN_REFRESH_TOKEN_EXPIRATION(111007, "refresh_token已过期。"), 34 | TOKEN_REFRESH_CLIENT_ID_NOT_MATCH(111008, "clientId不正确。"), 35 | TOKEN_IP_WHITELIST_ERROR(111009, "获取Token的IP地址不在白名单中。"), 36 | CLIENT_ID_EXIST(111101, "clientId已存在。"), 37 | CLIENT_NAME_EXIST(111102, "clientName已存在。"), 38 | CLIENT_REFRESH_SECRET_ERROR(111103, "clientId或currentSecret不正确。"), 39 | CLIENT_UPDATE_STATUS_ERROR(111104, "更新状态信息错误,请稍后重试。"), 40 | USER_LOGIN_URL_EMPTY(111201, "用户认证服务地址为空。"), 41 | UNAUTHORIZED_ERROR(110401, "您没有该授权。"), 42 | UNKNOWN_ERROR(110500, "未知异常。"); 43 | 44 | private final int value; 45 | private final String message; 46 | 47 | ErrorMessage(int value, String message) { 48 | this.value = value; 49 | this.message = message; 50 | } 51 | 52 | @Override 53 | public int value() { 54 | return this.value; 55 | } 56 | 57 | @Override 58 | public String getMessage() { 59 | return this.message; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return Integer.toString(this.value); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/user/service/impl/PasswordPBKDF2.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.user.service.impl; 2 | 3 | import com.github.hiling.common.utils.StringUtils; 4 | import com.github.hiling.auth.modules.user.service.PasswordHash; 5 | import org.springframework.beans.factory.annotation.Value; 6 | 7 | import javax.crypto.SecretKeyFactory; 8 | import javax.crypto.spec.PBEKeySpec; 9 | import javax.xml.bind.DatatypeConverter; 10 | 11 | import java.security.NoSuchAlgorithmException; 12 | import java.security.spec.InvalidKeySpecException; 13 | import java.security.spec.KeySpec; 14 | 15 | /** 16 | * Author by hiling, Email admin@mn-soft.com, Date on 12/30/2018. 17 | * 帮助文档: 18 | * https://docs.oracle.com/javase/8/docs/api/javax/crypto/SecretKeyFactory.html 19 | * http://jszx-jxpt.cuit.edu.cn/JavaAPI/javax/crypto/SecretKeyFactory.html 20 | */ 21 | public class PasswordPBKDF2 implements PasswordHash { 22 | 23 | /** 24 | * 秘密密钥算法 25 | * 支持类型:AES、ARCFOUR、DES、DESede、PBEWithAnd、PBEWithAnd、PBKDF2With 26 | * 参考文档:https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SecretKeyFactory 27 | */ 28 | @Value("${oauth.user.password.pbkdf2.algorithm:PBKDF2WithHmacSHA1}") 29 | String algorithm; 30 | 31 | /** 32 | * 导出的密钥长度 33 | */ 34 | @Value("${oauth.user.password.pbkdf2.keyLength:128}") 35 | int keyLength; 36 | 37 | /** 38 | * 迭代次数 39 | */ 40 | @Value("${oauth.user.password.pbkdf2.iterationCount:1024}") 41 | int iterationCount; 42 | 43 | @Override 44 | public boolean validate(String password, String salt, String hashPassword) { 45 | String encodedPassword = getPbkdf2(password, salt); 46 | return StringUtils.equals(hashPassword, encodedPassword); 47 | } 48 | 49 | private String getPbkdf2(String password, String salt) { 50 | try { 51 | byte[] bytes = DatatypeConverter.parseHexBinary(salt); 52 | KeySpec spec = new PBEKeySpec(password.toCharArray(), bytes, iterationCount, keyLength); 53 | SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); 54 | byte[] hash = secretKeyFactory.generateSecret(spec).getEncoded(); 55 | return DatatypeConverter.printHexBinary(hash); 56 | } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { 57 | return ""; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /mini-admin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.hiling 7 | mini-platform 8 | 0.0.1-SNAPSHOT 9 | ../pom.xml 10 | 11 | com.github.hiling 12 | mini-admin 13 | 0.0.1-SNAPSHOT 14 | MiniAdmin 15 | Spring Boot Admin Server 16 | 17 | 18 | 19 | de.codecentric 20 | spring-boot-admin-starter-server 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-starter-netflix-eureka-client 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-security 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-devtools 37 | runtime 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.junit.vintage 47 | junit-vintage-engine 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | de.codecentric 57 | spring-boot-admin-dependencies 58 | ${spring-boot-admin.version} 59 | pom 60 | import 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-maven-plugin 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/json/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils.json; 2 | 3 | import java.util.List; 4 | 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | /** 9 | * Author by hiling, Email admin@mn-soft.com, Date on 11/26/2018. 10 | */ 11 | 12 | public class JsonUtils { 13 | 14 | private static JsonMapper jsonMapper = JsonMapper.getDefault(); 15 | 16 | public static ObjectMapper getMapper(){ 17 | return jsonMapper.getMapper(); 18 | } 19 | 20 | public static String toJson(Object object) { 21 | return jsonMapper.toJson(object); 22 | } 23 | /** 24 | * 不含值为null的属性 25 | * @param object 26 | * @return 27 | */ 28 | public static String toJsonIgnoreNullField(Object object) { 29 | return JsonMapper.nonNullMapper().toJson(object); 30 | } 31 | 32 | public static T toObject(String jsonString, Class clazz) { 33 | return jsonMapper.toObject(jsonString, clazz); 34 | } 35 | 36 | public static List toList(String jsonString, Class clazz) { 37 | return jsonMapper.toList(jsonString, clazz); 38 | } 39 | 40 | public static JsonNode getNode(String jsonString,String nodeName){ 41 | try { 42 | JsonNode node = jsonMapper.getMapper().readTree(jsonString); 43 | return nodeName == null ? node : node.get(nodeName); 44 | } catch (Exception e) { 45 | throw new RuntimeException(e); 46 | } 47 | } 48 | 49 | /** 50 | * 51 | * @param jsonString 52 | * @param attrs (e.g:info.user.id) 53 | * @return 54 | */ 55 | public static String getJsonNodeValue(String jsonString, String attrs) { 56 | return getJsonNodeValue(getNode(jsonString, null), attrs); 57 | } 58 | 59 | /** 60 | * 61 | * @param node 62 | * @param attrs (e.g:info.user.id) 63 | * @return 64 | */ 65 | public static String getJsonNodeValue(JsonNode node, String attrs) { 66 | int index = attrs.indexOf('.'); 67 | if (index == -1) { 68 | if (node != null && node.get(attrs) != null) { 69 | return node.get(attrs).asText(); 70 | } 71 | return null; 72 | } else { 73 | String s1 = attrs.substring(0, index); 74 | String s2 = attrs.substring(index + 1); 75 | return getJsonNodeValue(node.get(s1), s2); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /mini-gateway/src/main/java/com/github/hiling/gateway/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.gateway.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 5 | import com.fasterxml.jackson.annotation.PropertyAccessor; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.data.redis.connection.RedisConnectionFactory; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 13 | import org.springframework.data.redis.serializer.StringRedisSerializer; 14 | 15 | /** 16 | * Author by hiling, Email admin@mn-soft.com, Date on 10/7/2018. 17 | */ 18 | @Configuration 19 | public class RedisConfig { 20 | /** 21 | * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类 22 | * @param redisConnectionFactory 23 | * @return 24 | */ 25 | @Bean 26 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 27 | RedisTemplate redisTemplate = new RedisTemplate<>(); 28 | redisTemplate.setConnectionFactory(redisConnectionFactory); 29 | 30 | // 使用Jackson2JsonRedisSerialize替换默认序列化 31 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 32 | ObjectMapper objectMapper = new ObjectMapper(); 33 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 34 | // 指定序列化输入的类型(即将对象全类名一起保存下来),类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常 35 | objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); 36 | 37 | jackson2JsonRedisSerializer.setObjectMapper(objectMapper); 38 | 39 | // 设置value的序列化规则和 key的序列化规则 40 | StringRedisSerializer stringSerializer = new StringRedisSerializer(); 41 | redisTemplate.setKeySerializer(stringSerializer); 42 | redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 43 | redisTemplate.setHashKeySerializer(stringSerializer); 44 | redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); 45 | 46 | redisTemplate.afterPropertiesSet(); 47 | return redisTemplate; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /mini-gateway/src/test/java/com/github/hiling/gateway/GatewayApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.gateway; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import javax.crypto.SecretKeyFactory; 12 | import javax.crypto.spec.PBEKeySpec; 13 | import javax.xml.bind.DatatypeConverter; 14 | import java.security.NoSuchAlgorithmException; 15 | import java.security.spec.InvalidKeySpecException; 16 | import java.security.spec.KeySpec; 17 | 18 | @RunWith(SpringRunner.class) 19 | @SpringBootTest 20 | @Slf4j 21 | public class GatewayApplicationTests { 22 | 23 | @Test 24 | public void encodingBCrypt() { 25 | BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); 26 | //加密时使用 27 | String hashPassword = passwordEncoder.encode("12345"); 28 | log.info("BCrypt HashPassword: {}", hashPassword); 29 | } 30 | 31 | @Test 32 | public void encodingPBKDF2() { 33 | //加密时使用 34 | //salt必填,且需要为十六进制字符串 35 | String salt = DatatypeConverter.printHexBinary("hayden".getBytes()); 36 | String hashPassword = getPBKDF2("12345", salt); 37 | log.info("PBKDF2 HashPassword: {} , saltSecureRandom: {}", hashPassword, salt); 38 | } 39 | 40 | @Value("${oauth.user.password.pbkdf2.algorithm:PBKDF2WithHmacSHA1}") 41 | String algorithm; 42 | 43 | /** 44 | * 导出的密钥长度 45 | */ 46 | @Value("${oauth.user.password.pbkdf2.keyLength:128}") 47 | int keyLength; 48 | 49 | /** 50 | * 迭代次数 51 | */ 52 | @Value("${oauth.user.password.pbkdf2.iterationCount:1024}") 53 | int iterationCount; 54 | 55 | private String getPBKDF2(String password, String salt) { 56 | try { 57 | byte[] bytes = DatatypeConverter.parseHexBinary(salt); 58 | KeySpec spec = new PBEKeySpec(password.toCharArray(), bytes, iterationCount, keyLength); 59 | SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm); 60 | byte[] hash = secretKeyFactory.generateSecret(spec).getEncoded(); 61 | return DatatypeConverter.printHexBinary(hash); 62 | } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { 63 | return ""; 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /mini-config/config-repo/mini-gateway-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8000 3 | 4 | spring: 5 | redis: 6 | database: 1 7 | 8 | ribbon: 9 | # 当eureka.enabled=true时,是基于eureka的service-id的动态路由策略,支持负载均衡,支持服务的自动注册与发现,后端服务需集成eureka client 10 | # 当eureka.enabled=false时,zuul有两种路由策略: 11 | # 1、通过ribbon的负载均衡跳转到后端url,如:user-service.ribbon.listOfServers=http://192.168.10:8001,http://192.168.11:8001 12 | # 2、直接跳转至指定url,如:zuul.routes.user.url=http://localhost:8030 ,如果后端需要负载均衡,可以使用Nginx等 13 | eureka: 14 | enabled: true 15 | #连接超时, 默认2000 16 | ConnectTimeout: 6000 17 | #响应超时, 默认5000 18 | ReadTimeout: 30000 19 | #最大连接数 20 | MaxTotalHttpConnections: 200 21 | #每个host的最大连接数 22 | MaxHttpConnectionsPerHost: 50 23 | #是否所有操作执行重试, 默认值为false, 只重试`GET`请求,*********要确保所有的get方法都幂等;*********** 24 | OkToRetryOnAllOperations: false 25 | #何种响应状态码进行重试 26 | retryableStatusCodes: 502,503,504,509 27 | #同一实例上的最大重试次数, 默认值为0. 不包括首次调用 28 | MaxAutoRetries: 0 29 | #重试其他实例的最大重试次数, 不包括第一次选的实例. 默认为1 30 | MaxAutoRetriesNextServer: 1 31 | 32 | 33 | zuul: 34 | #禁用默认的SendErrorFilter 35 | SendErrorFilter: 36 | error: 37 | disable: true 38 | #如果路由转发请求发生超时(连接超时或处理超时), 只要超时时间的设置小于Hystrix的命令超时时间, 39 | # 那么它就会自动发起重试. 默认为false. 或者对指定响应状态码进行重试 40 | # 可以通过service-id关闭某个服务的重试,如:zuul.routes.item-service.retryable = false 41 | retryable: true 42 | routes: 43 | # user service 44 | user: 45 | path: /user/** 46 | service-id: user-service 47 | #不过滤path,即zuul的地址/user/{id},后端服务地址也为/user/{id},如果是默认true,则过滤后,后端请求地址就成了/{id} 48 | strip-prefix: false 49 | item: 50 | path: /item/** 51 | service-id: item-service 52 | strip-prefix: false 53 | auth: 54 | path: /auth/** 55 | service-id: auth-service 56 | #zuul默认不会将请求头传递下去,因此,通过设置sensitiveHeaders敏感信息头,让配置以外的其他请求头传递下去 57 | #如sensitiveHeaders=cookie,表示只过滤cookie,其他的如Authorization等可以传递下去 58 | sensitive-headers: cookie 59 | # 设置远程URL跳转路由,该路由不指向后端服务,而是自定义过滤器通过下面list.redirect的配置跳转到指定的url 60 | link: 61 | path: /link/** 62 | service-id: link 63 | # url无实际用途,只是避免zuul路由报错 64 | url: http://localhost:8000 65 | 66 | # 设置远程URL地址,用Map实现,最后一段为map的key,也是link后的路径,值为map的value,也是要跳转的远程URL 67 | # 如:当path是/link/hiling时,跳转到https://github.com/hiling 68 | link: 69 | redirect: 70 | hiling: https://github.com/hiling 71 | mini: https://github.com/hiling/mini-platform 72 | 73 | # OAuth忽略验证的地址,多个用逗号分隔,需要以"/"开头; 74 | # 下面配置可忽略 /auth/token**和/link/**地址; 75 | auth: 76 | ignore: 77 | path-prefix: /auth/token,/link/,/item/ 78 | 79 | 80 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/resources/mapper/RefreshTokenMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | 30 | 31 | 32 | insert into oauth_refresh_token 33 | ( 34 | `client_id`, 35 | `user_id`, 36 | `refresh_token`, 37 | `expires_in`, 38 | `create_time`, 39 | `last_used_time` 40 | ) 41 | values 42 | ( 43 | #{clientId}, 44 | #{userId}, 45 | #{refreshToken}, 46 | #{expiresIn}, 47 | #{createTime}, 48 | #{lastUsedTime} 49 | ) 50 | 51 | 52 | 53 | update oauth_refresh_token set last_used_time = #{lastUsedTime} where id = #{id} 54 | 55 | 56 | 57 | delete from oauth_refresh_token where refresh_token in 58 | 59 | #{refreshToken} 60 | 61 | 62 | 63 | 64 | delete from oauth_refresh_token 65 | where create_time date_sub(now(), INTERVAL expires_in second) 66 | 67 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/resources/mapper/ClientMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | 46 | 47 | 48 | update oauth_client 49 | set client_secret = #{newSecret}, 50 | update_user = #{userId}, 51 | update_time = now() 52 | where client_id = #{clientId} 53 | and client_secret = #{currentSecret} 54 | 55 | 56 | 57 | update oauth_client 58 | set `status` = #{status}, 59 | update_user = #{userId}, 60 | update_time = now() 61 | where client_id = #{clientId} 62 | 63 | 64 | 65 | insert into oauth_client 66 | ( 67 | `client_id`, 68 | `client_name`, 69 | `client_secret` , 70 | `grant_types`, 71 | `ip_whitelist`, 72 | `scope` , 73 | `status`, 74 | `create_user`, 75 | `create_time`, 76 | `remark` 77 | ) 78 | values 79 | ( 80 | #{clientId}, 81 | #{clientName}, 82 | #{clientSecret}, 83 | #{grantTypes}, 84 | #{ipWhitelist}, 85 | #{scope}, 86 | #{status}, 87 | #{createUser}, 88 | now(), 89 | #{remark} 90 | ) 91 | 92 | -------------------------------------------------------------------------------- /mini-oauth-client/src/main/java/com/github/hiling/oauth/client/web/BaseController.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.oauth.client.web; 2 | 3 | import com.github.hiling.oauth.JwtUtils; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * Author by hiling, Email admin@mn-soft.com, Date on 12/14/2018. 13 | */ 14 | public class BaseController { 15 | @Autowired 16 | private HttpServletRequest request; 17 | 18 | public Boolean isLogin() { 19 | return getClientId() != null; 20 | } 21 | 22 | public Long getUserId() { 23 | String userId = getByClaimsKey(JwtUtils.USER_ID_KEY); 24 | return userId == null ? 0 : Long.parseLong(userId); 25 | } 26 | 27 | public String getUserName() { 28 | return getByClaimsKey(JwtUtils.USER_NAME_KEY); 29 | } 30 | 31 | public String getClientId() { 32 | return getByClaimsKey(JwtUtils.CLIENT_ID_KEY); 33 | } 34 | 35 | public List getScopeList() { 36 | String scope = getByClaimsKey(JwtUtils.SCOPE_KEY); 37 | return stringToList(scope); 38 | } 39 | 40 | public UserInfo getUserInfo() { 41 | String jwtToken = request.getHeader("jwtToken"); 42 | Map claims = JwtUtils.parserJavaWebToken(jwtToken); 43 | if (claims != null) { 44 | UserInfo userInfo = new UserInfo(); 45 | Object userId = claims.get(JwtUtils.USER_ID_KEY); 46 | Object userName = claims.get(JwtUtils.USER_NAME_KEY); 47 | userInfo.setUserId(userId == null ? 0 : Long.parseLong(userId.toString())); 48 | userInfo.setUserName(userName == null ? "" : userName.toString()); 49 | userInfo.setClientId(claims.get(JwtUtils.CLIENT_ID_KEY).toString()); 50 | userInfo.setScopeList(stringToList(claims.get(JwtUtils.SCOPE_KEY).toString())); 51 | return userInfo; 52 | } 53 | return null; 54 | } 55 | 56 | private String getByClaimsKey(String key) { 57 | String jwtToken = request.getHeader("jwtToken"); 58 | Map claims = JwtUtils.parserJavaWebToken(jwtToken); 59 | if (claims != null) { 60 | Object value = claims.get(key); 61 | return value == null ? null : value.toString(); 62 | } 63 | return null; 64 | } 65 | 66 | private List stringToList(String string) { 67 | 68 | String[] scopes = org.apache.commons.lang3.StringUtils.split(string, ","); 69 | if (scopes == null) { 70 | return null; 71 | } 72 | List scopeList = Arrays.asList(scopes); 73 | return scopeList; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /modules/mini-user/src/main/java/com/github/hiling/user/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.user.controller; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.github.hiling.common.exception.BusinessException; 5 | import com.github.hiling.oauth.client.web.BaseController; 6 | import com.github.hiling.oauth.client.web.UserInfo; 7 | import com.github.hiling.user.mapper.UserMapper; 8 | import com.github.hiling.user.model.User; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.annotation.Resource; 13 | import javax.servlet.http.HttpServletRequest; 14 | 15 | @RestController 16 | @RequestMapping("/user") 17 | public class UserController extends BaseController { 18 | @Resource 19 | private UserMapper mapper; 20 | 21 | /** 22 | * @param request 23 | * @return 24 | */ 25 | @GetMapping("/demo") 26 | public String demo(HttpServletRequest request) { 27 | 28 | 29 | //OAuth Client示例 30 | Boolean isLogin = isLogin(); 31 | Long loginUserId = getUserId(); 32 | UserInfo userInfo = getUserInfo(); 33 | 34 | //高可用测试 35 | String path = request.getRemoteHost() + ":" + request.getServerPort(); 36 | 37 | return " Path:" + path + System.getProperty("line.separator", "\n") 38 | + " is login: " + isLogin + System.getProperty("line.separator", "\n") 39 | + " userId: " + loginUserId + System.getProperty("line.separator", "\n") 40 | + " userName: " + (userInfo == null ? "Null" : userInfo.getUserName()) + System.getProperty("line.separator", "\n") 41 | + " clientId: " + (userInfo == null ? "Null" : userInfo.getClientId()) + System.getProperty("line.separator", "\n") 42 | ; 43 | } 44 | 45 | @GetMapping("/{id}") 46 | public ResponseEntity get(@PathVariable Integer id) { 47 | User user = mapper.selectById(id); 48 | if (user == null) { 49 | throw new BusinessException(110001, "用户不存在"); 50 | } 51 | return ResponseEntity.ok(user); 52 | } 53 | 54 | @GetMapping 55 | public ResponseEntity getByAccount(@RequestParam String username, @RequestParam String password) { 56 | User user = mapper.selectOne(new QueryWrapper().lambda() 57 | .eq(User::getUsername, username) 58 | .eq(User::getPassword, password) 59 | ); 60 | 61 | if (user == null) { 62 | return ResponseEntity.noContent().build(); 63 | } 64 | return ResponseEntity.ok(user); 65 | } 66 | 67 | @PostMapping 68 | public ResponseEntity insert(@RequestBody User model) { 69 | int userId = mapper.insert(model); 70 | return ResponseEntity.ok(userId); 71 | } 72 | } -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/task/RemoveRefreshTokenThread.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.task; 2 | 3 | import com.github.hiling.auth.model.RevokeToken; 4 | import com.github.hiling.auth.service.RefreshTokenService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Value; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.ConcurrentLinkedQueue; 12 | 13 | /** 14 | * Author by hiling, Email admin@mn-soft.com, Date on 10/16/2018. 15 | */ 16 | @Slf4j 17 | public class RemoveRefreshTokenThread extends BaseRemoveTokenThread { 18 | 19 | /** 20 | * 每执行一次后sleep的时间(秒),默认1秒 21 | */ 22 | @Value("${oauth.refresh-token.remove-expired.loop-wait:1}") 23 | final int refreshTokenLoopWait = 10; 24 | 25 | /** 26 | * 每次移除过期数据时,保留最近几秒的数据,默认5秒 27 | */ 28 | @Value("${oauth.refresh-token.remove-expired.reserve-time:5}") 29 | final int refreshTokenReserveTime = 5; 30 | 31 | /** 32 | * 每次移除的最多行数,避免单次处理数据过多导致数据库性能压力,默认1000条 33 | */ 34 | @Value("${oauth.refresh-token.remove-expired.max-remove-count:1000}") 35 | final int refreshTokenMaxRemoveCount = 1000; 36 | 37 | 38 | final RefreshTokenService refreshTokenService; 39 | 40 | /** 41 | * 过期的Refresh Token队列 42 | */ 43 | private static ConcurrentLinkedQueue refreshTokenQueue = new ConcurrentLinkedQueue<>(); 44 | 45 | public RemoveRefreshTokenThread(RefreshTokenService refreshTokenService) { 46 | super.setName("RemoveRefreshTokenThread"); 47 | this.revokeTokens = refreshTokenQueue; 48 | this.refreshTokenService = refreshTokenService; 49 | this.loopWait = refreshTokenLoopWait; 50 | this.reserveTime = refreshTokenReserveTime; 51 | this.maxRemoveCount = refreshTokenMaxRemoveCount; 52 | } 53 | 54 | public static boolean addRefreshTokenToRevokeQueue(String clientId, Long userId, LocalDateTime time) { 55 | log.debug("addRefreshTokenToRevokeQueue({}, {},{})",clientId,userId,time); 56 | return refreshTokenQueue.offer(RevokeToken.builder().clientId(clientId).userId(userId).time(time).build()); 57 | } 58 | 59 | @Override 60 | protected void removeRevokeAndExpiredToken(ArrayList revokeList) { 61 | try { 62 | //查询所有过期token 63 | List tokenList = refreshTokenService.getRevokeRefreshToken(revokeList); 64 | 65 | //删除数据库 66 | refreshTokenService.batchDeleteByRefreshToken(tokenList); 67 | 68 | //删除缓存 69 | //stringRedisTemplate.delete(tokenList); 70 | 71 | refreshTokenService.deleteExpiredRefreshToken(); 72 | log.debug("删除过期的Refresh Token"); 73 | 74 | } catch (Exception e) { 75 | e.printStackTrace(); 76 | log.error(e.getMessage()); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/client/controller/ClientController.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.client.controller; 2 | 3 | import com.github.hiling.common.exception.BusinessException; 4 | import com.github.hiling.auth.modules.client.model.Client; 5 | import com.github.hiling.auth.constant.ErrorMessage; 6 | import com.github.hiling.auth.modules.client.service.ClientService; 7 | import com.github.hiling.oauth.client.web.BaseController; 8 | import com.github.hiling.oauth.client.web.UserInfo; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * Author by hiling, Email admin@mn-soft.com, Date on 12/2/2018. 17 | */ 18 | @RestController 19 | @RequestMapping("/client") 20 | public class ClientController extends BaseController { 21 | 22 | @Autowired 23 | private ClientService clientService; 24 | 25 | @GetMapping 26 | public ResponseEntity> getList(String clientId, String clientName, String clientSecret, Integer status) { 27 | List scope = getScopeList(); 28 | List list = clientService.getList(clientId, clientName, clientSecret, status, scope); 29 | return ResponseEntity.ok(list); 30 | } 31 | 32 | @PostMapping() 33 | public ResponseEntity insert(Client client) { 34 | UserInfo userInfo = getUserInfo(); 35 | int newSecret = clientService.insert(client, userInfo.getUserName(), userInfo.getScopeList()); 36 | if (newSecret == 0) { 37 | throw new BusinessException(ErrorMessage.UNKNOWN_ERROR); 38 | //return ResponseEntity.badRequest().build(); 39 | } 40 | return ResponseEntity.ok(true); 41 | } 42 | 43 | @PutMapping("/secret") 44 | public ResponseEntity refreshSecret(String clientId, String currentSecret) { 45 | UserInfo userInfo = getUserInfo(); 46 | String newSecret = clientService.refreshSecret(clientId, currentSecret, userInfo.getUserName(), userInfo.getScopeList()); 47 | if (newSecret == null) { 48 | throw new BusinessException(ErrorMessage.CLIENT_REFRESH_SECRET_ERROR); 49 | //return ResponseEntity.badRequest().build(); 50 | } 51 | return ResponseEntity.ok(newSecret); 52 | } 53 | 54 | @PutMapping("/status") 55 | public ResponseEntity updateStatus(String clientId, Integer status) { 56 | UserInfo userInfo = getUserInfo(); 57 | Integer result = clientService.updateStatus(clientId, status, userInfo.getUserName(), userInfo.getScopeList()); 58 | if (result == 0) { 59 | throw new BusinessException(ErrorMessage.CLIENT_REFRESH_SECRET_ERROR); 60 | //return ResponseEntity.badRequest().build(); 61 | } 62 | return ResponseEntity.ok(true); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/modules/client/service/impl/ClientServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.modules.client.service.impl; 2 | 3 | import com.github.hiling.auth.constant.ErrorMessage; 4 | import com.github.hiling.auth.modules.client.mapper.ClientMapper; 5 | import com.github.hiling.auth.modules.client.model.Client; 6 | import com.github.hiling.auth.service.AccessTokenService; 7 | import com.github.hiling.common.exception.BusinessException; 8 | import com.github.hiling.common.utils.UuidUtils; 9 | import com.github.hiling.auth.modules.client.service.ClientService; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.stereotype.Service; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Author by hiling, Email admin@mn-soft.com, Date on 12/1/2018. 18 | */ 19 | @Service 20 | public class ClientServiceImpl implements ClientService { 21 | 22 | @Autowired 23 | ClientMapper clientMapper; 24 | 25 | @Autowired 26 | AccessTokenService accessTokenService; 27 | 28 | @Value("${oauth.client.allow.scope:oauth-client}") 29 | String allowClient; 30 | 31 | @Override 32 | public List getList(String clientId, String clientName, String clientSecret, Integer status, List scope){ 33 | verifyAuthorization(scope); 34 | return clientMapper.getList(clientId,clientName,clientSecret,status); 35 | } 36 | 37 | @Override 38 | public int insert(Client client, String loginUserId, List scope) { 39 | verifyAuthorization(scope); 40 | 41 | if (clientMapper.getList(client.getClientId(), null, null, null).size() > 0) { 42 | throw new BusinessException(ErrorMessage.CLIENT_ID_EXIST); 43 | } 44 | 45 | if (clientMapper.getList(null, client.getClientName(), null, null).size() > 0) { 46 | throw new BusinessException(ErrorMessage.CLIENT_NAME_EXIST); 47 | } 48 | 49 | client.setCreateUser(loginUserId); 50 | return clientMapper.insert(client); 51 | } 52 | 53 | @Override 54 | public String refreshSecret(String clientId, String currentSecret, String loginUserId, List scope) { 55 | verifyAuthorization(scope); 56 | String newSecret = UuidUtils.getUuid(); 57 | int result = clientMapper.refreshSecret(clientId, currentSecret, newSecret, loginUserId); 58 | if (result > 0) { 59 | return newSecret; 60 | } 61 | return null; 62 | } 63 | 64 | @Override 65 | public int updateStatus(String clientId, Integer status, String loginUserId, List scope) { 66 | verifyAuthorization(scope); 67 | return clientMapper.updateStatus(clientId, status, loginUserId); 68 | } 69 | 70 | private void verifyAuthorization(List scope){ 71 | if (scope == null || !scope.contains(allowClient)) { 72 | throw new BusinessException(ErrorMessage.UNAUTHORIZED_ERROR); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/task/BaseRemoveTokenThread.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.task; 2 | 3 | import com.github.hiling.auth.model.RevokeToken; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.ArrayList; 8 | import java.util.concurrent.ConcurrentLinkedQueue; 9 | 10 | /** 11 | * Author by hiling, Email admin@mn-soft.com, Date on 10/16/2018. 12 | */ 13 | @Slf4j 14 | public abstract class BaseRemoveTokenThread extends Thread { 15 | 16 | /** 17 | * 每执行一次清除过期Token后sleep的时间(秒),默认1秒 18 | */ 19 | protected int loopWait = 5; 20 | 21 | /** 22 | * 每次移除过期数据时,保留最近几秒的数据,默认5秒 23 | */ 24 | protected int reserveTime = 5; 25 | 26 | /** 27 | * 每次移除的最多行数,避免单次处理数据过多导致数据库性能压力,默认1000条 28 | */ 29 | protected int maxRemoveCount = 1000; 30 | 31 | /** 32 | * 过期的Access Token队列(先进先出) 33 | * size()要遍历整个集合,很慢,避免使用,判断空可以用isEmpty() 34 | */ 35 | ConcurrentLinkedQueue revokeTokens; 36 | 37 | @Override 38 | public void run() { 39 | while (true) { 40 | try { 41 | Thread.sleep(1000 * loopWait); 42 | log.debug("{}: queue isEmpty():{}", this.getName(), revokeTokens.isEmpty()); 43 | ArrayList expiredList = getRevokeToken(); 44 | removeRevokeAndExpiredToken(expiredList); 45 | } catch (Exception e) { 46 | e.printStackTrace(); 47 | log.error(e.getMessage()); 48 | } 49 | } 50 | } 51 | 52 | protected abstract void removeRevokeAndExpiredToken(ArrayList revokeList); 53 | 54 | protected ArrayList getRevokeToken() { 55 | ArrayList expiredList = new ArrayList(); 56 | 57 | try { 58 | for (int i = 0; i < this.maxRemoveCount; i++) { 59 | 60 | //查询最早一个 61 | RevokeToken token = this.revokeTokens.peek(); 62 | //没有Token或是最近几秒内的token,则不再执行(由于是队列,先进先出,下一个token在该token后插入,因此肯定也在5秒内)。revoke 63 | if (token == null) { 64 | log.debug("{}: 准备清除:token is null!", this.getName()); 65 | break; 66 | } 67 | 68 | if (token.getTime().isAfter(LocalDateTime.now().minusSeconds(this.reserveTime))) { 69 | log.debug("{}: 准备清除:过期时间未到! clientId:{}, UserId:{},过期时间:{}", this.getName(), token.getClientId(), token.getUserId(), token.getTime().toString()); 70 | break; 71 | } 72 | 73 | expiredList.add(token); 74 | this.revokeTokens.poll(); //查询并移除最早一个,如果没有查到,则返回Null 75 | log.debug("{}: 即将清除:clientId:{}, UserId:{},过期时间:{}", this.getName(), token.getClientId(), token.getUserId(), token.getTime().toString()); 76 | } 77 | } catch (Exception e) { 78 | e.printStackTrace(); 79 | log.error(e.getMessage()); 80 | } 81 | return expiredList; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/interceptor/RequestWrapper.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.interceptor; 2 | 3 | import javax.servlet.ReadListener; 4 | import javax.servlet.ServletInputStream; 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletRequestWrapper; 7 | import java.io.*; 8 | 9 | /** 10 | * @author hiling 11 | * 用于拦截http request body 12 | */ 13 | public class RequestWrapper extends HttpServletRequestWrapper { 14 | private final String body; 15 | 16 | public RequestWrapper(HttpServletRequest request) { 17 | super(request); 18 | StringBuilder stringBuilder = new StringBuilder(); 19 | BufferedReader bufferedReader = null; 20 | InputStream inputStream = null; 21 | try { 22 | inputStream = request.getInputStream(); 23 | if (inputStream != null) { 24 | bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); 25 | char[] charBuffer = new char[128]; 26 | int bytesRead = -1; 27 | while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { 28 | stringBuilder.append(charBuffer, 0, bytesRead); 29 | } 30 | } else { 31 | stringBuilder.append(""); 32 | } 33 | } catch (IOException ex) { 34 | 35 | } finally { 36 | if (inputStream != null) { 37 | try { 38 | inputStream.close(); 39 | } 40 | catch (IOException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | if (bufferedReader != null) { 45 | try { 46 | bufferedReader.close(); 47 | } 48 | catch (IOException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | } 53 | body = stringBuilder.toString(); 54 | } 55 | 56 | @Override 57 | public ServletInputStream getInputStream() throws IOException { 58 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); 59 | ServletInputStream servletInputStream = new ServletInputStream() { 60 | @Override 61 | public boolean isFinished() { 62 | return false; 63 | } 64 | @Override 65 | public boolean isReady() { 66 | return false; 67 | } 68 | @Override 69 | public void setReadListener(ReadListener readListener) { 70 | } 71 | @Override 72 | public int read() throws IOException { 73 | return byteArrayInputStream.read(); 74 | } 75 | }; 76 | return servletInputStream; 77 | 78 | } 79 | 80 | @Override 81 | public BufferedReader getReader() throws IOException { 82 | return new BufferedReader(new InputStreamReader(this.getInputStream())); 83 | } 84 | 85 | public String getBody() { 86 | return this.body; 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /mini-gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | mini-gateway 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | MiniGateway 11 | api gateway 12 | 13 | 14 | com.github.hiling 15 | mini-platform 16 | 0.0.1-SNAPSHOT 17 | ../pom.xml 18 | 19 | 20 | 21 | 22 | com.github.hiling 23 | mini-common 24 | ${common.version} 25 | 26 | 27 | com.github.hiling 28 | mini-oauth-client 29 | ${oauth-client.version} 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-starter-netflix-eureka-client 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-starter-netflix-zuul 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-starter-openfeign 46 | 47 | 48 | org.springframework.retry 49 | spring-retry 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-configuration-processor 54 | true 55 | 56 | 57 | 58 | 59 | org.apache.commons 60 | commons-pool2 61 | 2.6.0 62 | 63 | 64 | 65 | 66 | org.projectlombok 67 | lombok 68 | ${lombok.version} 69 | provided 70 | 71 | 72 | 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-maven-plugin 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /mini-discovery/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | mini-discovery 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | MiniDiscovery 11 | service registry and discovery 12 | 13 | 14 | com.github.hiling 15 | mini-platform 16 | 0.0.1-SNAPSHOT 17 | ../pom.xml 18 | 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-netflix-eureka-server 24 | 25 | 26 | spring-cloud-starter-netflix-archaius 27 | org.springframework.cloud 28 | 29 | 30 | spring-cloud-starter-netflix-ribbon 31 | org.springframework.cloud 32 | 33 | 34 | ribbon-eureka 35 | com.netflix.ribbon 36 | 37 | 38 | aws-java-sdk-core 39 | com.amazonaws 40 | 41 | 42 | aws-java-sdk-ec2 43 | com.amazonaws 44 | 45 | 46 | aws-java-sdk-autoscaling 47 | com.amazonaws 48 | 49 | 50 | aws-java-sdk-sts 51 | com.amazonaws 52 | 53 | 54 | aws-java-sdk-route53 55 | com.amazonaws 56 | 57 | 58 | 59 | org.springframework.security 60 | spring-security-crypto 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-maven-plugin 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.config; 2 | 3 | import io.swagger.models.auth.In; 4 | import org.springframework.boot.SpringBootVersion; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import springfox.documentation.builders.ApiInfoBuilder; 8 | import springfox.documentation.builders.PathSelectors; 9 | import springfox.documentation.builders.RequestHandlerSelectors; 10 | import springfox.documentation.service.*; 11 | import springfox.documentation.spi.DocumentationType; 12 | import springfox.documentation.spi.service.contexts.SecurityContext; 13 | import springfox.documentation.spring.web.plugins.Docket; 14 | 15 | import java.util.*; 16 | 17 | @Configuration 18 | public class SwaggerConfig extends SwaggerProperties { 19 | 20 | @Bean 21 | public Docket intiDocket() { 22 | return new Docket(DocumentationType.OAS_30).pathMapping("/") 23 | 24 | // 定义是否开启swagger,false为关闭,可以通过变量控制 25 | .enable(this.getEnable()) 26 | 27 | // 将api的元信息设置为包含在json ResourceListing响应中。 28 | .apiInfo(apiInfo()) 29 | 30 | // 接口调试地址 31 | .host(this.getTryHost()) 32 | 33 | // 选择哪些接口作为swagger的doc发布 34 | .select() 35 | .apis(RequestHandlerSelectors.any()) 36 | .paths(PathSelectors.any()) 37 | .build() 38 | 39 | // 支持的通讯协议集合 40 | .protocols(newHashSet("https", "http")) 41 | 42 | // 授权信息设置,必要的header token等认证信息 43 | .securitySchemes(securitySchemes()) 44 | 45 | // 授权信息全局应用 46 | .securityContexts(securityContexts()); 47 | } 48 | 49 | /** 50 | * API 页面上半部分展示信息 51 | */ 52 | private ApiInfo apiInfo() { 53 | return new ApiInfoBuilder() 54 | .title(this.getApplicationName() + " Api Doc") 55 | .description(this.getApplicationDescription()) 56 | .contact(new Contact("hiling", "http://www.mn-soft.com", "admin@mn-soft.com")) 57 | .version("Application Version: " + this.getApplicationVersion() + ", Spring Boot Version: " + SpringBootVersion.getVersion()) 58 | .build(); 59 | } 60 | 61 | /** 62 | * 设置授权信息 63 | */ 64 | private List securitySchemes() { 65 | ApiKey apiKey = new ApiKey("BASE_TOKEN", "token", In.HEADER.toValue()); 66 | return Collections.singletonList(apiKey); 67 | } 68 | 69 | /** 70 | * 授权信息全局应用 71 | */ 72 | private List securityContexts() { 73 | return Collections.singletonList( 74 | SecurityContext.builder() 75 | .securityReferences(Collections.singletonList(new SecurityReference("BASE_TOKEN", new AuthorizationScope[]{new AuthorizationScope("global", "")}))) 76 | .build() 77 | ); 78 | } 79 | 80 | @SafeVarargs 81 | private final Set newHashSet(T... ts) { 82 | if (ts.length > 0) { 83 | return new LinkedHashSet<>(Arrays.asList(ts)); 84 | } 85 | return null; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /modules/mini-item/src/test/java/com/github/hiling/item/ItemApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.item; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Random; 11 | import java.util.concurrent.*; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest 15 | public class ItemApplicationTests { 16 | 17 | @Test 18 | public void contextLoads() { 19 | } 20 | 21 | @Test 22 | public void testThread(){ 23 | Long startTime = System.currentTimeMillis(); 24 | ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);//创建只有2个线程的线程池 25 | //存放结果的列表 26 | List> resultList = new ArrayList<>(); 27 | //通过Random类生成一个随机数生成器 28 | Random random = new Random(); 29 | for (int i = 0; i < 10; i++) { 30 | int number = random.nextInt(10); 31 | FactorialCalculator calculator = new FactorialCalculator(number); 32 | Future result = executor.submit(calculator); 33 | resultList.add(result); 34 | } 35 | //创建一个循环来监控执行器的状态 36 | try { 37 | while (executor.getCompletedTaskCount() < resultList.size()) { 38 | System.out.printf("\n已完成的线程数量: %d\n", executor.getCompletedTaskCount()); 39 | for (int i = 0; i < resultList.size(); i++) { 40 | Future result = resultList.get(i); 41 | System.out.printf("第 %d 个线程 : 是否完成:%s\n", i, result.isDone()); 42 | } 43 | Thread.sleep(50); 44 | } 45 | } catch (InterruptedException e) { 46 | // TODO Auto-generated catch block 47 | e.printStackTrace(); 48 | } 49 | System.out.println("全部线程执行结束"); 50 | try { 51 | for (int i = 0; i < resultList.size(); i++) { 52 | Future result = resultList.get(i); 53 | Integer number = null; 54 | number = result.get(); 55 | System.out.printf("第 %d 个线程 执行结果是: %d\n", i, number); 56 | } 57 | } catch (InterruptedException e) { 58 | // TODO Auto-generated catch block 59 | e.printStackTrace(); 60 | } catch (ExecutionException e) { 61 | // TODO Auto-generated catch block 62 | e.printStackTrace(); 63 | } 64 | executor.shutdown(); 65 | Long endTime = System.currentTimeMillis(); 66 | System.out.println("使用时间 = [" + (endTime - startTime) + "]"); 67 | } 68 | 69 | public class FactorialCalculator implements Callable { 70 | private int number; 71 | 72 | public FactorialCalculator(int number) { 73 | this.number = number; 74 | } 75 | 76 | //计算阶乘 77 | public Integer call() throws Exception { 78 | Integer result = 1; 79 | if (number == 0 || number == 1) 80 | result = 1; 81 | else { 82 | for (int i = 2; i <= number; i++) { 83 | result *= i; 84 | //为了演示效果,休眠20ms 85 | Thread.sleep(20); 86 | } 87 | } 88 | System.out.printf("线程:%s," + number + "!= %d\n", Thread.currentThread().getName(), result); 89 | return result; 90 | } 91 | 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/task/RemoveAccessTokenThread.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.task; 2 | 3 | import com.github.hiling.auth.model.RevokeToken; 4 | import com.github.hiling.auth.constant.RedisNamespaces; 5 | import com.github.hiling.auth.service.AccessTokenService; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.data.redis.core.StringRedisTemplate; 9 | 10 | import java.time.LocalDateTime; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.concurrent.ConcurrentLinkedQueue; 14 | 15 | /** 16 | * Author by hiling, Email admin@mn-soft.com, Date on 10/16/2018. 17 | */ 18 | @Slf4j 19 | public class RemoveAccessTokenThread extends BaseRemoveTokenThread { 20 | 21 | /** 22 | * 每执行一次清除过期Token后sleep的时间(秒),默认1秒 23 | */ 24 | @Value("${oauth.access-token.remove-expired.loop-wait:1}") 25 | final int accessTokenLoopWait = 10; 26 | 27 | /** 28 | * 每次移除过期数据时,保留最近几秒的数据,默认5秒 29 | */ 30 | @Value("${oauth.access-token.remove-expired.reserve-time:5}") 31 | final int accessTokenReserveTime = 5; 32 | 33 | /** 34 | * 每次移除的最多行数,避免单次处理数据过多导致数据库性能压力,默认1000条 35 | */ 36 | @Value("${oauth.access-token.remove-expired.max-remove-count:1000}") 37 | final int accessTokenMaxRemoveCount = 1000; 38 | 39 | final AccessTokenService accessTokenService; 40 | final StringRedisTemplate stringRedisTemplate; 41 | 42 | /** 43 | * 过期的Access Token队列(先进先出) 44 | * size()要遍历整个集合,很慢,避免使用 45 | */ 46 | private static ConcurrentLinkedQueue accessTokenQueue = new ConcurrentLinkedQueue<>(); 47 | 48 | /** 49 | * 过期的Access Token队列(先进先出) 50 | * size()要遍历整个集合,很慢,避免使用 51 | */ 52 | public RemoveAccessTokenThread(AccessTokenService accessTokenService, 53 | StringRedisTemplate stringRedisTemplate) { 54 | setName("RemoveAccessTokenThread"); 55 | this.revokeTokens = accessTokenQueue; 56 | this.accessTokenService = accessTokenService; 57 | this.stringRedisTemplate = stringRedisTemplate; 58 | this.loopWait = accessTokenLoopWait; 59 | this.reserveTime = accessTokenReserveTime; 60 | this.maxRemoveCount = accessTokenMaxRemoveCount; 61 | } 62 | 63 | public static boolean addAccessTokenToRevokeQueue(String clientId, Long userId, LocalDateTime time) { 64 | log.debug("addAccessTokenToRevokeQueue({}, {},{})", clientId, userId, time); 65 | return accessTokenQueue.offer(RevokeToken.builder().clientId(clientId).userId(userId).time(time).build()); 66 | } 67 | 68 | @Override 69 | protected void removeRevokeAndExpiredToken(ArrayList revokeList) { 70 | try { 71 | //查询被吊销的token 72 | List revokeTokens = accessTokenService.getRevokeAccessToken(revokeList); 73 | 74 | //删除数据库中被吊销的token 75 | accessTokenService.batchDeleteByAccessToken(revokeTokens); 76 | 77 | //删除缓存中被吊销的token 78 | List revokeTokenKeys = new ArrayList<>(revokeTokens.size()); 79 | for (String token : revokeTokens) { 80 | revokeTokenKeys.add(RedisNamespaces.ACCESS_TOKEN + token); 81 | } 82 | stringRedisTemplate.delete(revokeTokenKeys); 83 | 84 | //删除过期的token 85 | accessTokenService.deleteExpiredAccessToken(); 86 | log.debug("删除过期的Access Token"); 87 | 88 | } catch (Exception e) { 89 | e.printStackTrace(); 90 | log.error(e.getMessage()); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /modules/mini-item/src/main/java/com/github/hiling/item/controller/ItemController.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.item.controller; 2 | 3 | import com.netflix.discovery.DiscoveryClient; 4 | import com.netflix.discovery.DiscoveryManager; 5 | import lombok.Data; 6 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import java.time.LocalDateTime; 18 | import java.time.format.DateTimeFormatter; 19 | import java.util.*; 20 | 21 | /** 22 | * Author by hiling, Email admin@mn-soft.com, Date on 11/20/2018. 23 | */ 24 | @RestController 25 | @RequestMapping("/item") 26 | public class ItemController { 27 | 28 | @Value("${aaa:default_aaa}") 29 | private String getAaa; 30 | 31 | @Value("${common:default}") 32 | private String getCommon; 33 | 34 | @GetMapping("config") 35 | public String getConfig() { 36 | return getAaa + " - "+ getCommon; 37 | } 38 | 39 | /** 40 | * 测试高可用 41 | * 42 | * @param request 43 | * @return 44 | */ 45 | @GetMapping("url") 46 | public String get(HttpServletRequest request) { 47 | 48 | return ">>>>>" + "Host:" + request.getRemoteHost() + " Port: 【" + request.getServerPort() 49 | + "】 Path:" + request.getRequestURI(); 50 | } 51 | 52 | @GetMapping("users") 53 | public ResponseEntity> getUsers(HttpServletRequest request) { 54 | List users = new ArrayList(); 55 | for (int i = 0; i < 10; i++) { 56 | users.add(new UserBean("name1_" + String.valueOf(i), i)); 57 | } 58 | ResponseEntity> responseEntity=new ResponseEntity<>(users,HttpStatus.OK); 59 | 60 | return responseEntity; 61 | } 62 | 63 | @GetMapping("oom/{id}") 64 | public String testOOM(@PathVariable Integer id) { 65 | 66 | List users = new ArrayList(); 67 | 68 | for (int i = 0; i < id; i++) { 69 | users.add(new UserBean("name_" + String.valueOf(i), i)); 70 | } 71 | return "OK"; 72 | } 73 | 74 | @Data 75 | public class UserBean { 76 | String name; 77 | int age; 78 | 79 | public UserBean(String name, int age) { 80 | this.name = name; 81 | this.age = age; 82 | } 83 | } 84 | 85 | @Autowired 86 | RabbitTemplate rabbitTemplate; //使用RabbitTemplate,这提供了接收/发送等等方法 87 | 88 | @GetMapping("/sendDirectMessage") 89 | public String sendDirectMessage() { 90 | String messageId = String.valueOf(UUID.randomUUID()); 91 | String messageData = "test message, hello!"; 92 | String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); 93 | Map map=new HashMap<>(); 94 | map.put("messageId",messageId); 95 | map.put("messageData",messageData); 96 | map.put("createTime",createTime); 97 | //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange 98 | rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting", map); 99 | return createTime; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /mini-gateway/src/main/java/com/github/hiling/gateway/filter/AuthFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.gateway.filter; 2 | 3 | import com.github.hiling.common.constant.ServiceNames; 4 | import com.github.hiling.common.exception.BusinessException; 5 | import com.github.hiling.common.utils.StringUtils; 6 | import com.netflix.zuul.ZuulFilter; 7 | import com.netflix.zuul.context.RequestContext; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; 12 | import org.springframework.cloud.openfeign.FeignClient; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.util.Enumeration; 20 | import java.util.List; 21 | 22 | /** 23 | * Author by hiling, Email admin@mn-soft.com, Date on 10/15/2018. 24 | */ 25 | @Slf4j 26 | @Component 27 | public class AuthFilter extends ZuulFilter { 28 | @Autowired 29 | TokenFeignClient tokenService; 30 | public static final String ACCESS_TOKEN_ERROR = "access_token无效或已过期。"; 31 | 32 | @Override 33 | public String filterType() { 34 | return FilterConstants.PRE_TYPE; 35 | } 36 | 37 | /** 38 | * int值来定义过滤器的执行顺序,数值越小优先级越高 39 | * @return 40 | */ 41 | @Override 42 | public int filterOrder() { 43 | return 2; 44 | } 45 | 46 | @Override 47 | public boolean shouldFilter() { 48 | return true; 49 | } 50 | 51 | @Value("#{'${auth.ignore.path-prefix}'.split(',')}") 52 | private List authIgnorePaths; 53 | 54 | @Override 55 | public Object run() { 56 | RequestContext ctx = RequestContext.getCurrentContext(); 57 | HttpServletRequest request = ctx.getRequest(); 58 | 59 | String uri = request.getRequestURI(); 60 | 61 | //忽略不需要授权的连接 62 | int pathCount = authIgnorePaths.size(); 63 | for (int i = 0; i < pathCount; i++) { 64 | if (uri.startsWith(authIgnorePaths.get(i))) { 65 | return null; 66 | } 67 | } 68 | 69 | String method = request.getMethod(); 70 | 71 | log.debug("------------------->pre Request:{}:{}",method, uri); 72 | 73 | //内部应用通过jwt_token访问后端服务 74 | String jwtToken = request.getHeader("jwt_token"); 75 | if (StringUtils.isNotEmpty(jwtToken)) { 76 | return null; 77 | } 78 | 79 | //外部应用通过access_token访问后端服务,需要使用access_token在OAuth Server上换取jwtToken后传递给后方服务 80 | String accessToken = request.getParameter("access_token"); 81 | 82 | if (StringUtils.isEmpty(accessToken)) { 83 | throw new BusinessException(ACCESS_TOKEN_ERROR); 84 | } 85 | 86 | try { 87 | jwtToken = tokenService.getJwtToken(accessToken); 88 | } catch (BusinessException e) { 89 | throw new BusinessException(e.getCode(), e.getMessage()); 90 | } 91 | 92 | if (StringUtils.isEmpty(jwtToken)) { 93 | throw new BusinessException(ACCESS_TOKEN_ERROR); 94 | } 95 | ctx.addZuulRequestHeader("jwtToken", jwtToken); 96 | 97 | return null; 98 | } 99 | 100 | @FeignClient(name = ServiceNames.AUTH_SERVICE) 101 | public interface TokenFeignClient { 102 | @RequestMapping(value = "token?access_token={access_token}", method = RequestMethod.GET) 103 | String getJwtToken(@PathVariable("access_token") String accessToken); 104 | } 105 | } -------------------------------------------------------------------------------- /mini-config/config-repo/auth-service-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8085 3 | 4 | spring: 5 | redis: 6 | database: 4 7 | datasource: 8 | url: jdbc:mysql://127.0.0.1/mini_api_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true 9 | username: root 10 | password: hiling11 11 | # OAuth数据库地址配置 12 | oauth: 13 | driver-class-name: com.mysql.cj.jdbc.Driver 14 | jdbc-url: jdbc:mysql://127.0.0.1/mini_oauth_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true 15 | username: root 16 | password: hiling11 17 | minimumIdle: 1 18 | maximumPoolSize: 4 19 | idleTimeout: 60000 20 | maxLifetime: 120000 21 | # Client数据库地址配置 22 | client: 23 | driver-class-name: com.mysql.cj.jdbc.Driver 24 | jdbc-url: jdbc:mysql://127.0.0.1/mini_oauth_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true 25 | username: root 26 | password: hiling11 27 | minimumIdle: 1 28 | maximumPoolSize: 4 29 | idleTimeout: 60000 30 | maxLifetime: 120000 31 | # User数据库地址配置 32 | user: 33 | driver-class-name: com.mysql.cj.jdbc.Driver 34 | jdbc-url: jdbc:mysql://127.0.0.1/mini_api_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true 35 | username: root 36 | password: hiling11 37 | readOnly: true #只读链接 38 | minimumIdle: 1 39 | maximumPoolSize: 4 40 | idleTimeout: 60000 41 | maxLifetime: 120000 42 | 43 | ###OAuth配置### 44 | 45 | oauth: 46 | ##Password模式配置## 47 | # 当Password时,clientSecret是否必须验证 48 | password: 49 | client-secret: 50 | required: false 51 | ##AccessToken## 52 | #accessToken过期时间(秒)默认2小时(每次生成accessToken时重新生成jwtToken,因此,jwtToken过期时间与accessToken过期时间相同) 53 | access-token: 54 | expiration: 3600 55 | remove-expired: 56 | #每执行一次清除过期Token后sleep的时间(秒),默认1秒 57 | loop-wait: 10 58 | #每次移除过期数据时,保留最近几秒的数据,默认5秒,,避免当客户端同时发起获取token和刷新token的请求时,先执行了刷新token,导致获取token失败。 59 | reserve-time: 5 60 | #每次移除的最多客户&用户数,避免单次处理数据过多导致数据库性能压力,默认1000条 61 | max-remove-count: 1000 62 | ##RefreshToken## 63 | #refreshToken过期设置,单位为秒 64 | #场景示例: 65 | # 1、滑动过期=0,绝对过期=0,表示永不过期。 66 | # 2、滑动过期=1天,绝对过期=0天,表示1天内没有调用后过期。 67 | # 3、滑动过期=0,绝对过期=1天,表示不管是否有调用,总是1天后过期。 68 | # 4、滑动过期=1天,绝对过期=7天,表示1天内没有调用后过期,最多缓存7天。 69 | refresh-token: 70 | #refreshToken滑动过期时间(秒) 默认1天 71 | sliding-expiration: 7200 72 | #refreshToken绝对过期时间(秒) 默认7天 73 | absolute-expiration: 86400 74 | remove-expired: 75 | loop-wait: 10 76 | max-remove-count: 1000 77 | reserve-time: 5 78 | # 当OAuth的grant_type=password时,验证username、password的方式,支持:url和sql; 79 | # 当为url时,使用外部服务验证用户名密码的正确性,需要配置url地址; 80 | # 当为sql时,直接使用配置的sql脚本验证,需同时配置sql脚本和数据库链接字符串 81 | user: 82 | login: 83 | type: sql 84 | # 验证用户名密码的服务地址,参数支持{username}和{password} 85 | url: http://127.0.0.1:8088/user?username={username}&password={password} 86 | # 验证用户名密码的sql脚本 87 | # 支持的列为:userId, username, password, scope,salt(密码加盐,如果为固定值,可以如 'mini' as salt,BCrypt算法时不需要) 88 | # 如果DB中的列表名不一致时,请使用as重命名,如:id as userId 89 | # 查询参数为:#{username},但Properties中“#”号为特殊字符,程序中会自动给“{”前添加“#”号,因此只需要输入{username}即可 90 | sql: select id as userId, username, password, salt from user where username={username} 91 | # 密码加密方式:支持none(明文),md5、bcrypt、pbkdf2 92 | password: 93 | hash: 94 | type: none 95 | # 允许操作client模块的scope,即:当某client的scope包含oauth-client时,可操作client模块 96 | client: 97 | allow: 98 | scope: oauth-client 99 | 100 | swagger: 101 | enable: true 102 | application-name: ${spring.application.name} 103 | application-version: 1.0 104 | application-description: 用户服务 105 | try-host: http://127.0.0.1:${server.port} 106 | 107 | 108 | -------------------------------------------------------------------------------- /mini-oauth-client/src/main/java/com/github/hiling/oauth/JwtUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.oauth; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | 7 | import javax.crypto.spec.SecretKeySpec; 8 | import javax.xml.bind.DatatypeConverter; 9 | import java.security.Key; 10 | import java.util.Date; 11 | import java.util.Map; 12 | import java.util.logging.Logger; 13 | 14 | /** 15 | * Author by hiling, Email admin@mn-soft.com, Date on 12/14/2018. 16 | */ 17 | public class JwtUtils { 18 | 19 | private static Logger log = Logger.getLogger("JwtUtils"); 20 | /** 21 | * 用户编号:sub = Subject, JWT面向的用户 22 | */ 23 | public static final String USER_ID_KEY = "sub"; 24 | 25 | 26 | /** 27 | * 客户端编号:aud = Audience 接受JWT的一方 28 | */ 29 | public static final String CLIENT_ID_KEY = "aud"; 30 | 31 | /** 32 | * 用户名称, 自定义属性 33 | */ 34 | public static final String USER_NAME_KEY = "unm"; 35 | 36 | 37 | /** 38 | * 授权范围,自定义属性 39 | */ 40 | public static final String SCOPE_KEY = "scp"; 41 | 42 | /** 43 | * 使用HS256签名算法和生成的signingKey最终的Token,claims中是有效载荷 44 | * 45 | * @param claims 46 | * @return 47 | */ 48 | public static String createJavaWebToken(Map claims) { 49 | return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, getKeyInstance()).compact(); 50 | } 51 | 52 | /** 53 | * 使用HS256签名算法和生成的signingKey最终的Token,claims中是有效载荷 54 | * 55 | * @param userName = sub JWT面向的用户 (User) 56 | * @param clientId = aud 接受JWT的一方 (Client) 57 | * @param expiration = exp 过期时间 58 | * @param issuedAt = iat 签发时间 59 | * @return 60 | */ 61 | public static String createJavaWebToken(Long userId, String userName, String clientId, String scope, 62 | Date expiration, Date issuedAt) { 63 | 64 | Claims claims = Jwts.claims(); 65 | claims.put(USER_ID_KEY, userId); 66 | claims.put(USER_NAME_KEY, userName); 67 | claims.put(CLIENT_ID_KEY, clientId); 68 | claims.put(SCOPE_KEY, scope); 69 | 70 | String token = Jwts.builder() 71 | .setClaims(claims) 72 | //JWT的签发者 73 | //.setIssuer("oauth") 74 | //.setSubject(userId) 75 | //.setAudience(clientId) 76 | .setExpiration(expiration) 77 | .setIssuedAt(issuedAt) 78 | .signWith(SignatureAlgorithm.HS256, getKeyInstance()) 79 | .compact(); 80 | return token; 81 | } 82 | 83 | /** 84 | * 解析Token,同时也能验证Token,当验证失败返回null 85 | * 86 | * @param jwt 87 | * @return 88 | */ 89 | public static Map parserJavaWebToken(String jwt) { 90 | 91 | if (jwt == null || jwt.isEmpty()) { 92 | return null; 93 | } 94 | 95 | if (jwt.startsWith("Bearer ")) { 96 | jwt = jwt.substring(7); 97 | } 98 | 99 | try { 100 | Map jwtClaims = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwt).getBody(); 101 | return jwtClaims; 102 | } catch (Exception e) { 103 | log.warning("json web token verify failed. error message:" + e.getMessage()); 104 | return null; 105 | } 106 | } 107 | 108 | /** 109 | * 该方法使用HS256算法和Secret生成signKey 110 | * 111 | * @return 112 | */ 113 | private static Key getKeyInstance() { 114 | //We will sign our JwtUtils with our ApiKey secret 115 | SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; 116 | byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("Trnj5MFuNPbAq2V0gbHsv9qMENRT12EI"); 117 | Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); 118 | return signingKey; 119 | } 120 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/DateTimeUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils; 2 | 3 | import org.apache.commons.lang3.time.DateUtils; 4 | 5 | import java.time.*; 6 | import java.time.format.DateTimeFormatter; 7 | import java.time.temporal.ChronoUnit; 8 | import java.util.Date; 9 | 10 | /** 11 | * Author by hiling, Email admin@mn-soft.com, Date on 10/8/2018. 12 | */ 13 | public class DateTimeUtils extends DateUtils { 14 | 15 | /** 16 | * 将 Date 转换成LocalDate * atZone()方法返回在指定时区从此Instant生成的ZonedDateTime。 17 | */ 18 | public static LocalDate dateToLocalDate(Date date) { 19 | Instant instant = date.toInstant(); 20 | ZoneId zoneId = ZoneId.systemDefault(); 21 | return instant.atZone(zoneId).toLocalDate(); 22 | } 23 | 24 | /** 25 | * 将 Date 转换成LocalDateTime * atZone()方法返回在指定时区从此Instant生成的ZonedDateTime。 26 | */ 27 | public static LocalDateTime dateToLocalDateTime(Date date) { 28 | Instant instant = date.toInstant(); 29 | ZoneId zoneId = ZoneId.systemDefault(); 30 | return instant.atZone(zoneId).toLocalDateTime(); 31 | } 32 | 33 | /** 34 | * 将LocalDate 转换成 Date 35 | */ 36 | public static Date localDateToDate(LocalDate localDate) { 37 | ZoneId zoneId = ZoneId.systemDefault(); 38 | ZonedDateTime zdt = localDate.atStartOfDay(zoneId); 39 | return Date.from(zdt.toInstant()); 40 | } 41 | 42 | /** 43 | * 将LocalDateTime 转换成 Date 44 | */ 45 | public static Date localDateTimeToDate(LocalDateTime localDateTime) { 46 | ZoneId zoneId = ZoneId.systemDefault(); 47 | ZonedDateTime zdt = localDateTime.atZone(zoneId); 48 | return Date.from(zdt.toInstant()); 49 | } 50 | 51 | /** 52 | * 将localDate按照一定的格式转换成String 53 | * @param localDate 54 | * @param pattern 对LocalDate进行格式化时不能带时分秒信息,否则报错 55 | * @return 56 | */ 57 | public static String localDateFormat(LocalDate localDate, String pattern) { 58 | return localDate.format(DateTimeFormatter.ofPattern(pattern)); 59 | } 60 | 61 | /** 62 | * 将localDateTime 按照一定的格式转换成String 63 | * @param pattern 如:yyyy-MM-dd 64 | */ 65 | public static String localDateTimeFormat(LocalDateTime localDateTime, String pattern) { 66 | return localDateTime.format(DateTimeFormatter.ofPattern(pattern)); 67 | } 68 | 69 | /** 70 | * 将String 按照pattern 转换成LocalDate 71 | */ 72 | public static LocalDate localDateParse(String date, String pattern) { 73 | return LocalDate.parse(date, DateTimeFormatter.ofPattern(pattern)); 74 | } 75 | 76 | /** 77 | * 将String 按照pattern 转换成LocalDateTime 78 | */ 79 | public static LocalDateTime localDateTimeParse(String time, String pattern) { 80 | return LocalDateTime.parse(time, DateTimeFormatter.ofPattern(pattern)); 81 | } 82 | 83 | /** 84 | * 将String 按照pattern 转换成Date 85 | */ 86 | public static Date dateParse(String date, String pattern) { 87 | LocalDateTime localDateTime = localDateTimeParse(date, pattern); 88 | return localDateTimeToDate(localDateTime); 89 | } 90 | 91 | /** 92 | * 将date转换成String 93 | */ 94 | public static String dateFormat(Date date, String pattern) { 95 | return localDateTimeFormat(dateToLocalDateTime(date), pattern); 96 | } 97 | 98 | /** 99 | * 获取两个日期的差 field参数为ChronoUnit.* 100 | * @param startTime 101 | * @param endTime 102 | * @param field 单位(年月日时分秒) 103 | * @return 104 | */ 105 | public static long betweenTwoTime(LocalDateTime startTime, LocalDateTime endTime, ChronoUnit field) { 106 | Period period = Period.between(LocalDate.from(startTime), LocalDate.from(endTime)); 107 | if (field == ChronoUnit.YEARS) { 108 | return period.getYears(); 109 | } 110 | if (field == ChronoUnit.MONTHS) { 111 | return period.getYears() * 12 + period.getMonths(); 112 | } 113 | return field.between(startTime, endTime); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/interceptor/ApiLogAspect.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.interceptor; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.github.hiling.common.config.BaseConfig; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.aspectj.lang.JoinPoint; 7 | import org.aspectj.lang.annotation.*; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.context.request.RequestContextHolder; 10 | import org.springframework.web.context.request.ServletRequestAttributes; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | 14 | @Aspect 15 | @Component 16 | @Slf4j 17 | public class ApiLogAspect extends BaseConfig { 18 | 19 | /** 20 | * 控制器切点 21 | */ 22 | @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)" 23 | + " || @annotation(org.springframework.web.bind.annotation.RestController)" 24 | + " || @annotation(org.springframework.web.bind.annotation.GetMapping)" 25 | + " || @annotation(org.springframework.web.bind.annotation.PostMapping)" 26 | + " || @annotation(org.springframework.web.bind.annotation.DeleteMapping)" 27 | + " || @annotation(org.springframework.web.bind.annotation.PutMapping)" 28 | + " || @annotation(org.springframework.web.bind.annotation.PatchMapping)" 29 | ) 30 | public void controllerAspect() { 31 | 32 | } 33 | 34 | /** 35 | * 返回通知 36 | * 37 | * @param joinPoint 38 | */ 39 | @Before(value = "controllerAspect()") 40 | public void beforeReturnMethod(JoinPoint joinPoint) { 41 | if (loggingHttpRequest) { 42 | beforeLog(joinPoint); 43 | } 44 | } 45 | 46 | /** 47 | * 返回通知 48 | * 49 | * @param joinPoint 50 | * @param resp 此参数值必须和方法签名里面的resp一致 51 | */ 52 | @AfterReturning(pointcut = "controllerAspect()", returning = "resp") 53 | public void afterReturnMethod(JoinPoint joinPoint, Object resp) { 54 | if (loggingHttpResponse) { 55 | afterLog(joinPoint, resp); 56 | } 57 | } 58 | 59 | /** 60 | * 抛出通知 61 | * 62 | * @param joinPoint 63 | * @param ex 此参数值必须和方法签名里面的ex一致 64 | */ 65 | @AfterThrowing(pointcut = "controllerAspect()", throwing = "ex") 66 | public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) { 67 | afterLog(joinPoint, ex); 68 | } 69 | 70 | 71 | /** 72 | * 处理请求日志 73 | * 74 | * @param joinPoint 75 | */ 76 | public void beforeLog(JoinPoint joinPoint) { 77 | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 78 | HttpServletRequest request = attributes.getRequest(); 79 | 80 | String body; 81 | if (loggingHttpRequestBody && request instanceof RequestWrapper) { 82 | body = new RequestWrapper(request).getBody(); 83 | } else { 84 | body = ""; 85 | } 86 | 87 | //只有打印response时,tracing才有用 88 | log.info(">>>>>>请求日志:[{} {}{}] keys={} token={} body={} ip={}", 89 | request.getMethod(), 90 | request.getRequestURI(), 91 | request.getQueryString() == null ? "" : "?" + request.getQueryString(), 92 | request.getHeader("keys"), 93 | request.getHeader("token"), 94 | body, 95 | request.getRemoteAddr() 96 | ); 97 | 98 | //class_method={} joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName() 99 | //log.info("ARGS : " + JSON.toJSONString(joinPoint.getArgs())); 100 | } 101 | 102 | /** 103 | * 处理响应日志 104 | * 105 | * @param joinPoint 106 | * @param object 107 | */ 108 | public void afterLog(JoinPoint joinPoint, Object object) { 109 | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 110 | HttpServletRequest request = attributes.getRequest(); 111 | 112 | log.debug(">>>>>>响应日志:[{} {}{}] resp={} args={}", 113 | request.getMethod(), 114 | request.getRequestURI(), 115 | request.getQueryString() == null ? "" : "?" + request.getQueryString(), 116 | JSON.toJSONString(object), 117 | JSON.toJSONString(joinPoint.getArgs()) 118 | ); 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Mini-Platform轻量级微服务平台 2 | 3 | - Mini-Platform致力于打造更简洁易用的轻量级微服务治理平台,更易于实施与运维; 4 | - 核心技术:SpringBoot、Spring Cloud、OAuth2(自研)、MyBatis、Redis、MySQL; 5 | - 核心功能:服务注册与发现、服务网关、负载均衡、统一认证、配置中心、异常处理等; 6 | 7 | --- 8 | ## mini-config - 配置中心 9 | - 采用Spring Cloud Config作为统一的配置中心 10 | - 所有服务启动均需要读取配置,因此配置中心为第一个启动服务; 11 | - 其他服务通过uri访问配置中心,当启动多个节点时,需要通过nginx等做代理达到高可用 12 | 13 | --- 14 | ## mini-discovery - 注册中心 15 | - 采用Eureka做服务自动注册与发现; 16 | - 所有服务启动均需注册到注册中心,因此注册中心为第二个启动服务; 17 | - 注册中心启动多个节点时,可以在配置中心的config-common/common-discovery中配置多个节点地址 18 | 19 | --- 20 | ## mini-gateway - API网关 21 | - 采用Zuul做服务网关; 22 | - 外部请求均需要通过网关进行转发,因此API网关为第三启动服务; 23 | - API网关启动多个节点时,需要通过nginx等做代理达到高可用; 24 | - Zuul支持三种路由策略: 25 | 1. 支持基于service-id的动态路由策略,支持负载均衡,支持服务的**自动**注册与发现,当服务地址变化后**无需手动配置**,当后端服务引入Eureka client时可选用; 26 | 2. 支持基于url的动态路由策略,支持负载均衡,支持服务的**手动**注册与发现,当服务地址变化后**需要手动配置**,当后端服务基于传统HTTP调用时可选用; 27 | 3. 支持基于默认url的动态路由策略,不支持负载均衡,后端服务需要单独处理负载均衡(如Nginx),支持服务的**手动**注册与发现,配置简单,可在测试中使用; 28 | - 支持服务异常重试,建议只开启GET的重试,且确保GET的幂等,否则建议关闭; 29 | 30 | 31 | --- 32 | ## mini-admin - 监控中心 33 | - 采用Spring Boot Admin作为统一的监控中心 34 | - 支持钉钉告警 35 | 36 | --- 37 | ## mini-auth - 授权中心 38 | - 为了更简单易用,OAuth Server采用自研实现。 39 | - GrantType支持password、client_credentials、refresh_token。 40 | - Token支持延迟吊销、滑动过期和绝对过期。 41 | - 用户名密码验证支持直连用户中心数据库验证和调用远程服务验证两种方式。 42 | - 密码模式授权,用于客户端与服务器之间的授权,流程如下: 43 | ![oauth-password-flow](./docs/images/oauth-password-flow.png "密码模式授权流程") 44 | 注:图例为三次请求,1.1-1.3为首次认证;2.1-2.5为通过Access Token访问后端资源;3.1-3.3为使用Refresh Token获取新的Access Token,可用于Access Token过期前刷新Token; 45 | 红色字体是Password与Client授权方式不同的地方。 46 | 47 | - 客户端模式授权,用于服务器与服务器之间的授权,流程如下: 48 | ![oauth-client-flow](./docs/images/oauth-client-flow.png "客户端模式授权流程") 49 | 50 | - 微服务模式授权示例,是获取到Access Token后请求后端资源的流程细化,如下: 51 | ![oauth-multi-services-flow](./docs/images/oauth-multi-services-flow.png "客户端模式授权流程") 52 | 53 | --- 54 | ## Gateway - ACL 55 | - Gateway中集成的ACL(访问控制列表)模块,对API进行权限控制。 56 | - 待实现 57 | 58 | --- 59 | ## Gateway - RateLimiting 60 | - Gateway中集成的限流模块,对API进行流量控制。 61 | - 待实现 62 | 63 | --- 64 | ## Gateway - Other 65 | - Gateway中的Log、Metrics、Trace、Alert、Security、Canary等模块。 66 | - 待实现 67 | 68 | --- 69 | ## OAuth-Client 70 | - 使用OAuth的客户端使用; 71 | - 客户端引用后,只需要继承BaseController便可方便的获取用户信息; 72 | - 参考示例:[UserController.java](https://github.com/hiling/mini-platform/blob/master/modules/user/src/main/java/com/github/hiling/user/controller/UserController.java) 73 | 74 | --- 75 | ## 项目中使用的其他技术介绍 76 | - **Lombok** 是一种 Java™ 实用工具,可用来帮助开发人员消除 Java 的冗长, 77 | 尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。 78 | - 官网地址:https://www.projectlombok.org/ 79 | - 项目地址:https://github.com/rzwitserloot/lombok 80 | - 中文介绍:https://blog.csdn.net/motui/article/details/79012846 81 | 82 | - **Swagger** 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件。 83 | 项目中集成Swagger3,访问地址:http://localhost:8085/swagger-ui/index.html 84 | - 官网地址:https://swagger.io/ 85 | 86 | - **MyBatis-Plus**(简称 MP)是一个 MyBatis 的增强工具, 87 | 在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 88 | - 官网地址:http://mp.baomidou.com/ 89 | - 项目地址:https://github.com/baomidou/mybatis-plus 90 | 91 | - **Prometheus** 是一套开源的新一代Metrics系统监控报警框架,是CNCF中重要的一员,它将所有信息都存储为时间序列数据;因此实现一种Profiling监控方式, 92 | 实时分析系统运行的状态、执行时间、调用次数等,以找到系统的热点,为性能优化提供依据。 93 | 可对核心业务指标、应用指标、系统指标等做高效的监控,可与Grafana结合打造出优秀的监控平台。 94 | - 官网地址:https://prometheus.io/ 95 | - 项目地址:https://github.com/prometheus 96 | 97 | - **Druid** 是阿里巴巴数据库事业部出品,为监控而生的数据库连接池。 98 | - 项目地址:https://github.com/alibaba/druid 99 | 100 | - **JMH** 是一个Java的微基准测试框架,精度可以精确到微秒级。 101 | - 项目地址:http://openjdk.java.net/projects/code-tools/jmh/ 102 | - 官方示例:http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/ 103 | - 中文介绍:https://www.xncoding.com/2018/01/07/java/jmh.html 104 | - 项目中OAuth基准测试代码 105 | ![create_token_test](./docs/images/create_token_test.png "基准测试代码") 106 | 注:测试代码:[UserApplicationTests.java](https://github.com/hiling/mini-platform/blob/master/modules/user/src/test/java/com/github/hiling/user/UserApplicationTests.java) 107 | - 测试结果 108 | ![create_token_test_result](./docs/images/create_token_test_result.png "基准测试结果") 109 | 注:在开发环境测试,电脑配置2C(i5-6300U)/8G/SSD,本机MySQL,使用相关参数,未考虑DB缓存等影响。 110 | 111 | - **Spring Boot Maven Plugin & Apache Maven Dependency Plugin** 可以将外部依赖jar与项目分离, 112 | 解决发布包过大问题。部署时可以将外部依赖包先上传至服务器,启动时需要使用参数-Dloader.path="lib/"加载外部依赖的jar包, 113 | 当依赖的外部jar包未更新时,不需要每次给服务器上传。 114 | 项目地址: 115 | - https://docs.spring.io/spring-boot/docs/current/maven-plugin/ 116 | - http://maven.apache.org/components/plugins/maven-dependency-plugin/ 117 | 118 | #### 欢迎Star和Fork,微信号:HilingWang,欢迎交流! -------------------------------------------------------------------------------- /modules/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | modules 6 | 0.0.1-SNAPSHOT 7 | pom 8 | 9 | modules 10 | business modules 11 | 12 | 13 | com.github.hiling 14 | mini-platform 15 | 0.0.1-SNAPSHOT 16 | ../pom.xml 17 | 18 | 19 | 20 | mini-auth 21 | mini-user 22 | mini-item 23 | 24 | 25 | 26 | 27 | com.github.hiling 28 | mini-common 29 | ${common.version} 30 | 31 | 32 | com.github.hiling 33 | mini-oauth-client 34 | ${oauth-client.version} 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-web 39 | 40 | 41 | org.springframework.cloud 42 | spring-cloud-starter-netflix-eureka-client 43 | 44 | 45 | org.springframework.cloud 46 | spring-cloud-starter-openfeign 47 | 48 | 49 | mysql 50 | mysql-connector-java 51 | runtime 52 | 53 | 54 | 55 | com.alibaba 56 | druid-spring-boot-starter 57 | 1.1.10 58 | 59 | 60 | 61 | 62 | com.baomidou 63 | mybatis-plus-boot-starter 64 | 3.3.2 65 | 66 | 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-devtools 72 | true 73 | 74 | 75 | 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-maven-plugin 80 | ${spring-boot-maven-plugin-version} 81 | 82 | 83 | true 84 | 85 | 86 | 87 | nothing 88 | nothing 89 | 90 | 91 | 92 | 93 | 94 | 95 | build-info 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-dependency-plugin 103 | 3.1.2 104 | 105 | 106 | copy-dependencies 107 | package 108 | 109 | copy-dependencies 110 | 111 | 112 | 113 | ${project.build.directory}/libs/ 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /modules/mini-auth/src/main/java/com/github/hiling/auth/controller/TokenController.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.auth.controller; 2 | 3 | import com.github.hiling.auth.constant.ErrorMessage; 4 | import com.github.hiling.auth.model.AccessToken; 5 | import com.github.hiling.auth.service.AccessTokenService; 6 | import com.github.hiling.common.exception.BusinessException; 7 | import com.github.hiling.common.utils.AddressUtils; 8 | import io.swagger.annotations.Api; 9 | import io.swagger.annotations.ApiOperation; 10 | import org.apache.commons.lang.StringUtils; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.util.Base64Utils; 14 | import org.springframework.web.bind.annotation.*; 15 | import javax.servlet.http.HttpServletRequest; 16 | 17 | import java.time.LocalDateTime; 18 | 19 | @RestController 20 | @RequestMapping("/token") 21 | @Api(value = "Token") 22 | public class TokenController { 23 | 24 | @Autowired 25 | private AccessTokenService accessTokenService; 26 | 27 | @GetMapping("/test") 28 | public ResponseEntity test() { 29 | return ResponseEntity.ok("current Time:"+LocalDateTime.now().toString()); 30 | } 31 | 32 | /** 33 | * 验证 Account Token (introspect) 34 | * 35 | * @return 返回jwtToken,如果token不存在或已过期,返回Null 36 | */ 37 | @GetMapping() 38 | @ApiOperation(value = "验证 Account Token (introspect)", notes = "返回jwtToken,如果token不存在或已过期,返回Null") 39 | public ResponseEntity getToken(@RequestParam(name = "access_token") String accessToken) { 40 | //如果没有找到,返回token过期或token非法,客户端需要通过refreshToken重新来获取accessToken 41 | String jwtToken = accessTokenService.getJwtToken(accessToken); 42 | if (StringUtils.isEmpty(jwtToken)){ 43 | return ResponseEntity.noContent().build(); 44 | } 45 | return ResponseEntity.ok(jwtToken); 46 | } 47 | 48 | //oauth2/introspect 49 | //oauth2/revoke 50 | 51 | /** 52 | * 获取 AccessToken,暂不支持scope 53 | * 54 | * @param 55 | * @param grantType 56 | * @param username 当grant_type=password时,username、password有效 57 | * @param password 58 | * @param refreshToken 当grant_type=refresh_token时,该参数有效 59 | * @return 60 | */ 61 | @PostMapping() 62 | @ApiOperation(value = "获取 AccessToken,暂不支持scope") 63 | public ResponseEntity postToken( 64 | // @RequestHeader("client_id") String clientId, 65 | // @RequestHeader("client_secret") String clientSecret, 66 | HttpServletRequest request, 67 | @RequestParam(name = "grant_type") String grantType, 68 | @RequestParam(defaultValue = "") String username, 69 | @RequestParam(defaultValue = "") String password, 70 | @RequestParam(name = "refresh_token", defaultValue = "") String refreshToken) { 71 | 72 | // client_id & client_secret可以通过参数传递,也可以通过HTTP Header的Authorization传递 73 | String clientId = request.getParameter("client_id"); 74 | String clientSecret = request.getParameter("client_secret"); 75 | 76 | if (StringUtils.isEmpty(clientId)) { 77 | String authorization = request.getHeader("Authorization"); 78 | if (authorization == null || !authorization.startsWith("Basic ")) { 79 | throw new BusinessException(ErrorMessage.TOKEN_AUTHORIZATION_ERROR); 80 | } 81 | 82 | try { 83 | String authDecode = new String(Base64Utils.decodeFromString(authorization.substring(6))); 84 | String[] accounts = authDecode.split(":"); 85 | clientId = accounts[0]; 86 | //Password模式时,clientId必填,clientSecret是可选的。 87 | if (accounts.length > 1) { 88 | clientSecret = accounts[1]; 89 | } 90 | } catch (Exception e) { 91 | throw new BusinessException(ErrorMessage.TOKEN_AUTHORIZATION_ERROR); 92 | } 93 | } 94 | 95 | String accessIp = AddressUtils.getHttpRequestIp(request); 96 | 97 | AccessToken accessToken = accessTokenService.createAccessToken(accessIp, clientId, clientSecret, grantType, username, password, refreshToken); 98 | if (accessToken != null) { 99 | return ResponseEntity.ok(accessToken); 100 | } 101 | return ResponseEntity.badRequest().build(); 102 | } 103 | 104 | /** 105 | * 吊销 Account Token (revoke) 106 | * 107 | * @return 108 | */ 109 | @DeleteMapping() 110 | public ResponseEntity revoke(@RequestParam(name = "access_token") String accessToken) { 111 | //如果没有找到,返回token过期或token非法,客户端需要通过refreshToken重新来获取accessToken 112 | Boolean result = accessTokenService.deleteToken(accessToken); 113 | return ResponseEntity.ok(result); 114 | } 115 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/config/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.config; 2 | 3 | import com.github.hiling.common.exception.BusinessException; 4 | import com.github.hiling.common.exception.ExceptionResult; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.validation.ObjectError; 9 | import org.springframework.web.bind.MethodArgumentNotValidException; 10 | import org.springframework.web.bind.annotation.ResponseBody; 11 | import org.springframework.web.servlet.NoHandlerFoundException; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.net.ConnectException; 15 | import java.time.LocalDateTime; 16 | import java.util.*; 17 | 18 | /** 19 | * Author by hiling, Email admin@mn-soft.com, Date on 10/19/2018. 20 | */ 21 | @Slf4j 22 | public class ExceptionHandler { 23 | @ResponseBody 24 | @org.springframework.web.bind.annotation.ExceptionHandler(value = BusinessException.class) 25 | public ExceptionResult businessException(HttpServletRequest request, BusinessException e) { 26 | String uri = getUri(request); 27 | String stackTrace = getStackTrace(e.getStackTrace()); 28 | writeLog(uri, e.getMessage(), stackTrace); 29 | return new ExceptionResult(e.getCode(), e.getMessage(), HttpStatus.BAD_REQUEST.value(), stackTrace, uri); 30 | } 31 | 32 | @ResponseBody 33 | @org.springframework.web.bind.annotation.ExceptionHandler(value = ConnectException.class) 34 | public ExceptionResult connectException(HttpServletRequest request, ConnectException e) { 35 | String uri = getUri(request); 36 | String stackTrace = getStackTrace(e.getStackTrace()); 37 | Integer code = HttpStatus.GATEWAY_TIMEOUT.value(); 38 | 39 | writeLog(uri, "无法连接到远程服务器。", stackTrace); 40 | return new ExceptionResult(code, "无法连接到远程服务器。", code, stackTrace, uri); 41 | } 42 | 43 | @ResponseBody 44 | @org.springframework.web.bind.annotation.ExceptionHandler(value = MethodArgumentNotValidException.class) 45 | public ExceptionResult methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) { 46 | 47 | String uri = getUri(request); 48 | String stackTrace = getStackTrace(e.getStackTrace()); 49 | List errors = e.getBindingResult().getAllErrors(); 50 | StringBuffer errorMsg = new StringBuffer(); 51 | errors.stream().forEach(x -> errorMsg.append(x.getDefaultMessage()).append(";")); 52 | String message = errorMsg.toString(); 53 | Integer code = HttpStatus.BAD_REQUEST.value(); 54 | 55 | writeLog(uri, message, stackTrace); 56 | return new ExceptionResult(code, message, code, stackTrace, uri); 57 | } 58 | 59 | @ResponseBody 60 | @org.springframework.web.bind.annotation.ExceptionHandler(value = NoHandlerFoundException.class) 61 | public ExceptionResult noHandlerFoundException(HttpServletRequest request, NoHandlerFoundException e) { 62 | String uri = getUri(request); 63 | String stackTrace = getStackTrace(e.getStackTrace()); 64 | Integer code = HttpStatus.NOT_FOUND.value(); 65 | 66 | writeLog(uri, "接口不存在!", stackTrace); 67 | return new ExceptionResult(code, "接口不存在!", code, stackTrace, uri); 68 | } 69 | 70 | @ResponseBody 71 | @org.springframework.web.bind.annotation.ExceptionHandler(value = Exception.class) 72 | public ExceptionResult defaultException(HttpServletRequest request, Exception e) { 73 | String uri = getUri(request); 74 | String stackTrace = getStackTrace(e.getStackTrace()); 75 | Integer code = HttpStatus.INTERNAL_SERVER_ERROR.value(); 76 | 77 | writeLog(uri, e.getMessage(), stackTrace); 78 | return new ExceptionResult(code, e.getMessage(), code, stackTrace, uri); 79 | } 80 | 81 | private String getStackTrace(StackTraceElement[] element) { 82 | String stackTrace = null; 83 | //堆栈信息 84 | if (element != null && element.length > 0) { 85 | stackTrace = element[0].toString(); 86 | } 87 | return stackTrace; 88 | } 89 | 90 | private String getUri(HttpServletRequest request) { 91 | String queryString = request.getQueryString(); 92 | return request.getMethod() + ":" + request.getRequestURI() + (StringUtils.isEmpty(queryString) ? "" : ("?" + queryString)); 93 | } 94 | 95 | private void writeLog(String uri, String message, String stackTrace) { 96 | StringBuilder error = new StringBuilder(); 97 | error.append(System.getProperty("line.separator")).append("Error:").append(System.getProperty("line.separator")); 98 | error.append("URI:").append(uri).append(System.getProperty("line.separator")); 99 | error.append("Message:").append(message).append(System.getProperty("line.separator")); 100 | error.append("StackTrace:").append(stackTrace).append(System.getProperty("line.separator")); 101 | error.append("Time:").append(LocalDateTime.now().toString()).append(System.getProperty("line.separator")); 102 | log.error(error.toString()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /mini-common/src/main/java/com/github/hiling/common/utils/AddressUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.hiling.common.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import java.io.IOException; 9 | import java.net.InetAddress; 10 | import java.net.NetworkInterface; 11 | import java.net.ServerSocket; 12 | import java.net.SocketAddress; 13 | import java.util.Enumeration; 14 | import java.util.regex.Pattern; 15 | 16 | /** 17 | * @author wanghailiang 18 | * @version 1.0 19 | * @date 2020/11/24 6:32 PM 20 | */ 21 | @Slf4j 22 | public class AddressUtils { 23 | 24 | private static final String LOCALHOST_IP = "127.0.0.1"; 25 | private static final String EMPTY_IP = "0.0.0.0"; 26 | private static final Pattern IP_PATTERN = Pattern.compile("[0-9]{1,3}(\\.[0-9]{1,3}){3,}"); 27 | 28 | public static boolean isAvailablePort(int port) { 29 | try (ServerSocket ss = new ServerSocket(port)) { 30 | ss.bind(null); 31 | return true; 32 | } catch (IOException e) { 33 | return false; 34 | } 35 | } 36 | 37 | private static boolean isValidHostAddress(InetAddress address) { 38 | if (address == null || address.isLoopbackAddress()) return false; 39 | String name = address.getHostAddress(); 40 | return (name != null && !EMPTY_IP.equals(name) && !LOCALHOST_IP.equals(name) && IP_PATTERN.matcher(name) 41 | .matches()); 42 | } 43 | 44 | public static String getHostIp() { 45 | InetAddress address = getHostAddress(); 46 | return address == null ? null : address.getHostAddress(); 47 | } 48 | 49 | public static String getHostName() { 50 | InetAddress address = getHostAddress(); 51 | return address == null ? null : address.getHostName(); 52 | } 53 | 54 | public static InetAddress getHostAddress() { 55 | InetAddress localAddress = null; 56 | try { 57 | Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); 58 | if (interfaces != null) { 59 | while (interfaces.hasMoreElements()) { 60 | try { 61 | NetworkInterface network = interfaces.nextElement(); 62 | Enumeration addresses = network.getInetAddresses(); 63 | if (addresses != null) { 64 | while (addresses.hasMoreElements()) { 65 | try { 66 | InetAddress address = addresses.nextElement(); 67 | if (isValidHostAddress(address)) { 68 | return address; 69 | } 70 | } catch (Throwable e) { 71 | log.warn("Failed to retriving network card ip address. cause:" + e.getMessage()); 72 | } 73 | } 74 | } 75 | } catch (Throwable e) { 76 | log.warn("Failed to retriving network card ip address. cause:" + e.getMessage()); 77 | } 78 | } 79 | } 80 | } catch (Throwable e) { 81 | log.warn("Failed to retriving network card ip address. cause:" + e.getMessage()); 82 | } 83 | log.error("Could not get local host ip address, will use 127.0.0.1 instead."); 84 | return localAddress; 85 | } 86 | 87 | public static String getHttpRequestIp(HttpServletRequest request) { 88 | 89 | String xFor = request.getHeader("X-Forwarded-For"); 90 | if (org.apache.commons.lang3.StringUtils.isNotEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)) { 91 | //多次反向代理后会有多个ip值,第一个ip才是真实ip 92 | int index = xFor.indexOf(","); 93 | if (index != -1) { 94 | return xFor.substring(0, index); 95 | } else { 96 | return xFor; 97 | } 98 | } 99 | xFor = request.getHeader("X-Real-IP"); 100 | if (org.apache.commons.lang3.StringUtils.isNotEmpty(xFor) && !"unKnown".equalsIgnoreCase(xFor)) { 101 | return xFor; 102 | } 103 | if (org.apache.commons.lang3.StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) { 104 | xFor = request.getHeader("Proxy-Client-IP"); 105 | } 106 | if (org.apache.commons.lang3.StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) { 107 | xFor = request.getHeader("WL-Proxy-Client-IP"); 108 | } 109 | if (org.apache.commons.lang3.StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) { 110 | xFor = request.getHeader("HTTP_CLIENT_IP"); 111 | } 112 | if (org.apache.commons.lang3.StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) { 113 | xFor = request.getHeader("HTTP_X_FORWARDED_FOR"); 114 | } 115 | if (StringUtils.isBlank(xFor) || "unknown".equalsIgnoreCase(xFor)) { 116 | xFor = request.getRemoteAddr(); 117 | } 118 | return xFor; 119 | } 120 | } --------------------------------------------------------------------------------