├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── deploy_to_docker.sh ├── docker-compose.debug.yml ├── docker-compose.dev.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img └── home.png ├── mmall-domain-account ├── Dockerfile ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── kay │ │ └── mmall │ │ └── account │ │ ├── AccountApplication.java │ │ ├── application │ │ ├── AccountApplicationService.java │ │ └── AddressApplicationService.java │ │ ├── controller │ │ ├── AccountController.java │ │ └── AddressController.java │ │ └── domain │ │ ├── AccountRepository.java │ │ ├── AddressRepository.java │ │ └── validation │ │ ├── AccountValidator.java │ │ ├── AuthenticatedAccount.java │ │ ├── NotConflictAccount.java │ │ └── UniqueAccount.java │ └── resources │ ├── application-test.yml │ ├── bootstrap.yml │ ├── db │ ├── data.sql │ └── schema.sql │ ├── public.cert │ ├── redisson-config.yml │ └── rsa.jks ├── mmall-domain-payment ├── Dockerfile ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── kay │ │ └── mmall │ │ ├── PaymentApplication.java │ │ └── payment │ │ ├── controller │ │ ├── PaymentController.java │ │ └── SettlementController.java │ │ └── domain │ │ ├── Payment.java │ │ ├── Wallet.java │ │ ├── application │ │ └── PaymentApplicationService.java │ │ ├── client │ │ └── ProductServiceClient.java │ │ ├── repo │ │ ├── PaymentRepository.java │ │ └── WalletRepository.java │ │ ├── service │ │ ├── PaymentService.java │ │ └── WalletService.java │ │ └── validation │ │ ├── SettlementValidator.java │ │ └── ValidStock.java │ └── resources │ ├── application-test.yml │ ├── bootstrap.yml │ ├── db │ ├── data.sql │ └── schema.sql │ ├── public.cert │ └── redisson-config.yml ├── mmall-domain-security ├── Dockerfile ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── kay │ │ └── mmall │ │ └── security │ │ ├── SecurityApplication.java │ │ ├── configuration │ │ ├── AuthenticationServerConfiguration.java │ │ ├── AuthorizationServerConfiguration.java │ │ └── WebSecurityConfiguration.java │ │ └── provider │ │ ├── PreAuthenticatedAuthenticationProvider.java │ │ ├── RSA256JWTAccessToken.java │ │ └── UsernamePasswordAuthenticationProvider.java │ └── resources │ ├── application-test.yml │ ├── bootstrap.yml │ ├── public.cert │ └── rsa.jks ├── mmall-domain-warehouse ├── Dockerfile ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── kay │ │ └── mmall │ │ └── warehouse │ │ ├── WarehouseApplication.java │ │ ├── application │ │ └── ProductApplicationService.java │ │ ├── controller │ │ ├── AdvertisementController.java │ │ └── ProductController.java │ │ └── domain │ │ ├── Advertisement.java │ │ ├── AdvertisementRepository.java │ │ ├── ProductRepository.java │ │ ├── ProductService.java │ │ ├── StockpileRepository.java │ │ └── StockpileService.java │ └── resources │ ├── application.yml │ ├── bootstrap.yml │ ├── db │ ├── data.sql │ └── schema.sql │ ├── public.cert │ └── redisson-config.yml ├── mmall-lib-infrastructure ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── github │ └── kay │ └── mmall │ ├── domain │ ├── BaseEntity.java │ ├── account │ │ ├── Account.java │ │ └── Address.java │ ├── product │ │ ├── DeliveredStatus.java │ │ ├── Product.java │ │ ├── Specification.java │ │ └── Stockpile.java │ └── security │ │ ├── AccountServiceClient.java │ │ ├── AuthenticAccount.java │ │ ├── AuthenticAccountDetailsService.java │ │ ├── AuthenticAccountRepository.java │ │ ├── GrantType.java │ │ ├── Role.java │ │ └── Scope.java │ ├── dto │ └── Settlement.java │ └── infrasucture │ ├── CROSFilter.java │ ├── EncoderConfiguration.java │ ├── common │ ├── CodedMessage.java │ ├── CommonResponse.java │ ├── ExceptionControllerAdvice.java │ └── ResourceNotFoundException.java │ ├── configuration │ ├── FeignConfiguration.java │ └── JPAConfiguration.java │ ├── lock │ ├── LockAutoConfiguration.java │ ├── LockOperationException.java │ ├── LockService.java │ ├── LockServiceDefault.java │ ├── LockServiceWithRedisson.java │ └── RedissonProperties.java │ ├── redis │ ├── RedisConfiguration.java │ ├── RedisKeyExpirationHandler.java │ ├── RedisKeyExpirationHandlerManager.java │ └── RedisKeyExpirationListener.java │ └── security │ ├── JWTAccessToken.java │ ├── JWTAccessTokenService.java │ ├── OAuthClientDetailsService.java │ ├── RSA256PublicJWTAccessToken.java │ └── ResourceServerConfiguration.java ├── mmall-platform-configuration ├── Dockerfile ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── kay │ │ └── mmall │ │ └── ConfigurationApplication.java │ └── resources │ ├── application.yml │ └── configurations │ ├── account.yml │ ├── gateway.yml │ ├── payment.yml │ ├── registry.yml │ ├── security.yml │ └── warehouse.yml ├── mmall-platform-gateway ├── Dockerfile ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── kay │ │ └── mmall │ │ └── GatewayApplication.java │ └── resources │ ├── bootstrap.yml │ └── static │ ├── index.html │ └── static │ ├── board │ ├── gitalk.css │ ├── gitalk.html │ └── gitalk.min.js │ ├── carousel │ ├── ai.png │ ├── banner1.jpg │ ├── banner2.jpg │ ├── banner3.jpg │ ├── banner4.jpg │ ├── banner5.jpg │ ├── fenix.png │ ├── fenix2.png │ └── jvm3.png │ ├── cover │ ├── product0.jpg │ ├── product1.jpg │ ├── product2.jpg │ ├── product3.jpg │ ├── product4.jpg │ ├── product5.jpg │ └── product6.jpg │ ├── css │ └── app.b5d124c9dd22d7f1d55bbcec57fb027d.css │ ├── desc │ ├── OSGi.jpg │ ├── ai.jpg │ ├── fenix.jpg │ ├── jvm2.jpg │ ├── jvm3.jpg │ └── jvms.jpg │ ├── fonts │ ├── element-icons.535877f.woff │ └── element-icons.732389d.ttf │ ├── img │ ├── bg2.ef8085e.png │ ├── cc-logo.3653e37.png │ ├── logo-color.5500ec5.png │ └── mmall.4971f42.png │ └── js │ ├── 0.c178f427b3d08777c70f.js │ ├── 1.6f63d09ab120b212c44d.js │ ├── 2.8df64f24adb63dcd7dcd.js │ ├── 3.de05a3e78470a7ab50f1.js │ ├── 4.ebe3ae2a78397bdf1010.js │ ├── 5.c79741f8718eb55113ff.js │ ├── 6.e2f43265608e8bcfeec1.js │ ├── 7.7f99ce3702c32ec932ca.js │ ├── 8.c0df9daed0421a7c48c4.js │ ├── 9.1fd42476fd68c9cb70b0.js │ ├── app.b692bd72e6d421b5e096.js │ ├── manifest.c1c199c8b8d75f95329a.js │ └── vendor.c2f13a2146485051ae24.js ├── mmall-platform-registry ├── Dockerfile ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── kay │ │ └── mmall │ │ └── RegistryApplication.java │ └── resources │ └── bootstrap.yml └── settings.gradle /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - feature/microservices_springcloud 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 16 | - name: Set up JDK 11 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: 11 20 | - name: Cache SonarCloud packages 21 | uses: actions/cache@v1 22 | with: 23 | path: ~/.sonar/cache 24 | key: ${{ runner.os }}-sonar 25 | restore-keys: ${{ runner.os }}-sonar 26 | - name: Cache Gradle packages 27 | uses: actions/cache@v1 28 | with: 29 | path: ~/.gradle/caches 30 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 31 | restore-keys: ${{ runner.os }}-gradle 32 | - name: Build and analyze 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GB_TOKEN }} # Needed to get PR information, if any 35 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 36 | run: ./gradlew build sonarqube --info 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MMall 2 | 3 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=LiuKay_mmall-java&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=LiuKay_mmall-java) 4 | [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=LiuKay_mmall-java&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=LiuKay_mmall-java) 5 | 6 | 7 | A simple project to learn different architectures. 8 | 9 | ## What - 这是什么 10 | 11 | Mmall 是一个十分简化的商城项目,仅包含了用户、支付、库存管理等能支撑一个购物流程的业务功能,同时也包括了一些非业务功能, 12 | 包括登录、身份认证、鉴权等。通过一些简化,从而来更好的学习不同的架构风格是什么样的。从单体服务风格,到微服务架构,再到 13 | 云原生,不同的架构为了架构本身的问题而引入了一些新的组件。 14 | 15 | > Based on [https://github.com/fenixsoft/monolithic_arch_springboot](https://github.com/fenixsoft/monolithic_arch_springboot) and made some improvements. 16 | > 17 | > 本项目基于周志明老师的[凤凰架构](https://icyfenix.cn/introduction/about-the-fenix-project.html)中的示例项目:[单体架构](https://github.com/fenixsoft/monolithic_arch_springboot). 在此基础上做一些改进和修改。 18 | 19 | 20 | ## Current Version - 当前分支版本: 21 | 22 | Microservices SpringCloud - 微服务 SpringCloud 版: [microservices_springcloud]([LiuKay/mmall-java at microservices_springcloud (github.com)](https://github.com/LiuKay/mmall-java/tree/microservices_springcloud)) 23 | 24 | | service | port | comment | 25 | | ---------------------------- | ---- | -------------------------------------------------------- | 26 | | mmall-domain-security | 8301 | security service, OAuth2, JWT | 27 | | mmall-domain-account | 8401 | account service | 28 | | mmall-domain-warehouse | 8501 | product, stockpile service | 29 | | mmall-domain-payment | 8601 | payment, wallet service | 30 | | mmall-domain-registry | 8761 | services registry center | 31 | | mmall-domain-gateway | 8080 | API Gateway | 32 | | mmall-platform-configuration | 8888 | configurations | 33 | | mmall-lib-infrastructure | NA | infrastructure library, domain, dto, utils, lock service | 34 | | | | | 35 | 36 | 37 | ## Technology - 技术选型 38 | 39 | - SpringBoot 40 | - Spring Cache + Redis 41 | - Redisson as distributed lock 42 | - Spring Data JPA 43 | - Spring Security 44 | - Spring Security OAuth 2.3 45 | - Spring Security JWT 46 | - Jackson 47 | - Bean Validation 2.0 (Hibernate Validator 6) 48 | - Netflix Zuul 49 | - Netflix Eureka 50 | - Netflix Feign 51 | - Spring Config 52 | 53 | ## Get Started 54 | 55 | ### Local Run - 本地运行 56 | 57 | 依赖的测试基础设施环境使用 Docker Compose 打包(见 docker-compose.yml),MySQL, Redis 等。 58 | 59 | 本地演示会将所有service 打包到 Docker 运行,详情见 `docker-compose.dev.yml` 60 | 61 | ```shell 62 | # 启动 docker 之后可以使用该命令启动演示 63 | ./deploy_to_docker.sh 64 | 65 | # 或者 66 | ./gradlew clean 67 | ./gradlew assemble 68 | docker-compose -f ./docker-compose.dev.yml up -d 69 | ``` 70 | 71 | ### Debug - 调式模式 72 | 73 | 调式模式只在 Docker 环境中启动需要的基础设施,如 Redis, MySQL 等,业务服务可以在 IDEA 中分别启动,或使用 Gradle 命令分别启动,按照先启动 registery,configuration 再启动其他。 74 | 75 | ```shell 76 | # 1.setup infrastructure 77 | docker-compose -f ./docker-compose.debug.yml up -d 78 | 79 | # 2.setup services 80 | ./gradlew :mmall-platform-registry:bootRun 81 | ./gradlew :mmall-platform-configuration:bootRun 82 | ./gradlew :mmall-platform-gateway:bootRun 83 | ./gradlew :mmall-domain-security:bootRun 84 | ./gradlew :mmall-domain-account:bootRun 85 | ./gradlew :mmall-domain-payment:bootRun 86 | ./gradlew :mmall-domain-warehouse:bootRun 87 | ``` 88 | 89 | 90 | 91 | 进入主页 [http://localhost:8080/](http://localhost:8080/) 默认账号 kaybee, 密码 123456 92 | 93 | ![home](img/home.png) 94 | 95 | ----- 96 | 97 | ### Develop Plan - 开发计划 98 | 99 | - 单体版本使用 SpringBoot (已完成),分支: [feature/monolithic_springboot](https://github.com/LiuKay/mmall-java/tree/feature/monolithic_springboot) 100 | - 微服务版本使用 Spring Cloud 体系 (已完成),分支:[feature/microservices_springcloud](https://github.com/LiuKay/mmall-java/tree/feature/microservices_springcloud) 101 | - K8s 版本 (开发中) 102 | 103 | ## Frontend Project - 前端项目 104 | 105 | [https://github.com/LiuKay/mmall-frontend](https://github.com/LiuKay/mmall-frontend) 106 | 107 | ## Other Versions - 其他版本(分支) 108 | 109 | - v1.0 110 | 111 | 单服务器 + FTP文件服务器, 112 | 113 | 主要技术:SSM/Guava/Jackson/Joda/注解 114 | 115 | - v2.0 116 | 117 | Tomcat集群+Nginx负载均衡+Redis分布式, 118 | 119 | 在V1.0基础上进行迭代重构,主要技术Redis 、Spring Schedule、Tomcat集群、Nginx负载均衡 120 | 121 | - v3.0_springboot_Deprecated (已废弃) 122 | 123 | 重构了登录鉴权的部分 124 | 125 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '2.2.5.RELEASE' 4 | id 'io.spring.dependency-management' version '1.0.10.RELEASE' 5 | id "org.sonarqube" version "3.4.0.2513" 6 | } 7 | 8 | bootJar { 9 | mainClassName = "mmall-springcloud" 10 | } 11 | 12 | subprojects { 13 | apply plugin: 'java' 14 | apply plugin: 'idea' 15 | apply plugin: 'io.spring.dependency-management' 16 | 17 | group = 'com.github.kay' 18 | version = '0.0.1-SNAPSHOT' 19 | 20 | dependencyManagement { 21 | imports { 22 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR8" 23 | mavenBom "org.springframework.boot:spring-boot-dependencies:2.2.5.RELEASE" 24 | } 25 | } 26 | 27 | compileJava { 28 | sourceCompatibility = '11' 29 | targetCompatibility = '11' 30 | } 31 | 32 | dependencies { 33 | annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" 34 | 35 | compileOnly 'org.projectlombok:lombok:1.18.24' 36 | annotationProcessor 'org.projectlombok:lombok:1.18.24' 37 | 38 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' 39 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' 40 | } 41 | } 42 | 43 | sonarqube { 44 | properties { 45 | property "sonar.projectKey", "LiuKay_mmall-java" 46 | property "sonar.organization", "liukay" 47 | property "sonar.host.url", "https://sonarcloud.io" 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /deploy_to_docker.sh: -------------------------------------------------------------------------------- 1 | # /bin/sh 2 | 3 | ./gradlew clean 4 | ./gradlew assemble 5 | 6 | #./gradlew :mmall-domain-account:bootJar 7 | #./gradlew :mmall-domain-payment:bootJar 8 | #./gradlew :mmall-domain-warehouse:bootJar 9 | #./gradlew :mmall-domain-security:bootJar 10 | #./gradlew :mmall-platform-configuration:bootJar 11 | #./gradlew :mmall-platform-gateway:bootJar 12 | #./gradlew :mmall-platform-registry:bootJar 13 | 14 | docker-compose -f ./docker-compose.dev.yml up -d 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docker-compose.debug.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | mysql: 5 | image: mysql:5.7.38 6 | ports: 7 | - 3306:3306 8 | environment: 9 | - MYSQL_ROOT_PASSWORD=root1234 10 | - MYSQL_DATABASE=mmall 11 | - MYSQL_USER=mmall 12 | - MYSQL_PASSWORD=mmall 13 | command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci 14 | 15 | redis: 16 | image: redis:latest 17 | ports: 18 | - 6379:6379 19 | command: redis-server --notify-keyspace-events Ex -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | mysql: 5 | image: mysql:5.7.38 6 | ports: 7 | - 3306:3306 8 | environment: 9 | - MYSQL_ROOT_PASSWORD=root1234 10 | - MYSQL_DATABASE=mmall 11 | - MYSQL_USER=mmall 12 | - MYSQL_PASSWORD=mmall 13 | command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci 14 | healthcheck: 15 | test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] 16 | timeout: 20s 17 | retries: 10 18 | 19 | redis: 20 | image: redis:latest 21 | ports: 22 | - 6379:6379 23 | command: redis-server --notify-keyspace-events Ex 24 | 25 | mmall-configuration: 26 | build: mmall-platform-configuration 27 | restart: always 28 | depends_on: 29 | mysql: 30 | condition: service_healthy 31 | ports: 32 | - 8888:8888 33 | 34 | mmall-registry: 35 | build: mmall-platform-registry 36 | environment: 37 | CONFIG_HOST: mmall-configuration 38 | restart: always 39 | depends_on: 40 | mmall-configuration: 41 | condition: service_healthy 42 | ports: 43 | - 8761:8761 44 | 45 | mmall-gateway: 46 | build: mmall-platform-gateway 47 | depends_on: 48 | mmall-configuration: 49 | condition: service_healthy 50 | environment: 51 | CONFIG_HOST: mmall-configuration 52 | REGISTRY_HOST: mmall-registry 53 | restart: always 54 | ports: 55 | - 8080:8080 56 | 57 | mmall-security: 58 | build: mmall-domain-security 59 | depends_on: 60 | mmall-configuration: 61 | condition: service_healthy 62 | environment: 63 | CONFIG_HOST: mmall-configuration 64 | REGISTRY_HOST: mmall-registry 65 | restart: always 66 | ports: 67 | - 8301:8301 68 | 69 | mmall-account: 70 | build: mmall-domain-account 71 | depends_on: 72 | mmall-configuration: 73 | condition: service_healthy 74 | environment: 75 | CONFIG_HOST: mmall-configuration 76 | REGISTRY_HOST: mmall-registry 77 | AUTH_HOST: mmall-security 78 | MYSQL_URL: jdbc:mysql://mysql/mmall?useUnicode=true&characterEncoding=utf-8 79 | REDIS_HOST: redis 80 | restart: always 81 | 82 | mmall-warehouse: 83 | build: mmall-domain-warehouse 84 | depends_on: 85 | mmall-configuration: 86 | condition: service_healthy 87 | environment: 88 | CONFIG_HOST: mmall-configuration 89 | REGISTRY_HOST: mmall-registry 90 | AUTH_HOST: mmall-security 91 | REDIS_HOST: redis 92 | MYSQL_URL: jdbc:mysql://mysql/mmall?useUnicode=true&characterEncoding=utf-8 93 | restart: always 94 | ports: 95 | - 8501:8501 96 | 97 | mmall-payment: 98 | build: mmall-domain-payment 99 | depends_on: 100 | mmall-configuration: 101 | condition: service_healthy 102 | environment: 103 | CONFIG_HOST: mmall-configuration 104 | REGISTRY_HOST: mmall-registry 105 | AUTH_HOST: mmall-security 106 | REDIS_HOST: redis 107 | MYSQL_URL: jdbc:mysql://mysql/mmall?useUnicode=true&characterEncoding=utf-8 108 | restart: always 109 | ports: 110 | - 8601:8601 111 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /img/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/img/home.png -------------------------------------------------------------------------------- /mmall-domain-account/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:12-alpine 2 | 3 | MAINTAINER kaybee 4 | 5 | ENV SPRING_OUTPUT_ANSI_ENABLE=ALWAYS \ 6 | JAVA_OPT="" \ 7 | PORT=8401 \ 8 | CONFIG_PORT=8888 \ 9 | AUTH_PORT=8301 \ 10 | PROFILES="default" 11 | 12 | ADD /build/libs/*.jar /mmall-account.jar 13 | 14 | ENTRYPOINT ["sh", "-c", "java $JAVA_OPT -jar /mmall-account.jar --spring.profiles.active=$PROFILES"] 15 | 16 | EXPOSE $PORT 17 | -------------------------------------------------------------------------------- /mmall-domain-account/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' 4 | id 'io.spring.dependency-management' 5 | } 6 | 7 | configurations { 8 | implementation { 9 | exclude group: 'com.sun.jersey', module: 'jersey-client' 10 | exclude group: 'com.sun.jersey', module: 'jersey-core' 11 | exclude group: 'com.sun.jersey.contribs', module: 'jersey-apache-client4' 12 | } 13 | } 14 | 15 | dependencies { 16 | implementation project(':mmall-lib-infrastructure') 17 | 18 | implementation 'org.springframework.boot:spring-boot-starter' 19 | implementation 'org.springframework.boot:spring-boot-starter-web' 20 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 21 | implementation 'org.springframework.cloud:spring-cloud-starter-security' 22 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' 23 | } 24 | 25 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/AccountApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | 8 | @EnableCaching 9 | @EnableDiscoveryClient 10 | @SpringBootApplication(scanBasePackages = {"com.github.kay.mmall"}) 11 | public class AccountApplication { 12 | public static void main(String[] args) { 13 | SpringApplication.run(AccountApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/application/AccountApplicationService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.application; 2 | 3 | import com.github.kay.mmall.domain.account.Account; 4 | import com.github.kay.mmall.account.domain.AccountRepository; 5 | import org.springframework.security.crypto.password.PasswordEncoder; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | @Service 10 | @Transactional 11 | public class AccountApplicationService { 12 | 13 | private final AccountRepository repository; 14 | private final PasswordEncoder encoder; 15 | 16 | public AccountApplicationService(AccountRepository repository, 17 | PasswordEncoder encoder) { 18 | this.repository = repository; 19 | this.encoder = encoder; 20 | } 21 | 22 | public void createAccount(Account account) { 23 | account.setPassword(encoder.encode(account.getPassword())); 24 | repository.save(account); 25 | } 26 | 27 | public Account findAccountByUsername(String username) { 28 | return repository.findByUsername(username); 29 | } 30 | 31 | public void updateAccount(Account account) { 32 | repository.save(account); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/application/AddressApplicationService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.application; 2 | 3 | import com.github.kay.mmall.domain.account.Address; 4 | import com.github.kay.mmall.account.domain.AddressRepository; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.Collection; 8 | 9 | @Service 10 | public class AddressApplicationService { 11 | 12 | private final AddressRepository addressRepository; 13 | 14 | public AddressApplicationService(AddressRepository addressRepository) { 15 | this.addressRepository = addressRepository; 16 | } 17 | 18 | public Collection
listAddresses(Integer userId) { 19 | return addressRepository.findAddressesByUserId(userId); 20 | } 21 | 22 | public void createAddress(Address address) { 23 | addressRepository.save(address); 24 | } 25 | 26 | public void updateAddress(Address address) { 27 | addressRepository.save(address); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/controller/AccountController.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.controller; 2 | 3 | import com.github.kay.mmall.account.application.AccountApplicationService; 4 | import com.github.kay.mmall.account.domain.validation.AuthenticatedAccount; 5 | import com.github.kay.mmall.account.domain.validation.NotConflictAccount; 6 | import com.github.kay.mmall.account.domain.validation.UniqueAccount; 7 | import com.github.kay.mmall.domain.account.Account; 8 | import com.github.kay.mmall.infrasucture.common.CodedMessage; 9 | import com.github.kay.mmall.infrasucture.common.CommonResponse; 10 | import com.github.kay.mmall.infrasucture.common.ResourceNotFoundException; 11 | 12 | import org.springframework.cache.annotation.CacheConfig; 13 | import org.springframework.cache.annotation.CacheEvict; 14 | import org.springframework.cache.annotation.Cacheable; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.security.access.prepost.PreAuthorize; 17 | import org.springframework.validation.annotation.Validated; 18 | import org.springframework.web.bind.annotation.GetMapping; 19 | import org.springframework.web.bind.annotation.PathVariable; 20 | import org.springframework.web.bind.annotation.PostMapping; 21 | import org.springframework.web.bind.annotation.PutMapping; 22 | import org.springframework.web.bind.annotation.RequestBody; 23 | import org.springframework.web.bind.annotation.RequestMapping; 24 | import org.springframework.web.bind.annotation.RestController; 25 | 26 | import java.util.Optional; 27 | 28 | @Validated 29 | @CacheConfig(cacheNames = "resource.account") 30 | @RestController 31 | @RequestMapping("/restful/accounts") 32 | public class AccountController { 33 | 34 | private final AccountApplicationService service; 35 | 36 | public AccountController(AccountApplicationService service) { 37 | this.service = service; 38 | } 39 | 40 | //Note 此方法的返回包含了 password,需要将 password 和 用户的其他信息分开存储,需要的时候各取所需 41 | @GetMapping("/{username}") 42 | @Cacheable(key = "#username") 43 | @PreAuthorize("#oauth2.hasAnyScope('SERVICE','BROWSER')") 44 | public Account getUser(@PathVariable String username) { 45 | return Optional.ofNullable(service.findAccountByUsername(username)) 46 | .orElseThrow( 47 | ResourceNotFoundException.supplier(String.format("Not found for username:%s", username))); 48 | } 49 | 50 | @PostMapping 51 | @CacheEvict(key = "#user.username") 52 | public ResponseEntity createUser(@RequestBody @UniqueAccount Account user) { 53 | return CommonResponse.op(() -> service.createAccount(user)); 54 | } 55 | 56 | @PutMapping 57 | @CacheEvict(key = "#user.username") 58 | @PreAuthorize("#oauth2.hasAnyScope('BROWSER')") 59 | public ResponseEntity updateUser( 60 | @RequestBody @AuthenticatedAccount @NotConflictAccount Account user) { 61 | return CommonResponse.op(() -> service.updateAccount(user)); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/controller/AddressController.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.controller; 2 | 3 | import com.github.kay.mmall.account.application.AddressApplicationService; 4 | import com.github.kay.mmall.domain.account.Address; 5 | import com.github.kay.mmall.domain.security.AuthenticAccount; 6 | import com.github.kay.mmall.infrasucture.common.CodedMessage; 7 | import com.github.kay.mmall.infrasucture.common.CommonResponse; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.security.core.context.SecurityContextHolder; 10 | import org.springframework.validation.annotation.Validated; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import java.util.Collection; 18 | 19 | @Validated 20 | @RestController 21 | @RequestMapping("/restful/addresses") 22 | public class AddressController { 23 | 24 | private final AddressApplicationService service; 25 | 26 | public AddressController(AddressApplicationService service) { 27 | this.service = service; 28 | } 29 | 30 | @GetMapping 31 | public Collection
listAddresses(){ 32 | AuthenticAccount account = getCurrentUser(); 33 | return service.listAddresses(account.getId()); 34 | } 35 | 36 | private AuthenticAccount getCurrentUser() { 37 | final Object principal = SecurityContextHolder.getContext() 38 | .getAuthentication() 39 | .getPrincipal(); 40 | return (AuthenticAccount) principal; 41 | } 42 | 43 | @PostMapping 44 | public ResponseEntity createAddress(@RequestBody Address address){ 45 | address.setUserId(getCurrentUser().getId()); 46 | return CommonResponse.op(() -> service.createAddress(address)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/domain/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.domain; 2 | 3 | import com.github.kay.mmall.domain.account.Account; 4 | import org.springframework.cache.annotation.CacheConfig; 5 | import org.springframework.cache.annotation.CacheEvict; 6 | import org.springframework.cache.annotation.Cacheable; 7 | import org.springframework.cache.annotation.Caching; 8 | import org.springframework.data.repository.CrudRepository; 9 | 10 | import java.util.Collection; 11 | import java.util.Optional; 12 | 13 | @CacheConfig(cacheNames = "repository.account") 14 | public interface AccountRepository extends CrudRepository { 15 | 16 | @Override 17 | Iterable findAll(); 18 | 19 | @Cacheable(key = "#username") 20 | Account findByUsername(String username); 21 | 22 | boolean existsByUsernameOrEmailOrTelephone(String username, String email, String telephone); 23 | 24 | Collection findByUsernameOrEmailOrTelephone(String username, String email, String telephone); 25 | 26 | @Cacheable(key = "#username") 27 | boolean existsByUsername(String username); 28 | 29 | @Caching(evict = { 30 | @CacheEvict(key = "#s.id"), 31 | @CacheEvict(key = "#s.username") 32 | }) 33 | @Override 34 | S save(S s); 35 | 36 | @CacheEvict(allEntries = true) 37 | @Override 38 | Iterable saveAll(Iterable iterable); 39 | 40 | @Cacheable(key = "#id") 41 | @Override 42 | Optional findById(Integer id); 43 | 44 | @Cacheable(key = "#id") 45 | @Override 46 | boolean existsById(Integer id); 47 | 48 | @CacheEvict(key = "#id") 49 | @Override 50 | void deleteById(Integer id); 51 | 52 | @CacheEvict(key = "#account.id") 53 | @Override 54 | void delete(Account account); 55 | 56 | @CacheEvict(allEntries = true) 57 | @Override 58 | void deleteAll(Iterable iterable); 59 | 60 | @CacheEvict(allEntries = true) 61 | @Override 62 | void deleteAll(); 63 | } 64 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/domain/AddressRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.domain; 2 | 3 | import com.github.kay.mmall.domain.account.Address; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.Collection; 7 | 8 | public interface AddressRepository extends CrudRepository { 9 | Collection
findAddressesByUserId(Integer userId); 10 | } 11 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/domain/validation/AccountValidator.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.domain.validation; 2 | 3 | import com.github.kay.mmall.account.domain.AccountRepository; 4 | import com.github.kay.mmall.domain.account.Account; 5 | import com.github.kay.mmall.domain.security.AuthenticAccount; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | 10 | import java.lang.annotation.Annotation; 11 | import java.util.Collection; 12 | import java.util.function.Predicate; 13 | 14 | import javax.validation.ConstraintValidator; 15 | import javax.validation.ConstraintValidatorContext; 16 | 17 | import lombok.extern.slf4j.Slf4j; 18 | 19 | @Slf4j 20 | public class AccountValidator implements ConstraintValidator { 21 | 22 | @Autowired 23 | protected AccountRepository repository; 24 | 25 | protected Predicate predicate = c -> true; 26 | 27 | @Override 28 | public boolean isValid(Account account, ConstraintValidatorContext constraintValidatorContext) { 29 | // 在JPA持久化时,默认采用Hibernate实现,插入、更新时都会调用BeanValidationEventListener进行验证 30 | // 而验证行为应该尽可能在外层进行,Resource中已经通过@Vaild注解触发过一次验证,这里会导致重复执行 31 | // 正常途径是使用分组验证避免,但@Vaild不支持分组,@Validated支持,却又是Spring的私有标签 32 | // 另一个途径是设置Hibernate配置文件中的javax.persistence.validation.mode参数为“none”,这个参数在Spring的yml中未提供桥接 33 | // 为了避免涉及到数据库操作的验证重复进行,在这里做增加此空值判断,利用Hibernate验证时验证器不是被Spring创建的特点绕开 34 | 35 | 36 | log.debug("AccountValidator:{}", this.getClass() 37 | .getSimpleName()); 38 | 39 | return repository == null || predicate.test(account); 40 | } 41 | 42 | 43 | public static class UniqueAccountValidator extends AccountValidator{ 44 | @Override 45 | public void initialize(UniqueAccount constraintAnnotation) { 46 | predicate = c -> !repository.existsByUsernameOrEmailOrTelephone(c.getUsername(), c.getEmail(), 47 | c.getTelephone()); 48 | } 49 | } 50 | 51 | public static class NotConflictAccountValidator extends AccountValidator{ 52 | @Override 53 | public void initialize(NotConflictAccount constraintAnnotation) { 54 | predicate = c -> { 55 | final Collection accounts = repository.findByUsernameOrEmailOrTelephone( 56 | c.getUsername(), c.getEmail(), c.getTelephone()); 57 | 58 | return accounts.isEmpty() || (accounts.size() == 1 && accounts.iterator() 59 | .next() 60 | .getId() 61 | .equals(c.getId())); 62 | }; 63 | } 64 | } 65 | 66 | public static class AuthenticatedAccountValidator extends AccountValidator{ 67 | @Override 68 | public void initialize(AuthenticatedAccount constraintAnnotation) { 69 | predicate = c -> { 70 | final Object principal = SecurityContextHolder.getContext() 71 | .getAuthentication() 72 | .getPrincipal(); 73 | 74 | if ("anonymousUser".equals(principal)) { 75 | return false; 76 | }else { 77 | AuthenticAccount account = (AuthenticAccount) principal; 78 | return c.getId() 79 | .equals(account.getId()); 80 | } 81 | }; 82 | } 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/domain/validation/AuthenticatedAccount.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.domain.validation; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.FIELD; 10 | import static java.lang.annotation.ElementType.METHOD; 11 | import static java.lang.annotation.ElementType.PARAMETER; 12 | import static java.lang.annotation.ElementType.TYPE; 13 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 14 | 15 | @Documented 16 | @Retention(RUNTIME) 17 | @Target({FIELD, METHOD, PARAMETER, TYPE}) 18 | @Constraint(validatedBy = AccountValidator.AuthenticatedAccountValidator.class) 19 | public @interface AuthenticatedAccount { 20 | String message() default "不是当前登录用户"; 21 | 22 | Class[] groups() default {}; 23 | 24 | Class[] payload() default {}; 25 | } 26 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/domain/validation/NotConflictAccount.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.domain.validation; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | import static java.lang.annotation.ElementType.FIELD; 10 | import static java.lang.annotation.ElementType.METHOD; 11 | import static java.lang.annotation.ElementType.PARAMETER; 12 | import static java.lang.annotation.ElementType.TYPE; 13 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 14 | 15 | @Documented 16 | @Retention(RUNTIME) 17 | @Target({FIELD, METHOD, PARAMETER, TYPE}) 18 | @Constraint(validatedBy = AccountValidator.NotConflictAccountValidator.class) 19 | public @interface NotConflictAccount { 20 | String message() default "用户名称、邮箱、手机号码与现存用户产生重复"; 21 | 22 | Class[] groups() default {}; 23 | 24 | Class[] payload() default {}; 25 | } 26 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/java/com/github/kay/mmall/account/domain/validation/UniqueAccount.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.account.domain.validation; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Documented 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE}) 14 | @Constraint(validatedBy = AccountValidator.UniqueAccountValidator.class) 15 | public @interface UniqueAccount { 16 | String message() default "用户名、邮箱、手机号均不能与现有用户重复"; 17 | 18 | Class[] groups() default {}; 19 | 20 | Class[] payload() default {}; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | enabled: false 5 | cache: 6 | type: redis 7 | redis: 8 | cache-null-values: false 9 | time-to-live: PT5M 10 | datasource: 11 | schema: "classpath:db/schema.sql" 12 | data: "classpath:db/data.sql" 13 | sql-script-encoding: UTF-8 14 | url: ${MYSQL_URL:jdbc:mysql://localhost/mmall?useUnicode=true&characterEncoding=utf-8} 15 | username: "root" 16 | password: "root1234" 17 | initialization-mode: always 18 | jpa: 19 | hibernate: 20 | ddl-auto: none 21 | open-in-view: true 22 | resources: 23 | chain: 24 | compressed: true 25 | cache: true 26 | cache: 27 | period: 86400 28 | redis: 29 | host: ${redis.host:localhost} 30 | port: ${redis.tcp.6379:6379} 31 | 32 | logging: 33 | pattern: 34 | console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}" 35 | level: 36 | root: INFO 37 | com.github.kay: DEBUG 38 | 39 | mmall: 40 | redisson-config: "redisson-config.yml" 41 | 42 | eureka: 43 | client: 44 | enabled: false 45 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: account 4 | cloud: 5 | config: 6 | uri: http://${CONFIG_HOST:localhost}:${CONFIG_PORT:8888} 7 | fail-fast: true 8 | password: ${CONFIG_PASS:dev} 9 | username: user 10 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/resources/db/data.sql: -------------------------------------------------------------------------------- 1 | 2 | INSERT INTO account 3 | VALUES (1, 'kaybee', '$2a$10$SvyLUrNMbwPAnPBvDBbItOtLnmg88IbKFnBQy4zjaRNCCi0p5OCya', 'kaybee', '', '18888888888', 4 | 'kaybee@gmail.com', '北京市'); 5 | 6 | INSERT INTO mmall.address (id, city, details, district, phone, province, receiver_name, user_id, zip) VALUES (1, '北京市', 'XXXXXX', null, '18888888887', '北京市', 'kay', 1, '10000'); -------------------------------------------------------------------------------- /mmall-domain-account/src/main/resources/db/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS address; 2 | DROP TABLE IF EXISTS account; 3 | 4 | CREATE TABLE IF NOT EXISTS account 5 | ( 6 | id int unsigned not null auto_increment primary key, 7 | username varchar(50), 8 | password varchar(100), 9 | name varchar(50), 10 | avatar varchar(100), 11 | telephone varchar(20), 12 | email varchar(100), 13 | location varchar(100) 14 | ) engine = InnoDB; 15 | 16 | create table address 17 | ( 18 | id int unsigned not null auto_increment primary key, 19 | city varchar(50) not null, 20 | details varchar(255) null, 21 | district varchar(255) null, 22 | phone varchar(255) not null, 23 | province varchar(50) not null, 24 | receiver_name varchar(50) null, 25 | user_id int not null, 26 | zip varchar(255) null 27 | ) engine = InnoDB; 28 | 29 | create index idx_username on account (username); -------------------------------------------------------------------------------- /mmall-domain-account/src/main/resources/public.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhB6SdL11wM6lkf3NbZIq AUZbwIHd1Aca/k3qtk2T58ABD6kwPJd9W1Vd6t6t3Db/C3EBJBJF8ZAWKgv0+fbI ZRrSsHCe5syAVfNUO34bF+pEuQKIIsk1gR85pCSNfRdUpDbNJmBjqrr6lLBbFdtc fh3eqaQOITqbxHPEm33D/YnKQHw2jDdPLWVeqj6uvy92UK+jRpGRzOwC/sVCfSwp QfZ51DiQx7nSfcS54SMzXklvxyKmex1D/Y61rb6ci13yFtIyz59SyY/p3BFQypIi QX6VqzCY48qquWA+Kh+HiO3WaZL0kjxwC06fgSOFBff9rngnap64XF/wlXFR3Bef ZQIDAQAB -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /mmall-domain-account/src/main/resources/redisson-config.yml: -------------------------------------------------------------------------------- 1 | singleServerConfig: 2 | address: "redis://${redis.host:-localhost}:${redis.tcp.6379:-6379}" 3 | connectionPoolSize: 24 4 | connectionMinimumIdleSize: 12 5 | keepAlive: true 6 | 7 | -------------------------------------------------------------------------------- /mmall-domain-account/src/main/resources/rsa.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-domain-account/src/main/resources/rsa.jks -------------------------------------------------------------------------------- /mmall-domain-payment/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:12-alpine 2 | 3 | MAINTAINER kaybee 4 | 5 | ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ 6 | JAVA_OPTS="" \ 7 | PORT=8601 \ 8 | CONFIG_PORT=8888 \ 9 | AUTH_PORT=8301 \ 10 | PROFILES="default" 11 | 12 | ADD /build/libs/*.jar /mmall-payment.jar 13 | 14 | ENTRYPOINT ["sh", "-c", "java $JAVA_OPT -jar /mmall-payment.jar --spring.profiles.active=$PROFILES"] 15 | 16 | EXPOSE $PORT -------------------------------------------------------------------------------- /mmall-domain-payment/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' 4 | id 'io.spring.dependency-management' 5 | } 6 | 7 | configurations { 8 | implementation { 9 | exclude group: 'com.sun.jersey', module: 'jersey-client' 10 | exclude group: 'com.sun.jersey', module: 'jersey-core' 11 | exclude group: 'com.sun.jersey.contribs', module: 'jersey-apache-client4' 12 | } 13 | } 14 | 15 | dependencies { 16 | implementation project(':mmall-lib-infrastructure') 17 | 18 | implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client') 19 | implementation 'org.springframework.boot:spring-boot-starter' 20 | implementation 'org.springframework.boot:spring-boot-starter-web' 21 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 22 | implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' 23 | implementation 'org.springframework.boot:spring-boot-starter-data-redis' 24 | implementation 'org.springframework.cloud:spring-cloud-starter-security' 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/PaymentApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | @EnableDiscoveryClient 8 | @SpringBootApplication(scanBasePackages = {"com.github.kay.mmall"}) 9 | public class PaymentApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(PaymentApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/controller/PaymentController.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.controller; 2 | 3 | import com.github.kay.mmall.domain.account.Account; 4 | import com.github.kay.mmall.domain.security.Role; 5 | import com.github.kay.mmall.infrasucture.common.CodedMessage; 6 | import com.github.kay.mmall.infrasucture.common.CommonResponse; 7 | import com.github.kay.mmall.payment.domain.Payment; 8 | import com.github.kay.mmall.payment.domain.application.PaymentApplicationService; 9 | 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.security.access.annotation.Secured; 12 | import org.springframework.security.core.context.SecurityContextHolder; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PatchMapping; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestParam; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | @RestController 21 | @RequestMapping("/restful/pay") 22 | public class PaymentController { 23 | 24 | private final PaymentApplicationService service; 25 | 26 | public PaymentController(PaymentApplicationService service) { 27 | this.service = service; 28 | } 29 | 30 | /** 31 | * 修改支付单据的状态 32 | */ 33 | @PatchMapping("/{payId}") 34 | @Secured(Role.USER) 35 | public ResponseEntity updatePaymentState(@PathVariable("payId") String payId, @RequestParam("state") Payment.State state) { 36 | Account account = (Account) SecurityContextHolder.getContext() 37 | .getAuthentication() 38 | .getPrincipal(); 39 | return updatePaymentStateAlias(payId, account.getId(), state); 40 | } 41 | 42 | /** 43 | * 修改支付单状态的GET方法别名 44 | * 考虑到该动作要由二维码扫描来触发,只能进行GET请求,所以增加一个别名以便通过二维码调用 45 | * 这个方法原本应该作为银行支付接口的回调,不控制调用权限(谁付款都行),但都认为是购买用户付的款 46 | */ 47 | @GetMapping("/modify/{payId}") 48 | public ResponseEntity updatePaymentStateAlias(@PathVariable("payId") String payId, 49 | @RequestParam("accountId") Integer accountId, 50 | @RequestParam("state") Payment.State state) { 51 | if (state == Payment.State.PAYED) { 52 | return CommonResponse.op(() -> service.accomplishPayment(accountId, payId)); 53 | } else { 54 | return CommonResponse.op(() -> service.cancelPayment(payId)); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/controller/SettlementController.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.controller; 2 | 3 | import com.github.kay.mmall.payment.domain.application.PaymentApplicationService; 4 | import com.github.kay.mmall.domain.security.Role; 5 | import com.github.kay.mmall.payment.domain.Payment; 6 | import com.github.kay.mmall.payment.domain.validation.ValidStock; 7 | import com.github.kay.mmall.dto.Settlement; 8 | import org.springframework.security.access.annotation.Secured; 9 | import org.springframework.validation.annotation.Validated; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | @Validated 16 | @RestController 17 | @RequestMapping("/restful/settlements") 18 | public class SettlementController { 19 | 20 | private final PaymentApplicationService service; 21 | 22 | public SettlementController(PaymentApplicationService service) { 23 | this.service = service; 24 | } 25 | 26 | @PostMapping 27 | @Secured(value = Role.USER) 28 | public Payment createPayment(@RequestBody @ValidStock Settlement settlement) { 29 | return service.executeBySettlement(settlement); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/domain/Payment.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.github.kay.mmall.domain.BaseEntity; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.PrePersist; 10 | import javax.persistence.PreUpdate; 11 | import javax.validation.constraints.NotNull; 12 | import java.math.BigDecimal; 13 | import java.util.Date; 14 | import java.util.UUID; 15 | 16 | /** 17 | * 支付对象 18 | */ 19 | @Setter 20 | @Getter 21 | @Entity 22 | public class Payment extends BaseEntity { 23 | 24 | @JsonIgnore 25 | public static final transient String PAY_ID_PREFIX = "pay:"; 26 | 27 | private Date createTime; 28 | 29 | private Date updateTime; 30 | 31 | private String payId; 32 | 33 | private BigDecimal totalPrice; 34 | 35 | private Long expires; // ms 36 | 37 | private String paymentLink; 38 | 39 | private State payState; 40 | 41 | public Payment() { 42 | } 43 | 44 | public Payment(BigDecimal totalPrice, Long expires,@NotNull Integer accountId) { 45 | setTotalPrice(totalPrice); 46 | setExpires(expires); 47 | setCreateTime(new Date()); 48 | setPayState(State.WAITING); 49 | 50 | // 下面这两个是随便写的,实际应该根据情况调用支付服务,返回待支付的ID 51 | setPayId(PAY_ID_PREFIX + UUID.randomUUID().toString()); 52 | // 产生支付单的时候一定是有用户的 53 | setPaymentLink("/pay/modify/" + getPayId() + "?state=PAYED&accountId=" + accountId); 54 | } 55 | 56 | @PreUpdate 57 | @PrePersist 58 | public void updateTime(){ 59 | this.updateTime = new Date(); 60 | } 61 | 62 | public enum State { 63 | /** 64 | * 等待支付中 65 | */ 66 | WAITING, 67 | /** 68 | * 已取消 69 | */ 70 | CANCEL, 71 | /** 72 | * 已支付 73 | */ 74 | PAYED, 75 | /** 76 | * 已超时回滚(未支付,并且商品已恢复) 77 | */ 78 | TIMEOUT 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/domain/Wallet.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.domain; 2 | 3 | import com.github.kay.mmall.domain.BaseEntity; 4 | import com.github.kay.mmall.domain.account.Account; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.FetchType; 10 | import javax.persistence.JoinColumn; 11 | import javax.persistence.OneToOne; 12 | import java.math.BigDecimal; 13 | 14 | @Getter 15 | @Setter 16 | @Entity 17 | public class Wallet extends BaseEntity { 18 | 19 | private BigDecimal money; 20 | 21 | @OneToOne(fetch = FetchType.LAZY) 22 | @JoinColumn(name = "account_id") 23 | private Account account; 24 | } 25 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/domain/application/PaymentApplicationService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.domain.application; 2 | 3 | import com.github.kay.mmall.payment.domain.Payment; 4 | import com.github.kay.mmall.payment.domain.client.ProductServiceClient; 5 | import com.github.kay.mmall.payment.domain.service.PaymentService; 6 | import com.github.kay.mmall.payment.domain.service.WalletService; 7 | 8 | import com.github.kay.mmall.dto.Settlement; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.math.BigDecimal; 14 | 15 | @Service 16 | @Transactional 17 | public class PaymentApplicationService { 18 | 19 | private final PaymentService paymentService; 20 | private final ProductServiceClient productService; 21 | private final WalletService walletService; 22 | private final RedisTemplate redisTemplate; 23 | 24 | public PaymentApplicationService(PaymentService paymentService, 25 | ProductServiceClient productService, 26 | WalletService walletService, 27 | RedisTemplate redisTemplate) { 28 | this.paymentService = paymentService; 29 | this.productService = productService; 30 | this.walletService = walletService; 31 | this.redisTemplate = redisTemplate; 32 | } 33 | 34 | /** 35 | * 根据结算清单的内容执行,生成对应的支付单 36 | */ 37 | public Payment executeBySettlement(Settlement bill) { 38 | // 从服务中获取商品的价格,计算要支付的总价(安全原因,这个不能由客户端传上来) 39 | productService.replenishProductInformation(bill); 40 | // 冻结部分库存(保证有货提供),生成付款单 41 | Payment payment = paymentService.producePayment(bill); 42 | // 设立解冻定时器(超时未支付则释放冻结的库存和资金) 43 | paymentService.setupAutoThawedTrigger(payment); 44 | return payment; 45 | } 46 | 47 | /** 48 | * 完成支付 49 | * 立即取消解冻定时器,执行扣减库存和资金 50 | */ 51 | public void accomplishPayment(Integer accountId, String payId) { 52 | // 订单从冻结状态变为派送状态,扣减库存 53 | BigDecimal price = paymentService.finish(payId); 54 | // 扣减货款 55 | walletService.decrease(accountId, price); 56 | // 支付成功的清除缓存 57 | redisTemplate.delete(payId); 58 | 59 | } 60 | 61 | 62 | /** 63 | * 取消支付 64 | * 立即触发解冻定时器,释放库存和资金 65 | */ 66 | public void cancelPayment(String payId) { 67 | // 释放冻结的库存 68 | paymentService.cancel(payId); 69 | // 支付成功的清除缓存 70 | redisTemplate.delete(payId); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/domain/client/ProductServiceClient.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.domain.client; 2 | 3 | import com.github.kay.mmall.domain.product.DeliveredStatus; 4 | import com.github.kay.mmall.domain.product.Product; 5 | import com.github.kay.mmall.domain.product.Stockpile; 6 | import com.github.kay.mmall.dto.Settlement; 7 | 8 | import org.springframework.cloud.openfeign.FeignClient; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PatchMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | 14 | import java.util.function.Function; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.Stream; 17 | 18 | 19 | @FeignClient(name = "warehouse") 20 | public interface ProductServiceClient { 21 | 22 | default void replenishProductInformation(Settlement bill) { 23 | bill.productMap = Stream.of(getProducts()).collect(Collectors.toMap(Product::getId, Function.identity())); 24 | } 25 | 26 | @GetMapping("/restful/products/{id}") 27 | Product getProduct(@PathVariable("id") Integer id); 28 | 29 | @GetMapping("/restful/products") 30 | Product[] getProducts(); 31 | 32 | default void decrease(Integer productId, Integer amount) { 33 | setDeliveredStatus(productId, DeliveredStatus.DECREASE, amount); 34 | } 35 | 36 | default void increase(Integer productId, Integer amount) { 37 | setDeliveredStatus(productId, DeliveredStatus.INCREASE, amount); 38 | } 39 | 40 | default void frozen(Integer productId, Integer amount) { 41 | setDeliveredStatus(productId, DeliveredStatus.FROZEN, amount); 42 | } 43 | 44 | default void thawed(Integer productId, Integer amount) { 45 | setDeliveredStatus(productId, DeliveredStatus.THAWED, amount); 46 | } 47 | 48 | @PatchMapping("/restful/products/stockpile/delivered/{productId}") 49 | void setDeliveredStatus(@PathVariable("productId") Integer productId, 50 | @RequestParam("status") DeliveredStatus status, 51 | @RequestParam("amount") Integer amount); 52 | 53 | @GetMapping("/restful/products/stockpile/{productId}") 54 | Stockpile queryStockpile(@PathVariable("productId") Integer productId); 55 | } 56 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/domain/repo/PaymentRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.domain.repo; 2 | 3 | import com.github.kay.mmall.payment.domain.Payment; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface PaymentRepository extends CrudRepository { 7 | 8 | Payment findByPayId(String payId); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/domain/repo/WalletRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.domain.repo; 2 | 3 | import com.github.kay.mmall.payment.domain.Wallet; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface WalletRepository extends CrudRepository { 9 | 10 | Optional findByAccountId(Integer accountId); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/domain/service/WalletService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.domain.service; 2 | 3 | import com.github.kay.mmall.domain.account.Account; 4 | import com.github.kay.mmall.payment.domain.Wallet; 5 | import com.github.kay.mmall.payment.domain.repo.WalletRepository; 6 | 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.math.BigDecimal; 10 | 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | @Slf4j 14 | @Service 15 | public class WalletService { 16 | 17 | private final WalletRepository repository; 18 | 19 | public WalletService(WalletRepository repository) { 20 | this.repository = repository; 21 | } 22 | 23 | public void decrease(Integer accountId, BigDecimal amount) { 24 | final Wallet wallet = repository.findByAccountId(accountId) 25 | .orElseGet(() -> { 26 | Wallet w = new Wallet(); 27 | Account account = new Account(); 28 | account.setId(accountId); 29 | w.setAccount(account); 30 | w.setMoney(BigDecimal.ZERO); 31 | repository.save(w); 32 | return w; 33 | }); 34 | 35 | if (wallet.getMoney().compareTo(amount) > 0) { 36 | wallet.setMoney(wallet.getMoney().subtract(amount)); 37 | repository.save(wallet); 38 | log.info("支付成功。用户余额:{},本次消费:{}", wallet.getMoney(), amount); 39 | }else { 40 | throw new IllegalStateException("用户余额不足以支付,请先充值"); 41 | } 42 | } 43 | 44 | /** 45 | * 账户资金增加(演示程序,没有做充值入口,实际这个方法无用) 46 | */ 47 | public void increase(Integer accountId, Double amount) { 48 | //todo 49 | } 50 | 51 | // 以下两个方法是为TCC事务准备的,在单体架构中不需要实现 52 | 53 | /** 54 | * 账户资金冻结 55 | * 从正常资金中移动指定数量至冻结状态 56 | */ 57 | public void frozen(Integer accountId, Double amount) { 58 | //todo 59 | } 60 | 61 | /** 62 | * 账户资金解冻 63 | * 从冻结资金中移动指定数量至正常状态 64 | */ 65 | public void thawed(Integer accountId, Double amount) { 66 | //todo 67 | } 68 | 69 | } 70 | 71 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/domain/validation/SettlementValidator.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.domain.validation; 2 | 3 | import com.github.kay.mmall.dto.Settlement; 4 | import com.github.kay.mmall.payment.domain.client.ProductServiceClient; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | 8 | import javax.validation.ConstraintValidator; 9 | import javax.validation.ConstraintValidatorContext; 10 | 11 | /** 12 | * 验证库存是否足够 13 | */ 14 | public class SettlementValidator implements ConstraintValidator { 15 | 16 | @Autowired 17 | private ProductServiceClient stockpileService; 18 | 19 | @Override 20 | public boolean isValid(Settlement settlement, ConstraintValidatorContext constraintValidatorContext) { 21 | return settlement.getItems() 22 | .stream() 23 | .noneMatch(i -> stockpileService.queryStockpile(i.getProductId()) 24 | .getAmount() < i.getAmount()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/java/com/github/kay/mmall/payment/domain/validation/ValidStock.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.payment.domain.validation; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Documented 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) 14 | @Constraint(validatedBy = SettlementValidator.class) 15 | public @interface ValidStock { 16 | 17 | String message() default "商品库存不足"; 18 | 19 | Class[] groups() default {}; 20 | 21 | Class[] payload() default {}; 22 | } 23 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | spring: 4 | cache: 5 | type: redis 6 | redis: 7 | cache-null-values: false 8 | time-to-live: PT5M 9 | datasource: 10 | schema: "classpath:db/schema.sql" 11 | data: "classpath:db/data.sql" 12 | sql-script-encoding: UTF-8 13 | url: "jdbc:mysql://localhost:3306/mmall?useUnicode=true&characterEncoding=utf-8" 14 | username: "root" 15 | password: "root1234" 16 | initialization-mode: always 17 | jpa: 18 | show-sql: true 19 | hibernate: 20 | ddl-auto: none 21 | open-in-view: true 22 | generate-ddl: false 23 | database: mysql 24 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 25 | resources: 26 | chain: 27 | compressed: true 28 | cache: true 29 | cache: 30 | period: 86400 31 | redis: 32 | host: ${redis.host:localhost} 33 | port: ${redis.tcp.6379:6379} 34 | 35 | logging: 36 | pattern: 37 | console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}" 38 | level: 39 | root: INFO 40 | com.github.kay: DEBUG 41 | 42 | mmall: 43 | redisson-config: "redisson-config.yml" 44 | 45 | 46 | eureka: 47 | client: 48 | enabled: false 49 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: payment 4 | cloud: 5 | config: 6 | uri: http://${CONFIG_HOST:localhost}:${CONFIG_PORT:8888} 7 | fail-fast: true 8 | password: ${CONFIG_PASS:dev} 9 | username: user 10 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/resources/db/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO mmall.wallet VALUES (1, 10000, 1); 2 | 3 | 4 | -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/resources/db/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS mmall; 2 | ALTER DATABASE mmall 3 | DEFAULT CHARACTER SET utf8 4 | DEFAULT COLLATE utf8_general_ci; 5 | GRANT ALL PRIVILEGES ON mmall.* TO 'mmall@%' IDENTIFIED BY 'mmall'; 6 | 7 | DROP TABLE IF EXISTS wallet; 8 | DROP TABLE IF EXISTS payment; 9 | 10 | create table payment 11 | ( 12 | id int unsigned not null auto_increment primary key, 13 | create_time datetime default current_timestamp, 14 | update_time datetime default current_timestamp, 15 | expires bigint null, 16 | pay_id varchar(255) not null, 17 | pay_state int null, 18 | payment_link varchar(255) null, 19 | total_price decimal(19, 2) null 20 | ) engine = InnoDB; 21 | 22 | create table wallet 23 | ( 24 | id int unsigned not null auto_increment primary key, 25 | money decimal(19, 2) null, 26 | account_id int unsigned not null 27 | ) engine = InnoDB; 28 | 29 | create index idx_account_id on wallet (account_id); -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/resources/public.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhB6SdL11wM6lkf3NbZIq AUZbwIHd1Aca/k3qtk2T58ABD6kwPJd9W1Vd6t6t3Db/C3EBJBJF8ZAWKgv0+fbI ZRrSsHCe5syAVfNUO34bF+pEuQKIIsk1gR85pCSNfRdUpDbNJmBjqrr6lLBbFdtc fh3eqaQOITqbxHPEm33D/YnKQHw2jDdPLWVeqj6uvy92UK+jRpGRzOwC/sVCfSwp QfZ51DiQx7nSfcS54SMzXklvxyKmex1D/Y61rb6ci13yFtIyz59SyY/p3BFQypIi QX6VqzCY48qquWA+Kh+HiO3WaZL0kjxwC06fgSOFBff9rngnap64XF/wlXFR3Bef ZQIDAQAB -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /mmall-domain-payment/src/main/resources/redisson-config.yml: -------------------------------------------------------------------------------- 1 | singleServerConfig: 2 | address: "redis://${redis.host:-localhost}:${redis.tcp.6379:-6379}" 3 | connectionPoolSize: 24 4 | connectionMinimumIdleSize: 12 5 | keepAlive: true 6 | 7 | -------------------------------------------------------------------------------- /mmall-domain-security/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:12-alpine 2 | 3 | MAINTAINER kaybee 4 | 5 | ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ 6 | JAVA_OPTS="" \ 7 | PORT=8301 \ 8 | CONFIG_PORT=8888 \ 9 | PROFILES="default" 10 | 11 | ADD /build/libs/*.jar /mmall-security.jar 12 | 13 | ENTRYPOINT ["sh", "-c", "java $JAVA_OPT -jar /mmall-security.jar --spring.profiles.active=$PROFILES"] 14 | 15 | EXPOSE $PORT -------------------------------------------------------------------------------- /mmall-domain-security/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'idea' 4 | id 'org.springframework.boot' 5 | id 'io.spring.dependency-management' 6 | } 7 | 8 | 9 | idea { 10 | module { 11 | downloadJavadoc = true 12 | downloadSources = true 13 | } 14 | } 15 | 16 | configurations { 17 | implementation { 18 | exclude group: 'com.sun.jersey', module: 'jersey-client' 19 | exclude group: 'com.sun.jersey', module: 'jersey-core' 20 | exclude group: 'com.sun.jersey.contribs', module: 'jersey-apache-client4' 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation project(':mmall-lib-infrastructure') 26 | 27 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' 28 | implementation 'org.springframework.security.oauth:spring-security-oauth2:2.3.5.RELEASE' 29 | implementation 'org.springframework.security:spring-security-jwt:1.0.10.RELEASE' 30 | implementation 'org.springframework.boot:spring-boot-starter-web' 31 | implementation 'org.springframework.boot:spring-boot-starter-security' 32 | } 33 | 34 | -------------------------------------------------------------------------------- /mmall-domain-security/src/main/java/com/github/kay/mmall/security/SecurityApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.security; 2 | 3 | 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 7 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 8 | 9 | @EnableDiscoveryClient 10 | @SpringBootApplication(scanBasePackages = {"com.github.kay.mmall"}, 11 | exclude = {DataSourceAutoConfiguration.class}) 12 | public class SecurityApplication { 13 | public static void main(String[] args) { 14 | SpringApplication.run(SecurityApplication.class, args); 15 | } 16 | } -------------------------------------------------------------------------------- /mmall-domain-security/src/main/java/com/github/kay/mmall/security/configuration/AuthenticationServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.security.configuration; 2 | 3 | import com.github.kay.mmall.security.provider.PreAuthenticatedAuthenticationProvider; 4 | import com.github.kay.mmall.security.provider.UsernamePasswordAuthenticationProvider; 5 | import com.github.kay.mmall.domain.security.AuthenticAccountDetailsService; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | 13 | /** 14 | * 用户认证服务配置 15 | */ 16 | @Configuration 17 | @EnableWebSecurity 18 | public class AuthenticationServerConfiguration extends WebSecurityConfiguration{ 19 | 20 | private final AuthenticAccountDetailsService authenticAccountDetailsService; 21 | private final UsernamePasswordAuthenticationProvider userProvider; 22 | private final PreAuthenticatedAuthenticationProvider preProvider; 23 | private final PasswordEncoder encoder; 24 | 25 | public AuthenticationServerConfiguration( 26 | AuthenticAccountDetailsService authenticAccountDetailsService, 27 | UsernamePasswordAuthenticationProvider userProvider, 28 | PreAuthenticatedAuthenticationProvider preProvider, 29 | PasswordEncoder encoder) { 30 | this.authenticAccountDetailsService = authenticAccountDetailsService; 31 | this.userProvider = userProvider; 32 | this.preProvider = preProvider; 33 | this.encoder = encoder; 34 | } 35 | 36 | @Bean 37 | @Override 38 | public AuthenticationManager authenticationManagerBean() throws Exception { 39 | return super.authenticationManagerBean(); 40 | } 41 | 42 | @Override 43 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 44 | auth.userDetailsService(authenticAccountDetailsService) 45 | .passwordEncoder(encoder); 46 | auth.authenticationProvider(userProvider); 47 | auth.authenticationProvider(preProvider); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mmall-domain-security/src/main/java/com/github/kay/mmall/security/configuration/AuthorizationServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.security.configuration; 2 | 3 | import com.github.kay.mmall.domain.security.AuthenticAccountDetailsService; 4 | import com.github.kay.mmall.infrasucture.security.JWTAccessTokenService; 5 | import com.github.kay.mmall.infrasucture.security.OAuthClientDetailsService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.http.HttpMethod; 9 | import org.springframework.security.authentication.AuthenticationManager; 10 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 11 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 12 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 13 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 14 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 15 | 16 | /** 17 | * Spring Security OAuth2 授权服务器配置 18 | */ 19 | @Configuration 20 | @EnableAuthorizationServer 21 | public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { 22 | 23 | /** 24 | * 令牌服务 25 | */ 26 | @Autowired 27 | private JWTAccessTokenService tokenService; 28 | 29 | /** 30 | * OAuth2客户端信息服务 31 | */ 32 | @Autowired 33 | private OAuthClientDetailsService clientService; 34 | 35 | /** 36 | * 认证服务管理器 37 | *

38 | * 一个认证服务管理器里面包含着多个可以从事不同认证类型的认证提供者(Provider) 39 | * 认证服务由认证服务器{@link AuthenticationServerConfiguration}定义并提供注入源 40 | */ 41 | @Autowired 42 | private AuthenticationManager authenticationManager; 43 | 44 | /** 45 | * 用户信息服务 46 | */ 47 | @Autowired 48 | private AuthenticAccountDetailsService accountService; 49 | 50 | @Override 51 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 52 | clients.withClientDetails(clientService); 53 | } 54 | 55 | /** 56 | * 配置授权的服务Endpoint 57 | *

58 | * Spring Security OAuth2会根据配置的认证服务、用户详情服务、令牌服务自动生成以下端点: 59 | * /oauth/authorize:授权端点 60 | * /oauth/token:令牌端点 61 | * /oauth/confirm_access:用户确认授权提交端点 62 | * /oauth/error:授权服务错误信息端点 63 | * /oauth/check_token:用于资源服务访问的令牌解析端点 64 | * /oauth/token_key:提供公有密匙的端点,如果JWT采用的是非对称加密加密算法,则资源服务其在鉴权时就需要这个公钥来解码 65 | * 如有必要,这些端点可以使用pathMapping()方法来修改它们的位置,使用prefix()方法来设置路径前缀 66 | */ 67 | @Override 68 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 69 | endpoints.authenticationManager(authenticationManager) 70 | .userDetailsService(accountService) 71 | .tokenServices(tokenService) 72 | .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); 73 | } 74 | 75 | /** 76 | * 配置OAuth2发布出来的Endpoint本身的安全约束 77 | *

78 | * 这些端点的默认访问规则原本是: 79 | * 1. 端点开启了HTTP Basic Authentication,通过allowFormAuthenticationForClients()关闭,即允许通过表单来验证 80 | * 2. 端点的访问均为denyAll(),可以在这里通过SpringEL表达式来改变为permitAll() 81 | */ 82 | @Override 83 | public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 84 | security.allowFormAuthenticationForClients() 85 | .tokenKeyAccess("permitAll()") 86 | .checkTokenAccess("permitAll()"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /mmall-domain-security/src/main/java/com/github/kay/mmall/security/configuration/WebSecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.security.configuration; 2 | 3 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 4 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 6 | 7 | public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { 8 | 9 | @Override 10 | protected void configure(HttpSecurity http) throws Exception { 11 | http.headers() 12 | .cacheControl() 13 | .disable(); 14 | } 15 | 16 | @Override 17 | public void configure(WebSecurity web) throws Exception { 18 | web.ignoring() 19 | .antMatchers("/static/**"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mmall-domain-security/src/main/java/com/github/kay/mmall/security/provider/PreAuthenticatedAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.security.provider; 2 | 3 | import com.github.kay.mmall.domain.security.AuthenticAccount; 4 | import org.springframework.security.authentication.AuthenticationProvider; 5 | import org.springframework.security.authentication.DisabledException; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.AuthenticationException; 9 | import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; 10 | import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException; 11 | import org.springframework.stereotype.Component; 12 | 13 | /** 14 | * 预验证身份认证器 15 | *

16 | * 预验证是指身份已经在其他地方(第三方)确认过 17 | * 预验证器的目的是将第三方身份管理系统集成到具有Spring安全性的Spring应用程序中,在本项目中,用于JWT令牌过期后重刷新时的验证 18 | * 此时只要检查用户是否有停用、锁定、密码过期、账号过期等问题,如果没有,可根据JWT令牌的刷新过期期限,重新给客户端发放访问令牌 19 | */ 20 | 21 | @Component 22 | public class PreAuthenticatedAuthenticationProvider implements AuthenticationProvider { 23 | 24 | @Override 25 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 26 | if (authentication.getPrincipal() instanceof UsernamePasswordAuthenticationToken) { 27 | UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication.getPrincipal(); 28 | AuthenticAccount account = (AuthenticAccount) token.getPrincipal(); 29 | 30 | if (account.isEnabled() && account.isCredentialsNonExpired() && account.isAccountNonExpired() && account.isAccountNonLocked()) { 31 | return new PreAuthenticatedAuthenticationToken(account, "", account.getAuthorities()); 32 | }else { 33 | throw new DisabledException("Account status is illegal."); 34 | } 35 | }else { 36 | throw new PreAuthenticatedCredentialsNotFoundException("Pre-Authentication failed, token is invalid."); 37 | } 38 | } 39 | 40 | @Override 41 | public boolean supports(Class aClass) { 42 | return aClass.equals(PreAuthenticatedAuthenticationToken.class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mmall-domain-security/src/main/java/com/github/kay/mmall/security/provider/RSA256JWTAccessToken.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.security.provider; 2 | 3 | import com.github.kay.mmall.infrasucture.security.JWTAccessToken; 4 | 5 | import org.springframework.context.annotation.Primary; 6 | import org.springframework.core.io.ClassPathResource; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.jwt.JwtHelper; 9 | import org.springframework.security.jwt.crypto.sign.Signer; 10 | import org.springframework.security.oauth2.common.OAuth2AccessToken; 11 | import org.springframework.security.oauth2.common.util.JsonParser; 12 | import org.springframework.security.oauth2.common.util.JsonParserFactory; 13 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 14 | import org.springframework.security.rsa.crypto.KeyStoreKeyFactory; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.util.ReflectionUtils; 17 | 18 | import java.lang.reflect.Field; 19 | import java.util.Collections; 20 | import java.util.Map; 21 | 22 | /** 23 | * 使用RSA SHA256私钥加密的JWT令牌 24 | *

25 | * 为了便于对比, 26 | * 27 | * 生成jks私钥公钥等 28 | * keytool -genkeypair -alias mmall-rsa -keyalg RSA -keypass 123456 -keystore rsa.jks -storepass 123456 -validity 36500 29 | * keytool -list -rfc --keystore rsa.jks | openssl x509 -inform pem -pubkey > pubkey.txt 30 | * 31 | **/ 32 | @Service 33 | @Primary 34 | public class RSA256JWTAccessToken extends JWTAccessToken { 35 | 36 | private final JsonParser objectMapper = JsonParserFactory.create(); 37 | 38 | RSA256JWTAccessToken(UserDetailsService userDetailsService) { 39 | super(userDetailsService); 40 | KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("rsa.jks"), "123456".toCharArray()); 41 | setKeyPair(keyStoreKeyFactory.getKeyPair("mmall-rsa")); 42 | } 43 | 44 | /** 45 | * 增强令牌Header 46 | * 使用JWKS验证令牌时,Header中需要有kid(Key ID),设置Header的方法在Spring的默认实现中没有开放出来,这里添加个默认处理 47 | */ 48 | @Override 49 | protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { 50 | Field signerField = ReflectionUtils.findField(this.getClass(), "signer"); 51 | signerField.setAccessible(true); 52 | Signer signer = (Signer) ReflectionUtils.getField(signerField, this); 53 | 54 | String content; 55 | try { 56 | content = this.objectMapper.formatMap(getAccessTokenConverter().convertAccessToken(accessToken, authentication)); 57 | } catch (Exception ex) { 58 | throw new IllegalStateException("Cannot convert access token to JSON", ex); 59 | } 60 | 61 | Map headers = Collections.singletonMap("kid", "mmall-jwt-kid"); 62 | return JwtHelper.encode(content, signer, headers).getEncoded(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /mmall-domain-security/src/main/java/com/github/kay/mmall/security/provider/UsernamePasswordAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.security.provider; 2 | 3 | import com.github.kay.mmall.domain.security.AuthenticAccountDetailsService; 4 | import org.springframework.security.authentication.AuthenticationProvider; 5 | import org.springframework.security.authentication.BadCredentialsException; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.AuthenticationException; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider { 15 | 16 | private final AuthenticAccountDetailsService accountDetailsService; 17 | private final PasswordEncoder passwordEncoder; 18 | 19 | public UsernamePasswordAuthenticationProvider( 20 | AuthenticAccountDetailsService accountDetailsService, 21 | PasswordEncoder passwordEncoder) { 22 | this.accountDetailsService = accountDetailsService; 23 | this.passwordEncoder = passwordEncoder; 24 | } 25 | 26 | @Override 27 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 28 | final String username = authentication.getName(); 29 | final String password= (String) authentication.getCredentials(); 30 | 31 | final UserDetails user = accountDetailsService.loadUserByUsername(username); 32 | 33 | if (!passwordEncoder.matches(password, user.getPassword())) { 34 | throw new BadCredentialsException("password is wrong."); 35 | } 36 | 37 | return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities()); 38 | } 39 | 40 | @Override 41 | public boolean supports(Class aClass) { 42 | return aClass.equals(UsernamePasswordAuthenticationToken.class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mmall-domain-security/src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | enabled: false 4 | 5 | spring: 6 | datasource: 7 | initialization-mode: never 8 | cache: 9 | type: none 10 | 11 | logging: 12 | pattern: 13 | console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}" 14 | level: 15 | root: INFO -------------------------------------------------------------------------------- /mmall-domain-security/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: security 4 | cloud: 5 | config: 6 | uri: http://${CONFIG_HOST:localhost}:${CONFIG_PORT:8888} 7 | fail-fast: true 8 | password: ${CONFIG_PASS:dev} 9 | username: user 10 | -------------------------------------------------------------------------------- /mmall-domain-security/src/main/resources/public.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhB6SdL11wM6lkf3NbZIq AUZbwIHd1Aca/k3qtk2T58ABD6kwPJd9W1Vd6t6t3Db/C3EBJBJF8ZAWKgv0+fbI ZRrSsHCe5syAVfNUO34bF+pEuQKIIsk1gR85pCSNfRdUpDbNJmBjqrr6lLBbFdtc fh3eqaQOITqbxHPEm33D/YnKQHw2jDdPLWVeqj6uvy92UK+jRpGRzOwC/sVCfSwp QfZ51DiQx7nSfcS54SMzXklvxyKmex1D/Y61rb6ci13yFtIyz59SyY/p3BFQypIi QX6VqzCY48qquWA+Kh+HiO3WaZL0kjxwC06fgSOFBff9rngnap64XF/wlXFR3Bef ZQIDAQAB -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /mmall-domain-security/src/main/resources/rsa.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-domain-security/src/main/resources/rsa.jks -------------------------------------------------------------------------------- /mmall-domain-warehouse/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:12-alpine 2 | 3 | MAINTAINER kaybee 4 | 5 | ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ 6 | JAVA_OPTS="" \ 7 | PORT=8501 \ 8 | CONFIG_PORT=8888 \ 9 | AUTH_PORT=8301 \ 10 | PROFILES="default" 11 | 12 | ADD /build/libs/*.jar /mmall-warehouse.jar 13 | 14 | ENTRYPOINT ["sh", "-c", "java $JAVA_OPT -jar /mmall-warehouse.jar --spring.profiles.active=$PROFILES"] 15 | 16 | EXPOSE $PORT -------------------------------------------------------------------------------- /mmall-domain-warehouse/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' 4 | id 'io.spring.dependency-management' 5 | } 6 | 7 | configurations { 8 | implementation { 9 | exclude group: 'com.sun.jersey', module: 'jersey-client' 10 | exclude group: 'com.sun.jersey', module: 'jersey-core' 11 | exclude group: 'com.sun.jersey.contribs', module: 'jersey-apache-client4' 12 | } 13 | } 14 | 15 | dependencies { 16 | implementation project(':mmall-lib-infrastructure') 17 | 18 | implementation 'org.springframework.boot:spring-boot-starter' 19 | implementation 'org.springframework.boot:spring-boot-starter-web' 20 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 21 | implementation 'org.springframework.cloud:spring-cloud-starter-security' 22 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' 23 | } 24 | 25 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/WarehouseApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | 8 | @EnableCaching 9 | @EnableDiscoveryClient 10 | @SpringBootApplication(scanBasePackages = {"com.github.kay.mmall"}) 11 | public class WarehouseApplication { 12 | public static void main(String[] args) { 13 | SpringApplication.run(WarehouseApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/application/ProductApplicationService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse.application; 2 | 3 | import com.github.kay.mmall.domain.product.DeliveredStatus; 4 | import com.github.kay.mmall.domain.product.Product; 5 | import com.github.kay.mmall.domain.product.Stockpile; 6 | import com.github.kay.mmall.warehouse.domain.ProductService; 7 | import com.github.kay.mmall.warehouse.domain.StockpileService; 8 | 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | public class ProductApplicationService { 13 | 14 | private final ProductService service; 15 | private final StockpileService stockpileService; 16 | 17 | public ProductApplicationService(ProductService service, 18 | StockpileService stockpileService) { 19 | this.service = service; 20 | this.stockpileService = stockpileService; 21 | } 22 | 23 | public Iterable getAllProducts() { 24 | return service.getAllProducts(); 25 | } 26 | 27 | public Product getProduct(Integer id) { 28 | return service.getProduct(id); 29 | } 30 | 31 | public Product saveProduct(Product product) { 32 | return service.saveProduct(product); 33 | } 34 | 35 | public void removeProduct(Integer id) { 36 | service.removeProduct(id); 37 | } 38 | 39 | 40 | public Stockpile getStockpile(Integer productId) { 41 | return stockpileService.getByProductId(productId); 42 | } 43 | 44 | public void setStockpileAmountByProductId(Integer productId, Integer amount) { 45 | stockpileService.set(productId, amount); 46 | } 47 | 48 | /** 49 | * 调整商品出库状态 50 | */ 51 | public void setDeliveredStatus(Integer productId, DeliveredStatus status, Integer amount) { 52 | switch (status) { 53 | case DECREASE: 54 | stockpileService.decrease(productId, amount); 55 | break; 56 | case INCREASE: 57 | stockpileService.increase(productId, amount); 58 | break; 59 | case FROZEN: 60 | stockpileService.frozen(productId, amount); 61 | break; 62 | case THAWED: 63 | stockpileService.thawed(productId, amount); 64 | break; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/controller/AdvertisementController.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse.controller; 2 | 3 | import com.github.kay.mmall.warehouse.domain.Advertisement; 4 | import com.github.kay.mmall.warehouse.domain.AdvertisementRepository; 5 | import org.springframework.cache.annotation.Cacheable; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @RequestMapping("/restful/advertisements") 12 | public class AdvertisementController { 13 | 14 | private final AdvertisementRepository repository; 15 | 16 | public AdvertisementController(AdvertisementRepository repository) { 17 | this.repository = repository; 18 | } 19 | 20 | @GetMapping 21 | @Cacheable("resource.advertisements") 22 | public Iterable getAllAdvertisements() { 23 | return repository.findAll(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse.controller; 2 | 3 | import com.github.kay.mmall.domain.product.DeliveredStatus; 4 | import com.github.kay.mmall.domain.product.Product; 5 | import com.github.kay.mmall.domain.product.Stockpile; 6 | import com.github.kay.mmall.domain.security.Role; 7 | import com.github.kay.mmall.infrasucture.common.CodedMessage; 8 | import com.github.kay.mmall.infrasucture.common.CommonResponse; 9 | import com.github.kay.mmall.warehouse.application.ProductApplicationService; 10 | 11 | import org.springframework.cache.annotation.CacheConfig; 12 | import org.springframework.cache.annotation.CacheEvict; 13 | import org.springframework.cache.annotation.Cacheable; 14 | import org.springframework.cache.annotation.Caching; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.security.access.annotation.Secured; 17 | import org.springframework.security.access.prepost.PreAuthorize; 18 | import org.springframework.validation.annotation.Validated; 19 | import org.springframework.web.bind.annotation.DeleteMapping; 20 | import org.springframework.web.bind.annotation.GetMapping; 21 | import org.springframework.web.bind.annotation.PatchMapping; 22 | import org.springframework.web.bind.annotation.PathVariable; 23 | import org.springframework.web.bind.annotation.PostMapping; 24 | import org.springframework.web.bind.annotation.PutMapping; 25 | import org.springframework.web.bind.annotation.RequestBody; 26 | import org.springframework.web.bind.annotation.RequestMapping; 27 | import org.springframework.web.bind.annotation.RequestParam; 28 | import org.springframework.web.bind.annotation.RestController; 29 | 30 | @Validated 31 | @RestController 32 | @RequestMapping("/restful/products") 33 | @CacheConfig(cacheNames = "resource.product") 34 | public class ProductController { 35 | 36 | private final ProductApplicationService service; 37 | 38 | public ProductController(ProductApplicationService productApplicationService) { 39 | this.service = productApplicationService; 40 | } 41 | 42 | @GetMapping 43 | @Cacheable(key = "'ALL_PRODUCT'") 44 | public Iterable getAllProducts() { 45 | return service.getAllProducts(); 46 | } 47 | 48 | @GetMapping("/{id}") 49 | @Cacheable(key = "#id") 50 | public Product getProduct(@PathVariable("id") Integer id) { 51 | return service.getProduct(id); 52 | } 53 | 54 | @PutMapping 55 | @Caching(evict = { 56 | @CacheEvict(key = "#product.id"), 57 | @CacheEvict(key = "'ALL_PRODUCT'") 58 | }) 59 | @Secured(Role.ADMIN) 60 | public ResponseEntity updateProduct(@RequestBody Product product) { 61 | return CommonResponse.op(() -> service.saveProduct(product)); 62 | } 63 | 64 | @PostMapping 65 | @Caching(evict = { 66 | @CacheEvict(key = "#product.id"), 67 | @CacheEvict(key = "'ALL_PRODUCT'") 68 | }) 69 | @Secured(Role.ADMIN) 70 | public Product createProduct(@RequestBody Product product) { 71 | return service.saveProduct(product); 72 | } 73 | 74 | @DeleteMapping("/{id}") 75 | @Caching(evict = { 76 | @CacheEvict(key = "#id"), 77 | @CacheEvict(key = "'ALL_PRODUCT'") 78 | }) 79 | @Secured(Role.ADMIN) 80 | public ResponseEntity removeProduct(@PathVariable("id") Integer id) { 81 | return CommonResponse.op(() -> service.removeProduct(id)); 82 | } 83 | 84 | /** 85 | * 将指定的产品库存调整为指定数额 86 | */ 87 | @PatchMapping("/stockpile/{productId}") 88 | @Secured(Role.ADMIN) 89 | @PreAuthorize("#oauth2.hasAnyScope('BROWSER')") 90 | public ResponseEntity updateStockpile(@PathVariable("productId") Integer productId, @RequestParam("amount") Integer amount) { 91 | return CommonResponse.op(() -> service.setStockpileAmountByProductId(productId, amount)); 92 | } 93 | 94 | @GetMapping("/stockpile/{productId}") 95 | @Secured(Role.ADMIN) 96 | @PreAuthorize("#oauth2.hasAnyScope('BROWSER','SERVICE')") 97 | public Stockpile queryStockpile(@PathVariable("productId") Integer productId) { 98 | return service.getStockpile(productId); 99 | } 100 | 101 | // 以下是开放给内部微服务调用的方法 102 | 103 | /** 104 | * 将指定的产品库存调整为指定数额 105 | */ 106 | @PatchMapping("/stockpile/delivered/{productId}") 107 | @PreAuthorize("#oauth2.hasAnyScope('SERVICE')") 108 | public ResponseEntity setDeliveredStatus(@PathVariable("productId") Integer productId, 109 | @RequestParam("status") DeliveredStatus status, 110 | @RequestParam("amount") Integer amount) { 111 | return CommonResponse.op(() -> service.setDeliveredStatus(productId, status, amount)); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/domain/Advertisement.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse.domain; 2 | 3 | 4 | import com.github.kay.mmall.domain.BaseEntity; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.validation.constraints.NotEmpty; 11 | import javax.validation.constraints.NotNull; 12 | 13 | @Getter 14 | @Setter 15 | @Entity 16 | public class Advertisement extends BaseEntity { 17 | 18 | @NotEmpty(message = "广告图片不允许为空") 19 | private String image; 20 | 21 | @NotNull(message = "广告应当有关联的商品") 22 | @Column(name = "product_id") 23 | private Integer productId; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/domain/AdvertisementRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse.domain; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | public interface AdvertisementRepository extends CrudRepository { 6 | } 7 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/domain/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse.domain; 2 | 3 | import com.github.kay.mmall.domain.product.Product; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.Collection; 7 | 8 | public interface ProductRepository extends CrudRepository { 9 | 10 | Collection findAllByIdIn(Collection ids); 11 | } 12 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/domain/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse.domain; 2 | 3 | import com.github.kay.mmall.domain.product.Product; 4 | import com.github.kay.mmall.dto.Settlement; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.List; 8 | import java.util.function.Function; 9 | import java.util.stream.Collectors; 10 | 11 | @Service 12 | public class ProductService { 13 | 14 | private final ProductRepository repository; 15 | 16 | public ProductService(ProductRepository productRepository) { 17 | this.repository = productRepository; 18 | } 19 | 20 | /** 21 | * 根据结算单中货物的ID,填充货物的完整信息到结算单对象上 22 | */ 23 | public void replenishProductInformation(Settlement bill) { 24 | List ids = bill.getItems().stream().map(Settlement.Item::getProductId).collect(Collectors.toList()); 25 | bill.productMap = repository.findAllByIdIn(ids).stream().collect(Collectors.toMap(Product::getId, Function.identity())); 26 | } 27 | 28 | public Iterable getAllProducts() { 29 | return repository.findAll(); 30 | } 31 | 32 | public Product getProduct(Integer id) { 33 | return repository.findById(id).orElse(null); 34 | } 35 | 36 | public Product saveProduct(Product product) { 37 | return repository.save(product); 38 | } 39 | 40 | public void removeProduct(Integer id) { 41 | repository.deleteById(id); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/domain/StockpileRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse.domain; 2 | 3 | import com.github.kay.mmall.domain.product.Stockpile; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface StockpileRepository extends CrudRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/java/com/github/kay/mmall/warehouse/domain/StockpileService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.warehouse.domain; 2 | 3 | import com.github.kay.mmall.domain.product.Stockpile; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | 7 | import javax.persistence.EntityNotFoundException; 8 | 9 | @Slf4j 10 | @Service 11 | public class StockpileService { 12 | 13 | private final StockpileRepository repository; 14 | 15 | public StockpileService(StockpileRepository repository) { 16 | this.repository = repository; 17 | } 18 | 19 | /** 20 | * 根据产品查询库存 21 | */ 22 | public Stockpile getByProductId(Integer productId) { 23 | return repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString())); 24 | } 25 | 26 | /** 27 | * 货物售出 28 | * 从冻结状态的货物中扣减 29 | */ 30 | public void decrease(Integer productId, Integer amount) { 31 | Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString())); 32 | stock.decrease(amount); 33 | repository.save(stock); 34 | log.info("库存出库,商品:{},数量:{}", productId, amount); 35 | } 36 | 37 | /** 38 | * 货物增加 39 | * 增加指定数量货物至正常货物状态 40 | */ 41 | public void increase(Integer productId, Integer amount) { 42 | Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString())); 43 | stock.increase(amount); 44 | repository.save(stock); 45 | log.info("库存入库,商品:{},数量:{}", productId, amount); 46 | } 47 | 48 | 49 | /** 50 | * 货物冻结 51 | * 从正常货物中移动指定数量至冻结状态 52 | */ 53 | public void frozen(Integer productId, Integer amount) { 54 | Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString())); 55 | stock.frozen(amount); 56 | repository.save(stock); 57 | log.info("冻结库存,商品:{},数量:{}", productId, amount); 58 | } 59 | 60 | /** 61 | * 货物解冻 62 | * 从冻结货物中移动指定数量至正常状态 63 | */ 64 | public void thawed(Integer productId, Integer amount) { 65 | Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString())); 66 | stock.thawed(amount); 67 | repository.save(stock); 68 | log.info("解冻库存,商品:{},数量:{}", productId, amount); 69 | } 70 | 71 | /** 72 | * 设置货物数量 73 | */ 74 | public void set(Integer productId, Integer amount) { 75 | Stockpile stock = repository.findById(productId).orElseThrow(() -> new EntityNotFoundException(productId.toString())); 76 | stock.setAmount(amount); 77 | repository.save(stock); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | spring: 4 | cache: 5 | type: redis 6 | redis: 7 | cache-null-values: false 8 | time-to-live: PT5M 9 | datasource: 10 | schema: "classpath:db/schema.sql" 11 | data: "classpath:db/data.sql" 12 | sql-script-encoding: UTF-8 13 | url: "jdbc:mysql://localhost:3306/mmall?useUnicode=true&characterEncoding=utf-8" 14 | username: "root" 15 | password: "root1234" 16 | initialization-mode: always 17 | jpa: 18 | show-sql: true 19 | hibernate: 20 | ddl-auto: none 21 | open-in-view: true 22 | generate-ddl: true 23 | database: mysql 24 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 25 | resources: 26 | chain: 27 | compressed: true 28 | cache: true 29 | cache: 30 | period: 86400 31 | redis: 32 | host: ${redis.host:localhost} 33 | port: ${redis.tcp.6379:6379} 34 | 35 | logging: 36 | pattern: 37 | console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}" 38 | level: 39 | root: INFO 40 | com.github.kay: DEBUG 41 | 42 | mmall: 43 | redisson-config: "redisson-config.yml" 44 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: warehouse 4 | cloud: 5 | config: 6 | uri: http://${CONFIG_HOST:localhost}:${CONFIG_PORT:8888} 7 | fail-fast: true 8 | password: ${CONFIG_PASS:dev} 9 | username: user 10 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/resources/db/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO mmall.product 2 | VALUES (1, '深入理解Java虚拟机(第3版)', 129, 3 | '

这是一部从工作原理和工程实践两个维度深入剖析JVM的著作,是计算机领域公认的经典,繁体版在台湾也颇受欢迎。

自2011年上市以来,前两个版本累计印刷36次,销量超过30万册,两家主要网络书店的评论近90000条,内容上近乎零差评,是原创计算机图书领域不可逾越的丰碑,第3版在第2版的基础上做了重大修订,内容更丰富、实战性更强:根据新版JDK对内容进行了全方位的修订和升级,围绕新技术和生产实践新增逾10万字,包含近50%的全新内容,并对第2版中含糊、瑕疵和错误内容进行了修正。

全书一共13章,分为五大部分:

第一部分(第1章)走近Java

系统介绍了Java的技术体系、发展历程、虚拟机家族,以及动手编译JDK,了解这部分内容能对学习JVM提供良好的指引。

第二部分(第2~5章)自动内存管理

详细讲解了Java的内存区域与内存溢出、垃圾收集器与内存分配策略、虚拟机性能监控与故障排除等与自动内存管理相关的内容,以及10余个经典的性能优化案例和优化方法;

第三部分(第6~9章)虚拟机执行子系统

深入分析了虚拟机执行子系统,包括类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎,以及多个类加载及其执行子系统的实战案例;

第四部分(第10~11章)程序编译与代码优化

详细讲解了程序的前、后端编译与优化,包括前端的易用性优化措施,如泛型、主动装箱拆箱、条件编译等的内容的深入分析;以及后端的性能优化措施,如虚拟机的热点探测方法、HotSpot 的即时编译器、提前编译器,以及各种常见的编译期优化技术;

第五部分(第12~13章)高效并发

主要讲解了Java实现高并发的原理,包括Java的内存模型、线程与协程,以及线程安全和锁优化。

全书以实战为导向,通过大量与实际生产环境相结合的案例分析和展示了解决各种Java技术难题的方案和技巧。

', 4 | '/static/cover/product0.jpg', '/static/desc/jvm3.jpg'); 5 | INSERT INTO mmall.product VALUES (2, '测试商品0', 10, '

测试描述

', '/static/cover/product1.jpg', '/static/cover/product1.jpg'); 6 | INSERT INTO mmall.product VALUES (3, '测试商品1', 15, '

测试描述

', '/static/cover/product2.jpg', '/static/cover/product2.jpg'); 7 | INSERT INTO mmall.product VALUES (4, '测试商品2', 15, '

测试描述

', '/static/cover/product3.jpg', '/static/cover/product3.jpg'); 8 | INSERT INTO mmall.product VALUES (5, '测试商品3', 15, '

测试描述

', '/static/cover/product4.jpg', '/static/cover/product4.jpg'); 9 | INSERT INTO mmall.product VALUES (6, '测试商品4', 15, '

测试描述

', '/static/cover/product5.jpg', '/static/cover/product5.jpg'); 10 | 11 | 12 | insert into stockpile values (1, 2000, 0, 1); 13 | insert into stockpile values (2, 2000, 0, 2); 14 | insert into stockpile values (3, 2000, 0, 3); 15 | insert into stockpile values (4, 2000, 0, 4); 16 | insert into stockpile values (5, 2000, 0, 5); 17 | insert into stockpile values (6, 2000, 0, 6); 18 | 19 | insert into specification values (1, '标签1', 1, '图书'); 20 | insert into specification values (2, '标签1', 2, '图书'); 21 | insert into specification values (3, '标签1', 3, '图书'); 22 | 23 | # 广告牌 24 | INSERT INTO mmall.advertisement (id, image, product_id) VALUES (1, '/static/carousel/banner1.jpg', 1); 25 | INSERT INTO mmall.advertisement (id, image, product_id) VALUES (2, '/static/carousel/banner2.jpg', 2); 26 | INSERT INTO mmall.advertisement (id, image, product_id) VALUES (3, '/static/carousel/banner3.jpg', 3); 27 | INSERT INTO mmall.advertisement (id, image, product_id) VALUES (4, '/static/carousel/banner4.jpg', 3); 28 | INSERT INTO mmall.advertisement (id, image, product_id) VALUES (5, '/static/carousel/banner5.jpg', 3); 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/resources/db/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS mmall; 2 | ALTER DATABASE mmall 3 | DEFAULT CHARACTER SET utf8 4 | DEFAULT COLLATE utf8_general_ci; 5 | GRANT ALL PRIVILEGES ON mmall.* TO 'mmall@%' IDENTIFIED BY 'mmall'; 6 | 7 | DROP TABLE IF EXISTS specification; 8 | DROP TABLE IF EXISTS advertisement; 9 | DROP TABLE IF EXISTS stockpile; 10 | DROP TABLE IF EXISTS product; 11 | 12 | create table advertisement 13 | ( 14 | id int unsigned not null auto_increment primary key, 15 | image varchar(255) null, 16 | product_id int unsigned not null 17 | ) engine = InnoDB; 18 | 19 | create table specification 20 | ( 21 | id int unsigned not null auto_increment primary key, 22 | item varchar(255) not null , 23 | product_id int unsigned not null, 24 | value varchar(255) null 25 | ) engine = InnoDB; 26 | 27 | create table product 28 | ( 29 | id int unsigned not null auto_increment primary key, 30 | title varchar(255) null, 31 | price decimal(19, 2) null, 32 | description VARCHAR(8000) null, 33 | cover varchar(255) null, 34 | detail varchar(255) null 35 | ) engine = InnoDB; 36 | 37 | create table stockpile 38 | ( 39 | id int unsigned not null auto_increment primary key, 40 | amount int default 0, 41 | frozen int default 0, 42 | product_id int unsigned not null 43 | ) engine = InnoDB; 44 | 45 | create index idx_product_id on specification (product_id); -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/resources/public.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhB6SdL11wM6lkf3NbZIq 3 | AUZbwIHd1Aca/k3qtk2T58ABD6kwPJd9W1Vd6t6t3Db/C3EBJBJF8ZAWKgv0+fbI 4 | ZRrSsHCe5syAVfNUO34bF+pEuQKIIsk1gR85pCSNfRdUpDbNJmBjqrr6lLBbFdtc 5 | fh3eqaQOITqbxHPEm33D/YnKQHw2jDdPLWVeqj6uvy92UK+jRpGRzOwC/sVCfSwp 6 | QfZ51DiQx7nSfcS54SMzXklvxyKmex1D/Y61rb6ci13yFtIyz59SyY/p3BFQypIi 7 | QX6VqzCY48qquWA+Kh+HiO3WaZL0kjxwC06fgSOFBff9rngnap64XF/wlXFR3Bef 8 | ZQIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /mmall-domain-warehouse/src/main/resources/redisson-config.yml: -------------------------------------------------------------------------------- 1 | singleServerConfig: 2 | address: "redis://${redis.host:-localhost}:${redis.tcp.6379:-6379}" 3 | connectionPoolSize: 24 4 | connectionMinimumIdleSize: 12 5 | keepAlive: true 6 | 7 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' apply false 4 | id 'io.spring.dependency-management' 5 | } 6 | 7 | dependencyManagement { 8 | imports { 9 | mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES 10 | } 11 | } 12 | 13 | dependencies { 14 | implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' 15 | implementation 'io.github.openfeign:feign-jaxrs2:10.9' 16 | implementation 'io.github.openfeign:feign-httpclient:10.9' 17 | implementation 'org.springframework.cloud:spring-cloud-starter-config' 18 | implementation 'org.springframework.cloud:spring-cloud-starter-oauth2' 19 | implementation 'org.springframework.cloud:spring-cloud-starter-security' 20 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 21 | implementation 'org.springframework.security:spring-security-jwt:1.0.10.RELEASE' 22 | implementation 'org.springframework.boot:spring-boot-starter-cache' 23 | implementation 'org.springframework.boot:spring-boot-starter-data-redis' 24 | implementation 'javax.servlet:javax.servlet-api' 25 | implementation 'org.hibernate.validator:hibernate-validator' 26 | 27 | implementation 'org.redisson:redisson:3.17.4' 28 | implementation 'redis.clients:jedis:3.8.0' 29 | 30 | runtimeOnly 'mysql:mysql-connector-java' 31 | } -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain; 2 | 3 | import javax.persistence.GeneratedValue; 4 | import javax.persistence.GenerationType; 5 | import javax.persistence.Id; 6 | import javax.persistence.MappedSuperclass; 7 | import java.io.Serializable; 8 | 9 | 10 | @MappedSuperclass 11 | public class BaseEntity implements Serializable { 12 | 13 | @Id 14 | @GeneratedValue(strategy = GenerationType.IDENTITY) 15 | private Integer id; 16 | 17 | public Integer getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Integer id) { 22 | this.id = id; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/account/Account.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.account; 2 | 3 | import com.github.kay.mmall.domain.BaseEntity; 4 | 5 | import javax.persistence.Entity; 6 | import javax.validation.constraints.Email; 7 | import javax.validation.constraints.NotEmpty; 8 | import javax.validation.constraints.Pattern; 9 | 10 | import lombok.Getter; 11 | import lombok.Setter; 12 | 13 | @Getter 14 | @Setter 15 | @Entity 16 | public class Account extends BaseEntity { 17 | 18 | @NotEmpty(message = "用户不允许为空") 19 | private String username; 20 | 21 | // 密码字段不参与序列化(但反序列化是参与的)、不参与更新(但插入是参与的) 22 | // 这意味着密码字段不会在获取对象(很多操作都会关联用户对象)的时候泄漏出去; 23 | // 也意味着此时“修改密码”一类的功能无法以用户对象资源的接口来处理(因为更新对象时密码不会被更新),需要单独提供接口去完成 24 | //@JsonProperty(access = JsonProperty.Access.WRITE_ONLY) 25 | //@Column(updatable = false) 26 | //在微服务内部调用需要 27 | private String password; 28 | 29 | @NotEmpty(message = "用户姓名不允许为空") 30 | private String name; 31 | 32 | private String avatar; 33 | 34 | @Pattern(regexp = "1\\d{10}", message = "手机号格式不正确") 35 | private String telephone; 36 | 37 | @Email(message = "邮箱格式不正确") 38 | private String email; 39 | 40 | private String location; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/account/Address.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.account; 2 | 3 | import com.github.kay.mmall.domain.BaseEntity; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import javax.persistence.Entity; 8 | import javax.validation.constraints.NotEmpty; 9 | import javax.validation.constraints.Pattern; 10 | 11 | @Setter 12 | @Getter 13 | @Entity 14 | public class Address extends BaseEntity { 15 | private Integer userId; 16 | 17 | @NotEmpty(message = "姓名不允许为空") 18 | private String receiverName; 19 | 20 | @Pattern(regexp = "1\\d{10}", message = "手机号格式不正确") 21 | private String phone; 22 | 23 | @NotEmpty(message = "省份不允许为空") 24 | private String province; 25 | 26 | @NotEmpty(message = "城市不允许为空") 27 | private String city; 28 | 29 | private String district; 30 | 31 | @NotEmpty(message = "地址不允许为空") 32 | private String details; 33 | 34 | private String zip; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/product/DeliveredStatus.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.product; 2 | 3 | public enum DeliveredStatus { 4 | 5 | /** 6 | * 出库调减库存 7 | */ 8 | DECREASE, 9 | 10 | /** 11 | * 入库调增库存 12 | */ 13 | INCREASE, 14 | 15 | /** 16 | * 待出库冻结库存 17 | */ 18 | FROZEN, 19 | 20 | /** 21 | * 取消出库解冻库存 22 | */ 23 | THAWED 24 | } 25 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/product/Product.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.product; 2 | 3 | import com.github.kay.mmall.domain.BaseEntity; 4 | import com.github.kay.mmall.domain.product.Specification; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.CascadeType; 9 | import javax.persistence.Entity; 10 | import javax.persistence.FetchType; 11 | import javax.persistence.JoinColumn; 12 | import javax.persistence.OneToMany; 13 | import javax.validation.constraints.Min; 14 | import javax.validation.constraints.NotEmpty; 15 | import javax.validation.constraints.NotNull; 16 | import java.math.BigDecimal; 17 | import java.util.Set; 18 | 19 | @Setter 20 | @Getter 21 | @Entity 22 | public class Product extends BaseEntity { 23 | 24 | @NotEmpty(message = "商品名称不允许为空") 25 | private String title; 26 | 27 | @NotNull(message = "商品应当有明确的价格") 28 | @Min(value = 0, message = "商品价格最低为零") 29 | private BigDecimal price; 30 | 31 | private String description; 32 | 33 | private String cover; 34 | 35 | private String detail; 36 | 37 | @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 38 | @JoinColumn(name = "product_id") 39 | private transient Set specifications; 40 | } 41 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/product/Specification.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.product; 2 | 3 | import com.github.kay.mmall.domain.BaseEntity; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.validation.constraints.NotEmpty; 10 | import javax.validation.constraints.NotNull; 11 | 12 | @Getter 13 | @Setter 14 | @Entity 15 | public class Specification extends BaseEntity { 16 | 17 | @NotEmpty(message = "商品规格名称不允许为空") 18 | private String item; 19 | 20 | @NotEmpty(message = "商品规格内容不允许为空") 21 | private String value; 22 | 23 | @NotNull(message = "商品规格必须归属于指定商品") 24 | @Column(name = "product_id") 25 | private Integer productId; 26 | } 27 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/product/Stockpile.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.product; 2 | 3 | import com.github.kay.mmall.domain.BaseEntity; 4 | import com.github.kay.mmall.domain.product.Product; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.FetchType; 10 | import javax.persistence.JoinColumn; 11 | import javax.persistence.OneToOne; 12 | 13 | /** 14 | * 库存 15 | */ 16 | @Setter 17 | @Getter 18 | @Entity 19 | public class Stockpile extends BaseEntity { 20 | 21 | private Integer amount; 22 | 23 | private Integer frozen; 24 | 25 | @OneToOne(fetch = FetchType.LAZY) 26 | @JoinColumn(name = "product_id") 27 | private transient Product product; 28 | 29 | public void frozen(Integer number) { 30 | this.amount -= number; 31 | this.frozen += number; 32 | } 33 | 34 | public void thawed(Integer number) { 35 | frozen(-1 * number); 36 | } 37 | 38 | public void decrease(Integer number) { 39 | this.frozen -= number; 40 | } 41 | 42 | public void increase(Integer number) { 43 | this.amount += number; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/security/AccountServiceClient.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.security; 2 | 3 | import com.github.kay.mmall.domain.account.Account; 4 | import org.springframework.cloud.openfeign.FeignClient; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | 8 | @FeignClient(name = "account") 9 | public interface AccountServiceClient { 10 | 11 | @GetMapping("/restful/accounts/{username}") 12 | Account findByUsername(@PathVariable("username") String username); 13 | } 14 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/security/AuthenticAccount.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.security; 2 | 3 | import com.github.kay.mmall.domain.account.Account; 4 | import org.springframework.beans.BeanUtils; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | 9 | import java.util.Collection; 10 | import java.util.HashSet; 11 | 12 | public class AuthenticAccount extends Account implements UserDetails { 13 | 14 | private Collection authorities = new HashSet<>(); 15 | 16 | public AuthenticAccount() { 17 | super(); 18 | authorities.add(new SimpleGrantedAuthority(Role.USER)); 19 | } 20 | 21 | public AuthenticAccount(Account origin) { 22 | this(); 23 | BeanUtils.copyProperties(origin, this); 24 | if (getId() == 1) { 25 | // 由于没有做用户管理功能,默认给系统中第一个用户赋予管理员角色 26 | authorities.add(new SimpleGrantedAuthority(Role.ADMIN)); 27 | } 28 | } 29 | 30 | @Override 31 | public Collection getAuthorities() { 32 | return authorities; 33 | } 34 | 35 | @Override 36 | public boolean isAccountNonExpired() { 37 | return true; 38 | } 39 | 40 | @Override 41 | public boolean isAccountNonLocked() { 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean isCredentialsNonExpired() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean isEnabled() { 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/security/AuthenticAccountDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.security; 2 | 3 | import org.springframework.security.core.userdetails.UserDetails; 4 | import org.springframework.security.core.userdetails.UserDetailsService; 5 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class AuthenticAccountDetailsService implements UserDetailsService { 10 | 11 | private final AuthenticAccountRepository accountRepository; 12 | 13 | public AuthenticAccountDetailsService(AuthenticAccountRepository accountRepository) { 14 | this.accountRepository = accountRepository; 15 | } 16 | 17 | 18 | @Override 19 | public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { 20 | return accountRepository.findByUsername(s); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/security/AuthenticAccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.Optional; 8 | 9 | @Component 10 | public class AuthenticAccountRepository { 11 | 12 | @Autowired 13 | private AccountServiceClient accountServiceClient; 14 | 15 | public AuthenticAccount findByUsername(String username) { 16 | return new AuthenticAccount( 17 | Optional.ofNullable(accountServiceClient.findByUsername(username)) 18 | .orElseThrow( 19 | () -> new UsernameNotFoundException(String.format("User '%s' is not existed.", username))) 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/security/GrantType.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.security; 2 | 3 | /** 4 | * OAuth2 授权类型 5 | **/ 6 | public enum GrantType { 7 | 8 | // 四种标准类型 9 | PASSWORD("password"), 10 | CLIENT_CREDENTIALS("client_credentials"), 11 | IMPLICIT("implicit"), 12 | AUTHORIZATION_CODE("authorization_code"), 13 | // 用于刷新令牌 14 | REFRESH_TOKEN("refresh_token"); 15 | 16 | private final String value; 17 | 18 | GrantType(String value) { 19 | this.value = value; 20 | } 21 | 22 | public String getValue() { 23 | return value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/security/Role.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.security; 2 | 3 | public final class Role { 4 | 5 | private Role() { 6 | //no-op 7 | } 8 | 9 | public static final String USER = "ROLE_USER"; 10 | public static final String ADMIN = "ROLE_ADMIN"; 11 | } 12 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/domain/security/Scope.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.domain.security; 2 | 3 | public final class Scope { 4 | private Scope() { 5 | //no-op 6 | } 7 | 8 | public static final String BROWSER = "BROWSER"; 9 | public static final String SERVICE = "SERVICE"; 10 | } 11 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/dto/Settlement.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.dto; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.github.kay.mmall.domain.product.Product; 6 | import lombok.Data; 7 | 8 | import javax.validation.constraints.Min; 9 | import javax.validation.constraints.NotEmpty; 10 | import javax.validation.constraints.NotNull; 11 | import javax.validation.constraints.Size; 12 | import java.util.Collection; 13 | import java.util.Map; 14 | 15 | //支付订单 TODO: 支付订单放在了缓存中,没有做持久化 16 | @Data 17 | public class Settlement { 18 | 19 | @Size(min = 1, message = "结算单中缺少商品清单") 20 | private Collection items; 21 | 22 | @NotNull(message = "结算单中缺少配送信息") 23 | private Purchase purchase; 24 | 25 | /** 26 | * 购物清单中的商品信息 27 | * 基于安全原因(避免篡改价格),改信息不会取客户端的,需在服务端根据商品ID再查询出来 28 | */ 29 | public transient Map productMap; 30 | 31 | /** 32 | * 购买的商品 33 | */ 34 | @Data 35 | public static class Item { 36 | @NotNull(message = "结算单中必须有明确的商品数量") 37 | @Min(value = 1, message = "结算单中商品数量至少为一件") 38 | private Integer amount; 39 | 40 | @JsonProperty("id") 41 | @NotNull(message = "结算单中必须有明确的商品信息") 42 | private Integer productId; 43 | } 44 | 45 | @Data 46 | public static class Purchase{ 47 | 48 | private Boolean delivery = true; 49 | 50 | @NotEmpty(message = "配送信息中缺少支付方式") 51 | private String pay; 52 | 53 | @NotEmpty(message = "配送信息中缺少收件人姓名") 54 | private String name; 55 | 56 | @NotEmpty(message = "配送信息中缺少收件人电话") 57 | private String telephone; 58 | 59 | @NotEmpty(message = "配送信息中缺少收件地址") 60 | private String location; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/CROSFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture; 2 | 3 | import org.springframework.core.Ordered; 4 | import org.springframework.core.annotation.Order; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.servlet.Filter; 8 | import javax.servlet.FilterChain; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.ServletRequest; 11 | import javax.servlet.ServletResponse; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.io.IOException; 15 | 16 | @Component 17 | @Order(Ordered.HIGHEST_PRECEDENCE) 18 | public class CROSFilter implements Filter { 19 | 20 | @Override 21 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 22 | throws IOException, ServletException { 23 | HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; 24 | HttpServletResponse response = (HttpServletResponse) servletResponse; 25 | response.setHeader("Access-Control-Allow-Origin", "*"); 26 | response.setHeader("Access-Control-Allow-Methods", "*"); 27 | response.setHeader("Access-Control-Allow-Headers", "*"); 28 | response.setHeader("Access-Control-Allow-Credentials", "true"); 29 | response.setHeader("Access-Control-Max-Age", "3600"); 30 | if ("OPTIONS".equals(httpServletRequest.getMethod())) { 31 | response.setStatus(HttpServletResponse.SC_OK); 32 | return; 33 | } 34 | 35 | filterChain.doFilter(servletRequest, response); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/EncoderConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | @Configuration 9 | public class EncoderConfiguration { 10 | 11 | @Bean 12 | public PasswordEncoder passwordEncoder(){ 13 | return new BCryptPasswordEncoder(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/common/CodedMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.common; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Data; 5 | 6 | /** 7 | * 目前大多数前端用到的结构,习惯使用 JSON-RPC 的这种格式,而不是以 HTTP 状态码为准 8 | */ 9 | @JsonInclude(JsonInclude.Include.NON_NULL) 10 | @Data 11 | public class CodedMessage { 12 | public static final Integer CODE_SUCCESS = 0; 13 | public static final Integer CODE_DEFAULT_FAILURE = 1; 14 | 15 | private Integer code; 16 | private String message; 17 | private Object data; 18 | 19 | public CodedMessage(Integer code, String message) { 20 | this.code = code; 21 | this.message = message; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/common/CommonResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.common; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.http.ResponseEntity; 8 | 9 | import java.util.function.Consumer; 10 | 11 | @Slf4j 12 | @UtilityClass 13 | public class CommonResponse { 14 | 15 | public static ResponseEntity send(HttpStatus status, String message) { 16 | Integer code = status.is2xxSuccessful() ? CodedMessage.CODE_SUCCESS : CodedMessage.CODE_DEFAULT_FAILURE; 17 | return ResponseEntity.status(status) 18 | .contentType(MediaType.APPLICATION_JSON) 19 | .body(new CodedMessage(code, message)); 20 | } 21 | 22 | public static ResponseEntity failure(String message) { 23 | return send(HttpStatus.INTERNAL_SERVER_ERROR, message); 24 | } 25 | 26 | public static ResponseEntity success(String message) { 27 | return send(HttpStatus.OK, message); 28 | } 29 | 30 | public static ResponseEntity success() { 31 | return send(HttpStatus.OK, "success"); 32 | } 33 | 34 | public static ResponseEntity op(Runnable executor) { 35 | return op(executor, e -> log.error(e.getMessage(), e)); 36 | } 37 | 38 | public static ResponseEntity op(Runnable executor, Consumer exceptionConsumer) { 39 | try { 40 | executor.run(); 41 | return CommonResponse.success(); 42 | } catch (Exception e) { 43 | exceptionConsumer.accept(e); 44 | throw e; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/common/ExceptionControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.common; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.util.StringUtils; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | 10 | import javax.validation.ConstraintViolationException; 11 | 12 | @Slf4j 13 | @RestControllerAdvice 14 | public class ExceptionControllerAdvice { 15 | 16 | @ExceptionHandler({ 17 | ConstraintViolationException.class 18 | }) 19 | public ResponseEntity badRequest(ConstraintViolationException exception) { 20 | 21 | log.warn("validation failed.", exception); 22 | 23 | return CommonResponse.send(HttpStatus.BAD_REQUEST, exception.getMessage()); 24 | } 25 | 26 | @ExceptionHandler(ResourceNotFoundException.class) 27 | public ResponseEntity notFound(ResourceNotFoundException notFoundException){ 28 | return CommonResponse.send(HttpStatus.NOT_FOUND, notFoundException.getMessage()); 29 | } 30 | 31 | @ExceptionHandler(Exception.class) 32 | public ResponseEntity error(Exception exception){ 33 | 34 | log.error("internal error.", exception); 35 | 36 | String message = exception.getMessage(); 37 | message = StringUtils.isEmpty(message) ? exception.toString() : message; 38 | 39 | return CommonResponse.send(HttpStatus.INTERNAL_SERVER_ERROR, message); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/common/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.common; 2 | 3 | import java.util.function.Supplier; 4 | 5 | public class ResourceNotFoundException extends RuntimeException{ 6 | public ResourceNotFoundException(String message) { 7 | super(message); 8 | } 9 | 10 | public static ResourceNotFoundException create(String message){ 11 | return new ResourceNotFoundException(message); 12 | } 13 | 14 | public static Supplier supplier(String message){ 15 | return () -> new ResourceNotFoundException(message); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/configuration/FeignConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.configuration; 2 | 3 | import org.springframework.cloud.openfeign.EnableFeignClients; 4 | import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Profile; 8 | import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; 9 | import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; 10 | 11 | import feign.RequestInterceptor; 12 | 13 | @Configuration 14 | @Profile("!test") 15 | @EnableFeignClients(basePackages = {"com.github.kay.mmall"}) 16 | public class FeignConfiguration { 17 | 18 | //在请求时自动加入基于OAuth2的客户端模式认证的Header 19 | @Bean 20 | public RequestInterceptor oauth2FeignRequestInterceptor(ClientCredentialsResourceDetails resourceDetails){ 21 | return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resourceDetails); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/configuration/JPAConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.configuration; 2 | 3 | import org.springframework.boot.autoconfigure.domain.EntityScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @EntityScan(basePackages = "com.github.kay.mmall") 8 | public class JPAConfiguration { 9 | } 10 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/lock/LockAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.lock; 2 | 3 | import org.redisson.Redisson; 4 | import org.redisson.api.RedissonClient; 5 | import org.redisson.config.Config; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Primary; 12 | import org.springframework.core.io.ClassPathResource; 13 | 14 | import lombok.SneakyThrows; 15 | 16 | @Configuration 17 | public class LockAutoConfiguration { 18 | 19 | @Configuration 20 | @ConditionalOnProperty(prefix = "mmall.redisson", name = "enabled",havingValue = "true") 21 | @EnableConfigurationProperties(RedissonProperties.class) 22 | static class RedissonLockAutoConfiguration{ 23 | 24 | @SneakyThrows 25 | @Bean 26 | public RedissonClient getRedissonClient(RedissonProperties redissonProperties) { 27 | final Config config = Config.fromYAML(new ClassPathResource(redissonProperties.getFileName()).getInputStream()); 28 | return Redisson.create(config); 29 | } 30 | 31 | @Bean 32 | @Primary 33 | public LockService lockService(RedissonClient redissonClient){ 34 | return new LockServiceWithRedisson(redissonClient); 35 | } 36 | } 37 | 38 | @Bean 39 | @ConditionalOnMissingBean(LockService.class) 40 | public LockService lockService(){ 41 | return new LockServiceDefault(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/lock/LockOperationException.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.lock; 2 | 3 | public class LockOperationException extends RuntimeException{ 4 | 5 | public LockOperationException(String message, Throwable cause) { 6 | super(message, cause); 7 | } 8 | 9 | public LockOperationException(Throwable cause) { 10 | super(cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/lock/LockService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.lock; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | public interface LockService { 6 | 7 | T execute(String id, Callable callable); 8 | 9 | void execute(String id, Runnable runnable); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/lock/LockServiceDefault.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.lock; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | import lombok.SneakyThrows; 6 | 7 | /** 8 | * No lock execute 9 | */ 10 | public class LockServiceDefault implements LockService { 11 | 12 | @SneakyThrows 13 | @Override 14 | public T execute(String id, Callable callable) { 15 | return callable.call(); 16 | } 17 | 18 | @Override 19 | public void execute(String id, Runnable runnable) { 20 | runnable.run(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/lock/LockServiceWithRedisson.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.lock; 2 | 3 | import org.redisson.api.RLock; 4 | import org.redisson.api.RedissonClient; 5 | 6 | import java.util.concurrent.Callable; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | public class LockServiceWithRedisson implements LockService { 13 | 14 | private static final String LOCK_PREFIX = "LOCK:"; 15 | private static final int RELEASE_TIME_IN_MINUTE = 10; 16 | 17 | private final RedissonClient redissonClient; 18 | 19 | public LockServiceWithRedisson(RedissonClient redissonClient) { 20 | this.redissonClient = redissonClient; 21 | } 22 | 23 | @Override 24 | public T execute(String id, Callable callable) { 25 | RLock lock = redissonClient.getLock(LOCK_PREFIX + id); 26 | 27 | try { 28 | if (lock.tryLock(0, RELEASE_TIME_IN_MINUTE, TimeUnit.MINUTES)) { 29 | return callable.call(); 30 | } 31 | 32 | log.warn("Failed to acquire lock for:[{}]", id); 33 | } catch (InterruptedException e) { 34 | log.warn("Lock Thread InterruptedException", e); 35 | Thread.currentThread() 36 | .interrupt(); 37 | } catch (Exception e) { 38 | throw new LockOperationException(e); 39 | } finally { 40 | lock.unlock(); 41 | } 42 | 43 | return null; 44 | } 45 | 46 | @Override 47 | public void execute(String id, Runnable runnable) { 48 | RLock lock = redissonClient.getLock(LOCK_PREFIX + id); 49 | 50 | try { 51 | if (lock.tryLock(0, RELEASE_TIME_IN_MINUTE, TimeUnit.MINUTES)) { 52 | runnable.run(); 53 | } else { 54 | log.warn("Failed to acquire lock for:[{}]", id); 55 | } 56 | } catch (InterruptedException e) { 57 | log.warn("Lock Thread InterruptedException", e); 58 | Thread.currentThread() 59 | .interrupt(); 60 | } catch (Exception e) { 61 | throw new LockOperationException(e); 62 | } finally { 63 | lock.unlock(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/lock/RedissonProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.lock; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import lombok.Data; 6 | 7 | 8 | @Data 9 | @ConfigurationProperties(prefix = "mmall.redisson") 10 | public class RedissonProperties { 11 | private boolean enabled; 12 | private String fileName; 13 | } 14 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/redis/RedisConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.redis; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import org.springframework.boot.autoconfigure.data.redis.RedisProperties; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.data.redis.connection.RedisConnectionFactory; 8 | import org.springframework.data.redis.connection.RedisStandaloneConfiguration; 9 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 10 | import org.springframework.data.redis.core.RedisTemplate; 11 | import org.springframework.data.redis.listener.KeyExpirationEventMessageListener; 12 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; 13 | import org.springframework.data.redis.serializer.RedisSerializer; 14 | 15 | @Configuration 16 | @ConditionalOnProperty(prefix = "spring.cache",name = "type", havingValue = "redis") 17 | public class RedisConfiguration { 18 | 19 | @Bean 20 | public JedisConnectionFactory redisConnectionFactory(RedisProperties redisProperties) { 21 | RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration( 22 | redisProperties.getHost(), redisProperties.getPort()); 23 | return new JedisConnectionFactory(configuration); 24 | } 25 | 26 | @Bean 27 | public RedisMessageListenerContainer messageListenerContainer(RedisConnectionFactory connectionFactory) { 28 | RedisMessageListenerContainer container = new RedisMessageListenerContainer(); 29 | container.setConnectionFactory(connectionFactory); 30 | return container; 31 | } 32 | 33 | @Bean 34 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 35 | RedisTemplate redisTemplate = new RedisTemplate<>(); 36 | redisTemplate.setConnectionFactory(redisConnectionFactory); 37 | redisTemplate.setKeySerializer(RedisSerializer.string()); 38 | redisTemplate.setValueSerializer(RedisSerializer.json()); 39 | redisTemplate.setHashKeySerializer(RedisSerializer.string()); 40 | redisTemplate.setHashValueSerializer(RedisSerializer.json()); 41 | return redisTemplate; 42 | } 43 | 44 | @Bean 45 | public KeyExpirationEventMessageListener keyExpirationEventMessageListener( 46 | RedisMessageListenerContainer listenerContainer, 47 | RedisKeyExpirationHandlerManager redisKeyExpirationHandlerManager){ 48 | return new RedisKeyExpirationListener(listenerContainer, redisKeyExpirationHandlerManager); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/redis/RedisKeyExpirationHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.redis; 2 | 3 | public interface RedisKeyExpirationHandler { 4 | 5 | boolean match(String key); 6 | 7 | void handle(String key); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/redis/RedisKeyExpirationHandlerManager.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.redis; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Objects; 12 | 13 | import javax.annotation.PostConstruct; 14 | 15 | @Component 16 | public class RedisKeyExpirationHandlerManager implements ApplicationContextAware { 17 | 18 | private ApplicationContext applicationContext; 19 | private final List handlers = new ArrayList<>(); 20 | 21 | @Override 22 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 23 | this.applicationContext = applicationContext; 24 | } 25 | 26 | @PostConstruct 27 | private void init(){ 28 | final Map beans = applicationContext.getBeansOfType( 29 | RedisKeyExpirationHandler.class); 30 | if (beans != null) { 31 | for (RedisKeyExpirationHandler handler : beans.values()) { 32 | Objects.requireNonNull(handler); 33 | handlers.add(handler); 34 | } 35 | } 36 | } 37 | 38 | public List getHandlers() { 39 | return handlers; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/redis/RedisKeyExpirationListener.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.redis; 2 | 3 | import org.springframework.data.redis.connection.Message; 4 | import org.springframework.data.redis.listener.KeyExpirationEventMessageListener; 5 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; 6 | 7 | import java.util.List; 8 | 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { 13 | 14 | private final RedisKeyExpirationHandlerManager redisKeyExpirationHandlerManager; 15 | 16 | public RedisKeyExpirationListener( 17 | RedisMessageListenerContainer listenerContainer, 18 | RedisKeyExpirationHandlerManager redisKeyExpirationHandlerManager) { 19 | super(listenerContainer); 20 | this.redisKeyExpirationHandlerManager = redisKeyExpirationHandlerManager; 21 | } 22 | 23 | @Override 24 | public void onMessage(Message message, byte[] pattern) { 25 | try { 26 | log.debug("==> Redis Message:{}", message); 27 | 28 | final String key = new String(message.getBody()); 29 | final List handlers = redisKeyExpirationHandlerManager.getHandlers(); 30 | for (RedisKeyExpirationHandler handler : handlers) { 31 | if (handler.match(key)) { 32 | handler.handle(key); 33 | } 34 | } 35 | } catch (Exception exception) { 36 | log.error(String.format("Failed to handle expiration key:%s", message), exception); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/security/JWTAccessToken.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.security; 2 | 3 | import org.springframework.security.core.Authentication; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.userdetails.UserDetailsService; 6 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; 7 | import org.springframework.security.oauth2.common.OAuth2AccessToken; 8 | import org.springframework.security.oauth2.provider.OAuth2Authentication; 9 | import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; 10 | import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter; 11 | import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * JWT访问令牌 18 | *

19 | * JWT令牌的结构为三部分组成:[令牌头(Header)].[负载信息(Payload)].[签名(Signature)] 20 | * 令牌头:定义了令牌的元数据,如令牌采用的签名算法,默认为HMAC SHA256算法 21 | * 负载信息:由签发者自定义的数据,一般会包括过期时间(Expire)、授权范围(Authority)、令牌ID编号(JTI)等 22 | * 签名:签名是使用私钥和头部指定的算法,前两部分进行的数字签名,防止数据被篡改。 23 | * 以上,令牌头和负载均为JSON结构,进行Base64URLEncode之后进行签名,然后用“.”连接,构成令牌报文 24 | *

25 | * Spring Security OAuth2的{@link JwtAccessTokenConverter}提供了令牌的基础结构(令牌头、部分负载,如过期时间、JTI)的转换实现 26 | * 继承此类,在加入自己定义的负载信息即可使用。一般来说负载中至少要告知服务端当前用户是谁,但又不应存放过多信息导致HTTP Header过大,尤其不应存放敏感信息。 27 | * 28 | * @author icyfenix@gmail.com 29 | * @date 2020/3/9 9:46 30 | */ 31 | public class JWTAccessToken extends JwtAccessTokenConverter { 32 | 33 | public JWTAccessToken(UserDetailsService userDetailsService) { 34 | // 设置从资源请求中带上来的JWT令牌转换回安全上下文中的用户信息的查询服务 35 | // 如果不设置该服务,则从JWT令牌获得的Principal就只有一个用户名(令牌中确实就只存了用户名) 36 | // 将用户用户信息查询服务提供给默认的令牌转换器,使得转换令牌时自动根据用户名还原出完整的用户对象 37 | // 这方便了后面编码(可以在直接获得登陆用户信息),但也稳定地为每次请求增加了一次(从数据库/缓存)查询,自行取舍 38 | DefaultUserAuthenticationConverter converter = new DefaultUserAuthenticationConverter(); 39 | converter.setUserDetailsService(userDetailsService); 40 | ((DefaultAccessTokenConverter)getAccessTokenConverter()).setUserTokenConverter(converter); 41 | } 42 | 43 | /** 44 | * 增强令牌 45 | * 增强主要就是在令牌的负载中加入额外的信息 46 | */ 47 | @Override 48 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { 49 | Authentication user = authentication.getUserAuthentication(); 50 | if (user != null) { 51 | String[] authorities = user.getAuthorities().stream().map(GrantedAuthority::getAuthority).toArray(String[]::new); 52 | Map payLoad = new HashMap<>(); 53 | // Spring Security OAuth的JWT令牌默认实现中就加入了一个“user_name”的项存储了当前用户名 54 | // 这里主要是出于演示Payload的用途,以及方便客户端获取(否则客户端要从令牌中解码Base64来获取),设置了一个“username”,两者的内容是一致的 55 | payLoad.put("username", user.getName()); 56 | payLoad.put("authorities", authorities); 57 | payLoad.put("iss", "kaybee@gmail.com"); 58 | payLoad.put("sub", "mmall"); 59 | 60 | ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(payLoad); 61 | } 62 | return super.enhance(accessToken, authentication); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/security/JWTAccessTokenService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.security; 2 | 3 | import org.springframework.security.authentication.AuthenticationManager; 4 | import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager; 5 | import org.springframework.security.oauth2.provider.token.DefaultTokenServices; 6 | import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.Optional; 10 | 11 | /** 12 | * JWT访问令牌服务 13 | *

14 | * 在此服务中提供了令牌如何存储、携带哪些信息、如何签名、持续多长时间等相关内容的定义 15 | * 令牌服务应当会被授权服务器注册验证Endpoint时候调用到 16 | * 17 | * @author icyfenix@gmail.com 18 | * @date 2020/3/8 11:07 19 | **/ 20 | @Component 21 | public class JWTAccessTokenService extends DefaultTokenServices { 22 | /** 23 | * 构建JWT令牌,并进行默认的配置 24 | */ 25 | public JWTAccessTokenService(JWTAccessToken token, 26 | OAuthClientDetailsService clientService, 27 | Optional authenticationManager) { 28 | // 设置令牌的持久化容器 29 | // 令牌持久化有多种方式,单节点服务可以存放在Session中,集群可以存放在Redis中 30 | // 而JWT是后端无状态、前端存储的解决方案,Token的存储由前端完成 31 | setTokenStore(new JwtTokenStore(token)); 32 | // 令牌支持的客户端详情 33 | setClientDetailsService(clientService); 34 | // 设置验证管理器,在鉴权的时候需要用到 35 | setAuthenticationManager(authenticationManager.orElseGet(()->{ 36 | OAuth2AuthenticationManager manager = new OAuth2AuthenticationManager(); 37 | manager.setClientDetailsService(clientService); 38 | manager.setTokenServices(this); 39 | return manager; 40 | })); 41 | // 定义令牌的额外负载 42 | setTokenEnhancer(token); 43 | // access_token有效期,单位:秒,默认12小时 44 | setAccessTokenValiditySeconds(60 * 60 * 3); 45 | // refresh_token的有效期,单位:秒, 默认30天 46 | // 这决定了客户端选择“记住当前登录用户”的最长时效,即失效前都不用再请求用户赋权了 47 | setRefreshTokenValiditySeconds(60 * 60 * 24 * 15); 48 | // 是否支持refresh_token,默认false 49 | setSupportRefreshToken(true); 50 | // 是否复用refresh_token,默认为true 51 | // 如果为false,则每次请求刷新都会删除旧的refresh_token,创建新的refresh_token 52 | setReuseRefreshToken(true); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/security/OAuthClientDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.security; 2 | 3 | import com.github.kay.mmall.domain.security.GrantType; 4 | import com.github.kay.mmall.domain.security.Scope; 5 | 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder; 8 | import org.springframework.security.oauth2.provider.ClientDetails; 9 | import org.springframework.security.oauth2.provider.ClientDetailsService; 10 | import org.springframework.security.oauth2.provider.ClientRegistrationException; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | import javax.annotation.PostConstruct; 17 | 18 | /** 19 | * OAuth2客户端类型定义 20 | *

21 | * OAuth2支持四种授权模式,这里仅定义了密码模式(Resource Owner Password Credentials Grant)一种 22 | * OAuth2作为开放的(面向不同服务提供商)授权协议,要求用户提供明文用户名、密码的这种“密码模式”并不常用 23 | * 而这里可以采用是因为前端(BookStore FrontEnd)与后端服务是属于同一个服务提供者的,实质上不存在密码会不会被第三方保存的敏感问题. 24 | * 25 | * 如果永远只考虑单体架构、单一服务提供者,则并无引入OAuth的必要,Spring Security的表单认证就能很良好、便捷地解决认证和授权的问题 26 | * 这里使用密码模式来解决,是为了下一阶段演示微服务化后,服务之间鉴权作准备,以便后续扩展以及对比。 27 | * 28 | * @author icyfenix@gmail.com 29 | * @date 2020/3/7 19:45 30 | **/ 31 | @Component 32 | public class OAuthClientDetailsService implements ClientDetailsService { 33 | 34 | /** 35 | * 客户端模型 36 | */ 37 | private static class Client { 38 | /** 39 | * 客户端ID 40 | */ 41 | String clientId; 42 | 43 | /** 44 | * 客户端密钥 45 | * 在OAuth2协议中,ID是可以公开的,密钥应当保密,密钥用以证明当前申请授权的客户端是未被冒充的 46 | */ 47 | String clientSecret; 48 | 49 | /** 50 | * 授权类型 51 | * 前端API使用密码授权模式,微服务使用客户端授权模式 52 | */ 53 | String[] grantTypes; 54 | 55 | /** 56 | * 授权范围 57 | */ 58 | String[] scopes; 59 | 60 | Client(String clientId, String clientSecret, String[] grantTypes, String[] scopes) { 61 | this.clientId = clientId; 62 | this.clientSecret = clientSecret; 63 | this.grantTypes = grantTypes; 64 | this.scopes = scopes; 65 | } 66 | } 67 | 68 | /** 69 | * 客户端列表 70 | *

71 | * 此场景中微服务一种有JavaScript前端、Account微服务、Warehouse微服务、Payment微服务四种客户端 72 | * 如果正式使用,这部分信息应该做成可以配置的,以便快速增加微服务的类型。clientSecret也不应该出现在源码中,应由外部配置传入 73 | */ 74 | private static final List clients = Arrays.asList( 75 | new Client("mmall_frontend", "mmall_secret", new String[]{GrantType.PASSWORD.getValue(), 76 | GrantType.REFRESH_TOKEN.name()}, 77 | new String[]{Scope.BROWSER}), 78 | new Client("account", "account_secret", new String[]{GrantType.CLIENT_CREDENTIALS.getValue()}, 79 | new String[]{Scope.SERVICE}), 80 | new Client("warehouse", "warehouse_secret", new String[]{GrantType.CLIENT_CREDENTIALS.getValue()}, 81 | new String[]{Scope.SERVICE}), 82 | new Client("payment", "payment_secret", new String[]{GrantType.CLIENT_CREDENTIALS.getValue()}, 83 | new String[]{Scope.SERVICE}), 84 | new Client("security", "security_secret", new String[]{GrantType.CLIENT_CREDENTIALS.getValue()}, 85 | new String[]{Scope.SERVICE}) 86 | ); 87 | 88 | private final PasswordEncoder passwordEncoder; 89 | 90 | private ClientDetailsService clientDetailsService; 91 | 92 | public OAuthClientDetailsService(PasswordEncoder passwordEncoder) { 93 | this.passwordEncoder = passwordEncoder; 94 | } 95 | 96 | /** 97 | * 构造密码授权模式 98 | * 99 | * 授权Endpoint示例: 100 | * /oauth/token?grant_type=password & username=#USER# & password=#PWD# & client_id=#CLIENT_ID# & client_secret=#CLIENT_SECRET# 101 | * 刷新令牌Endpoint示例: 102 | * /oauth/token?grant_type=refresh_token & refresh_token=#REFRESH_TOKEN# & client_id=#CLIENT_ID# & client_secret=#CLIENT_SECRET# 103 | */ 104 | @PostConstruct 105 | public void init() throws Exception { 106 | InMemoryClientDetailsServiceBuilder builder = new InMemoryClientDetailsServiceBuilder(); 107 | // 提供客户端ID和密钥,并指定该客户端支持密码授权、刷新令牌两种访问类型 108 | clients.forEach(client -> builder.withClient(client.clientId) 109 | .secret(passwordEncoder.encode(client.clientSecret)) 110 | .scopes(client.scopes) 111 | .authorizedGrantTypes(client.grantTypes)); 112 | clientDetailsService = builder.build(); 113 | } 114 | 115 | /** 116 | * 外部根据客户端ID查询验证方式 117 | */ 118 | @Override 119 | public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { 120 | return clientDetailsService.loadClientByClientId(clientId); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/security/RSA256PublicJWTAccessToken.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.security; 2 | 3 | import org.springframework.core.io.ClassPathResource; 4 | import org.springframework.core.io.Resource; 5 | import org.springframework.security.core.userdetails.UserDetailsService; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.util.FileCopyUtils; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * 使用RSA SHA256公钥解密的JWT令牌 13 | * 可验证收到的请求是否通过在Security工程中的私钥加密的。 14 | * Spring Security也提供了jks-uri的形式进行自动验证,为了便于对比,就没有节省这几行代码 15 | * 16 | * @author icyfenix@gmail.com 17 | * @date 2020/7/17 15:15 18 | **/ 19 | @Service 20 | public class RSA256PublicJWTAccessToken extends JWTAccessToken { 21 | 22 | RSA256PublicJWTAccessToken(UserDetailsService userDetailsService) throws IOException { 23 | super(userDetailsService); 24 | Resource resource = new ClassPathResource("public.cert"); 25 | String publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); 26 | setVerifierKey(publicKey); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /mmall-lib-infrastructure/src/main/java/com/github/kay/mmall/infrasucture/security/ResourceServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall.infrasucture.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.access.annotation.Secured; 8 | import org.springframework.security.access.prepost.PostAuthorize; 9 | import org.springframework.security.access.prepost.PreAuthorize; 10 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; 14 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 15 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 16 | import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; 17 | 18 | import javax.annotation.security.RolesAllowed; 19 | 20 | /** 21 | * 资源服务器配置 22 | *

23 | * 配置资源服务访问权限,主流有两种方式: 24 | * 一是在这里通过{@link HttpSecurity}的antMatchers方法集中配置 25 | * 二是启用全局方法级安全支持{@link EnableGlobalMethodSecurity} 在各个资源的访问方法前,通过注解来逐个配置,使用的注解包括有: 26 | * JSR 250标准注解{@link RolesAllowed},可完整替代Spring的{@link Secured}功能 27 | * 以及可以使用EL表达式的Spring注解{@link PreAuthorize}、{@link PostAuthorize} 28 | * 29 | * @author icyfenix@gmail.com 30 | * @date 2020/3/7 19:43 31 | **/ 32 | @Configuration 33 | @EnableResourceServer 34 | public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { 35 | 36 | @Autowired 37 | private JWTAccessTokenService tokenService; 38 | 39 | /** 40 | * 配置HTTP访问相关的安全选项 41 | */ 42 | @Override 43 | public void configure(HttpSecurity http) throws Exception { 44 | // 基于JWT来绑定用户状态,所以服务端可以是无状态的 45 | http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 46 | // 关闭CSRF(Cross Site Request Forgery)跨站请求伪造的防御 47 | // 因为需要状态存储CSRF Token才能开启该功能 48 | http.csrf().disable(); 49 | // 关闭HTTP Header中的X-Frame-Options选项,允许页面在frame标签中打开 50 | http.headers().frameOptions().disable(); 51 | // 设置服务的安全规则 52 | http.authorizeRequests().antMatchers("/oauth/**").permitAll(); 53 | } 54 | 55 | @Override 56 | public void configure(ResourceServerSecurityConfigurer resources) { 57 | resources.tokenServices(tokenService); 58 | } 59 | 60 | @Bean 61 | @ConfigurationProperties(prefix = "security.oauth2.client") 62 | public ClientCredentialsResourceDetails clientCredentialsResourceDetails() { 63 | return new ClientCredentialsResourceDetails(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mmall-platform-configuration/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:12-alpine 2 | 3 | MAINTAINER kaybee 4 | 5 | ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ 6 | JAVA_OPTS="" \ 7 | PORT=8888 \ 8 | PROFILES="native" 9 | 10 | ADD /build/libs/*.jar /mmall-config.jar 11 | 12 | RUN apk update && apk add curl && rm -rf /var/cache/apk/* 13 | 14 | HEALTHCHECK --interval=5s --timeout=30s CMD curl -f http://localhost:$PORT/actuator/health || exit 1 15 | 16 | ENTRYPOINT ["sh", "-c", "java $JAVA_OPT -jar /mmall-config.jar --spring.profiles.active=$PROFILES"] 17 | 18 | EXPOSE $PORT -------------------------------------------------------------------------------- /mmall-platform-configuration/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' 4 | id 'io.spring.dependency-management' 5 | } 6 | 7 | 8 | dependencies { 9 | implementation 'org.springframework.cloud:spring-cloud-config-server' 10 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 11 | } 12 | 13 | -------------------------------------------------------------------------------- /mmall-platform-configuration/src/main/java/com/github/kay/mmall/ConfigurationApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall; 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 ConfigurationApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(ConfigurationApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mmall-platform-configuration/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | server: 5 | native: 6 | search-locations: classpath:/configurations 7 | profiles: 8 | active: native 9 | security: 10 | user: 11 | password: ${CONFIG_PASS:dev} 12 | 13 | management: 14 | endpoints: 15 | web: 16 | exposure: 17 | include: "*" 18 | 19 | server: 20 | port: ${PORT:8888} 21 | -------------------------------------------------------------------------------- /mmall-platform-configuration/src/main/resources/configurations/account.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | serviceUrl: 4 | defaultZone: http://${REGISTRY_HOST:localhost}:${REGISTRY_PORT:8761}/eureka/ 5 | 6 | spring: 7 | cache: 8 | type: redis 9 | redis: 10 | cache-null-values: false 11 | time-to-live: PT5M 12 | datasource: 13 | schema: "classpath:db/schema.sql" 14 | data: "classpath:db/data.sql" 15 | sql-script-encoding: UTF-8 16 | url: ${MYSQL_URL:jdbc:mysql://localhost/mmall?useUnicode=true&characterEncoding=utf-8} 17 | username: "root" 18 | password: "root1234" 19 | initialization-mode: always 20 | jpa: 21 | show-sql: true 22 | hibernate: 23 | ddl-auto: none 24 | open-in-view: true 25 | generate-ddl: false 26 | database: mysql 27 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 28 | resources: 29 | chain: 30 | compressed: true 31 | cache: true 32 | cache: 33 | period: 86400 34 | redis: 35 | host: ${REDIS_HOST:localhost} 36 | port: ${REDIS_PORT:6379} 37 | 38 | logging: 39 | pattern: 40 | console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}" 41 | level: 42 | root: INFO 43 | com.github.kay: DEBUG 44 | 45 | security: 46 | oauth2: 47 | client: 48 | # OAuth的ClientID和ClientSecret是写在OAuthClientDetailsService中的 49 | # 实际生产中要考虑好如何获取验证服务器的Endpoint、动态增加微服务客户端、如何分发客户端密钥等问题,而在演示工程中并不关注这些 50 | clientId: account 51 | clientSecret: account_secret 52 | accessTokenUri: http://${AUTH_HOST:localhost}:${AUTH_PORT:8301}/oauth/token 53 | grant-type: client_credentials 54 | scope: SERVICE 55 | resource: 56 | userInfoUri: BUGFIX 57 | 58 | server: 59 | port: ${PORT:8401} 60 | 61 | mmall: 62 | redisson-config: "redisson-config.yml" 63 | -------------------------------------------------------------------------------- /mmall-platform-configuration/src/main/resources/configurations/gateway.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | serviceUrl: 4 | defaultZone: http://${REGISTRY_HOST:localhost}:${REGISTRY_PORT:8761}/eureka/ 5 | 6 | hystrix: 7 | command: 8 | default: 9 | execution: 10 | isolation: 11 | thread: 12 | timeoutInMilliseconds: 20000 13 | 14 | ribbon: 15 | ReadTimeout: 20000 16 | ConnectTimeout: 20000 17 | 18 | zuul: 19 | ignoredServices: '*' 20 | host: 21 | connect-timeout-millis: 20000 22 | socket-timeout-millis: 20000 23 | 24 | routes: 25 | account: 26 | path: /restful/accounts/** 27 | serviceId: account 28 | stripPrefix: false 29 | sensitiveHeaders: "*" 30 | 31 | payment: 32 | path: /restful/pay/** 33 | serviceId: payment 34 | stripPrefix: false 35 | sensitiveHeaders: "*" 36 | 37 | settlement: 38 | path: /restful/settlements/** 39 | serviceId: payment 40 | stripPrefix: false 41 | sensitiveHeaders: "*" 42 | 43 | advertisements: 44 | path: /restful/advertisements/** 45 | serviceId: warehouse 46 | stripPrefix: false 47 | sensitiveHeaders: "*" 48 | 49 | products: 50 | path: /restful/products/** 51 | serviceId: warehouse 52 | stripPrefix: false 53 | sensitiveHeaders: "*" 54 | 55 | security: 56 | path: /oauth/** 57 | serviceId: security 58 | stripPrefix: false 59 | sensitiveHeaders: "*" 60 | 61 | server: 62 | port: ${PORT:8080} 63 | 64 | logging: 65 | level: 66 | root: INFO 67 | -------------------------------------------------------------------------------- /mmall-platform-configuration/src/main/resources/configurations/payment.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | serviceUrl: 4 | defaultZone: http://${REGISTRY_HOST:localhost}:${REGISTRY_PORT:8761}/eureka/ 5 | 6 | spring: 7 | cache: 8 | type: redis 9 | redis: 10 | cache-null-values: false 11 | time-to-live: PT5M 12 | datasource: 13 | schema: "classpath:db/schema.sql" 14 | data: "classpath:db/data.sql" 15 | sql-script-encoding: UTF-8 16 | driverClassName: "com.mysql.cj.jdbc.Driver" 17 | url: ${MYSQL_URL:jdbc:mysql://localhost/mmall?useUnicode=true&characterEncoding=utf-8} 18 | username: "root" 19 | password: "root1234" 20 | initialization-mode: always 21 | jpa: 22 | show-sql: true 23 | hibernate: 24 | ddl-auto: none 25 | open-in-view: true 26 | generate-ddl: false 27 | database: mysql 28 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 29 | resources: 30 | chain: 31 | compressed: true 32 | cache: true 33 | cache: 34 | period: 86400 35 | redis: 36 | host: ${REDIS_HOST:localhost} 37 | port: ${REDIS_PORT:6379} 38 | 39 | logging: 40 | pattern: 41 | console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}" 42 | level: 43 | root: INFO 44 | com.github.kay: DEBUG 45 | 46 | security: 47 | oauth2: 48 | client: 49 | # OAuth的ClientID和ClientSecret是写在OAuthClientDetailsService中的 50 | # 实际生产中要考虑好如何获取验证服务器的Endpoint、动态增加微服务客户端、如何分发客户端密钥等问题,而在演示工程中并不关注这些 51 | clientId: account 52 | clientSecret: account_secret 53 | accessTokenUri: http://${AUTH_HOST:localhost}:${AUTH_PORT:8301}/oauth/token 54 | grant-type: client_credentials 55 | scope: SERVICE 56 | resource: 57 | userInfoUri: BUGFIX 58 | 59 | server: 60 | port: ${PORT:8601} 61 | 62 | mmall: 63 | redisson-config: "redisson-config.yml" 64 | -------------------------------------------------------------------------------- /mmall-platform-configuration/src/main/resources/configurations/registry.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${PORT:8761} 3 | -------------------------------------------------------------------------------- /mmall-platform-configuration/src/main/resources/configurations/security.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | serviceUrl: 4 | defaultZone: http://${REGISTRY_HOST:localhost}:${REGISTRY_PORT:8761}/eureka/ 5 | 6 | server: 7 | port: ${PORT:8301} 8 | 9 | security: 10 | oauth2: 11 | client: 12 | # OAuth的ClientID和ClientSecret是写在OAuthClientDetailsService中的 13 | # 实际生产中要考虑好如何获取验证服务器的Endpoint、动态增加微服务客户端、如何分发客户端密钥等问题,而在演示工程中并不关注这些 14 | clientId: security 15 | clientSecret: security_secret 16 | accessTokenUri: http://localhost:${PORT:8301}/oauth/token 17 | grant-type: client_credentials 18 | scope: SERVICE 19 | 20 | logging: 21 | pattern: 22 | console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}" 23 | level: 24 | root: INFO 25 | 26 | spring: 27 | cache: 28 | type: none 29 | 30 | mmall: 31 | redisson: 32 | enabled: false -------------------------------------------------------------------------------- /mmall-platform-configuration/src/main/resources/configurations/warehouse.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | serviceUrl: 4 | defaultZone: http://${REGISTRY_HOST:localhost}:${REGISTRY_PORT:8761}/eureka/ 5 | 6 | spring: 7 | cache: 8 | type: redis 9 | redis: 10 | cache-null-values: false 11 | time-to-live: PT5M 12 | datasource: 13 | schema: "classpath:db/schema.sql" 14 | data: "classpath:db/data.sql" 15 | sql-script-encoding: UTF-8 16 | url: ${MYSQL_URL:jdbc:mysql://localhost/mmall?useUnicode=true&characterEncoding=utf-8} 17 | username: "root" 18 | password: "root1234" 19 | initialization-mode: always 20 | jpa: 21 | show-sql: true 22 | hibernate: 23 | ddl-auto: none 24 | open-in-view: true 25 | generate-ddl: false 26 | database: mysql 27 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 28 | resources: 29 | chain: 30 | compressed: true 31 | cache: true 32 | cache: 33 | period: 86400 34 | redis: 35 | host: ${REDIS_HOST:localhost} 36 | port: ${REDIS_PORT:6379} 37 | 38 | logging: 39 | pattern: 40 | console: "%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr(-){faint} %clr([%t]){faint} %clr(%-40logger{39}){cyan}[%line]%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}" 41 | level: 42 | root: INFO 43 | com.github.kay: DEBUG 44 | 45 | security: 46 | oauth2: 47 | client: 48 | # OAuth的ClientID和ClientSecret是写在OAuthClientDetailsService中的 49 | # 实际生产中要考虑好如何获取验证服务器的Endpoint、动态增加微服务客户端、如何分发客户端密钥等问题,而在演示工程中并不关注这些 50 | clientId: account 51 | clientSecret: account_secret 52 | accessTokenUri: http://${AUTH_HOST:localhost}:${AUTH_PORT:8301}/oauth/token 53 | grant-type: client_credentials 54 | scope: SERVICE 55 | resource: 56 | userInfoUri: BUGFIX 57 | 58 | server: 59 | port: ${PORT:8501} 60 | 61 | mmall: 62 | redisson-config: "redisson-config.yml" 63 | -------------------------------------------------------------------------------- /mmall-platform-gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:12-alpine 2 | 3 | MAINTAINER kaybee 4 | 5 | ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ 6 | JAVA_OPTS="" \ 7 | PORT=8080 \ 8 | CONFIG_PORT=8888 \ 9 | PROFILES="default" 10 | 11 | ADD /build/libs/*.jar /mmall-gateway.jar 12 | 13 | ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /mmall-gateway.jar --spring.profiles.active=$PROFILES"] 14 | 15 | EXPOSE $PORT -------------------------------------------------------------------------------- /mmall-platform-gateway/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' 4 | id 'io.spring.dependency-management' 5 | } 6 | 7 | 8 | dependencies { 9 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul' 10 | implementation 'org.springframework.cloud:spring-cloud-starter-config' 11 | implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' 12 | } 13 | 14 | -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/java/com/github/kay/mmall/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.kay.mmall; 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 | 8 | @EnableZuulProxy 9 | @EnableDiscoveryClient 10 | @SpringBootApplication 11 | public class GatewayApplication { 12 | public static void main(String[] args) { 13 | SpringApplication.run(GatewayApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: gateway 4 | cloud: 5 | config: 6 | uri: http://${CONFIG_HOST:localhost}:${CONFIG_PORT:8888} 7 | fail-fast: true 8 | password: ${CONFIG_PASS:dev} 9 | username: user 10 | -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | Mmall Store

-------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/board/gitalk.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 20 | 21 | -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/carousel/ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/carousel/ai.png -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/carousel/banner1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/carousel/banner1.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/carousel/banner2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/carousel/banner2.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/carousel/banner3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/carousel/banner3.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/carousel/banner4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/carousel/banner4.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/carousel/banner5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/carousel/banner5.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/carousel/fenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/carousel/fenix.png -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/carousel/fenix2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/carousel/fenix2.png -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/carousel/jvm3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/carousel/jvm3.png -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/cover/product0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/cover/product0.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/cover/product1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/cover/product1.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/cover/product2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/cover/product2.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/cover/product3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/cover/product3.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/cover/product4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/cover/product4.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/cover/product5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/cover/product5.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/cover/product6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/cover/product6.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/desc/OSGi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/desc/OSGi.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/desc/ai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/desc/ai.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/desc/fenix.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/desc/fenix.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/desc/jvm2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/desc/jvm2.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/desc/jvm3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/desc/jvm3.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/desc/jvms.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/desc/jvms.jpg -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/fonts/element-icons.535877f.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/fonts/element-icons.535877f.woff -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/fonts/element-icons.732389d.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/fonts/element-icons.732389d.ttf -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/img/bg2.ef8085e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/img/bg2.ef8085e.png -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/img/cc-logo.3653e37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/img/cc-logo.3653e37.png -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/img/logo-color.5500ec5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/img/logo-color.5500ec5.png -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/img/mmall.4971f42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiuKay/mmall-java/681436fe8768399ad07ebece2993cb996442bc59/mmall-platform-gateway/src/main/resources/static/static/img/mmall.4971f42.png -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/js/0.c178f427b3d08777c70f.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([0],{"7WGw":function(t,e,s){"use strict";var a={name:"PayStepIndicator",props:{step:Number}},i={render:function(){var t=this.$createElement,e=this._self._c||t;return e("el-card",{staticClass:"box-card",staticStyle:{"margin-top":"20px"}},[e("div",{staticClass:"header",attrs:{slot:"header"},slot:"header"},[e("span",[this._v("购买流程")])]),this._v(" "),e("div",{staticClass:"content",staticStyle:{padding:"0 100px"}},[e("el-steps",{attrs:{active:this.step,"align-center":""}},[e("el-step",{attrs:{title:"我的购物车",description:"在购物车中确认每件商品的价格、数量"}}),this._v(" "),e("el-step",{attrs:{title:"我的结算单",description:"在结算单中确认配送地址、支付信息"}}),this._v(" "),e("el-step",{attrs:{title:"支付",description:"通过微信、支付宝完成付款,等待收货"}})],1)],1)])},staticRenderFns:[]};var n=s("VU/8")(a,i,!1,function(t){s("ne/a")},"data-v-9c9995a8",null);e.a=n.exports},"ne/a":function(t,e){}}); -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/js/3.de05a3e78470a7ab50f1.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([3],{"1RDs":function(e,t,a){e.exports=a.p+"static/img/mmall.4971f42.png"},CAsR:function(e,t){},P7ry:function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=a("Xxa5"),n=a.n(r),o=a("exGp"),s=a.n(o),i=a("Dd8w"),l=a.n(i),c=a("NYxO"),u=a("gyMJ"),p={render:function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",[r("img",{staticClass:"logo",attrs:{src:a("1RDs")}}),e._v(" "),r("span",{staticClass:"title"},[e._v("Mmall Store")]),e._v(" "),r("el-form",{ref:"login-form",staticClass:"login-form",attrs:{model:e.authorization,rules:e.rules}},[r("el-form-item",{attrs:{prop:"name"}},[r("el-input",{attrs:{placeholder:"请输入用户"},model:{value:e.authorization.name,callback:function(t){e.$set(e.authorization,"name",t)},expression:"authorization.name"}},[r("template",{slot:"prepend"},[r("i",{staticClass:"el-icon-user"})])],2)],1),e._v(" "),r("el-form-item",{attrs:{prop:"password"}},[r("el-input",{attrs:{placeholder:"请输入密码","show-password":""},model:{value:e.authorization.password,callback:function(t){e.$set(e.authorization,"password",t)},expression:"authorization.password"}},[r("template",{slot:"prepend"},[r("i",{staticClass:"el-icon-unlock"})])],2)],1),e._v(" "),r("el-select",{staticStyle:{width:"370px"},attrs:{placeholder:"请选择语言"},model:{value:e.authorization.language,callback:function(t){e.$set(e.authorization,"language",t)},expression:"authorization.language"}},[r("el-option",{attrs:{label:" 中文",value:"zhCN"}}),e._v(" "),r("el-option",{attrs:{label:" 英文(无效,国际化预留)",value:"enUS"}}),e._v(" "),r("template",{slot:"prefix"},[r("div",{staticClass:"select-prefix"},[r("i",{staticClass:"el-icon-map-location"})])])],2),e._v(" "),r("template",{slot:"prepend"},[r("i",{staticClass:"el-icon-map-location"})]),e._v(" "),r("div",{staticClass:"actions"},[r("el-checkbox",{staticClass:"check",model:{value:e.authorization.rememberMe,callback:function(t){e.$set(e.authorization,"rememberMe",t)},expression:"authorization.rememberMe"}},[e._v("\n 自动登录\n ")]),e._v(" "),r("el-button",{staticStyle:{float:"right",display:"inline-block",padding:"0 10px 0 0"},attrs:{type:"text"},on:{click:function(t){return e.$emit("changeMode")}}},[e._v("注册新用户\n ")]),e._v(" "),r("el-button",{staticStyle:{width:"100%",display:"block",margin:"50px 0 0 0"},attrs:{type:"primary"},on:{click:e.login}},[e._v("登录")])],1),e._v(" "),r("hr"),e._v(" "),r("div",{staticStyle:{"text-align":"center"}},[e._v("\n 登录代表你已同意\n "),r("el-tooltip",{attrs:{effect:"dark",content:"演示用途,并没有写",placement:"bottom"}},[r("el-button",{attrs:{type:"text"}},[e._v("用户协议")])],1),e._v("\n 和\n "),r("el-tooltip",{attrs:{effect:"dark",content:"也是没有写",placement:"bottom"}},[r("el-button",{staticStyle:{"margin-left":"0"},attrs:{type:"text"}},[e._v("隐私政策")])],1)],1)],2)],1)},staticRenderFns:[]};var m=a("VU/8")({name:"LoginForm",data:function(){return{authorization:{name:"",password:"",language:"zhCN",rememberMe:!1},rules:{name:[{required:!0,message:"请输入用户名称",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]}}},methods:{login:function(){var e=this;this.$refs["login-form"].validate(function(t){if(!t)return!1;e.$emit("login",e.authorization)})}}},p,!1,function(e){a("ewBM")},"data-v-615c3afa",null).exports,d={name:"RegistrationForm",data:function(){return{account:{username:"",email:"",password:"",telephone:""},rules:{username:[{required:!0,message:"请填写用户名",trigger:"blur"}],name:[{required:!0,message:"请填写真实姓名",trigger:"blur"}],password:[{required:!0,message:"请填写密码",trigger:"blur"}],email:[{required:!0,message:"请填写邮箱",trigger:"blur"},{type:"email",message:"不符合邮箱格式",trigger:"blur"}],telephone:[{required:!0,message:"请填写手机",trigger:"blur"}]}}},methods:l()({},Object(c.d)("user",["setupSession"]),{registerAccount:function(){var e=this;this.$refs.account_form.validate(function(t){if(!t)return!1;e.submitRegistration()})},submitRegistration:function(){var e=this;return s()(n.a.mark(function t(){var a,r;return n.a.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,u.a.account.registerAccount(e.account);case 3:if(a=t.sent,(r=a.data).code!==u.a.constants.REMOTE_OPERATION_SUCCESS){t.next=14;break}return t.next=8,u.a.auth.login(e.account.username,e.account.password);case 8:return(r=t.sent.data).rememberMe=!1,r.language="zhCN",e.setupSession(r),e.$router.push("/"),t.abrupt("return");case 14:t.next=20;break;case 16:t.prev=16,t.t0=t.catch(0),console.error(t.t0),e.$alert(t.t0.message,"出现异常");case 20:case"end":return t.stop()}},t,e,[[0,16]])}))()}})},g={render:function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",[r("img",{staticClass:"logo",attrs:{src:a("lgL1")}}),e._v(" "),r("span",{staticClass:"title"},[e._v("新用户注册")]),e._v(" "),r("el-form",{ref:"account_form",staticClass:"account_form",attrs:{model:e.account,rules:e.rules,"label-position":"left"}},[r("el-form-item",{attrs:{prop:"user"}},[r("el-input",{attrs:{placeholder:"请输入用户名"},model:{value:e.account.username,callback:function(t){e.$set(e.account,"username",t)},expression:"account.username"}},[r("template",{slot:"prepend"},[r("i",{staticClass:"el-icon-user"})])],2)],1),e._v(" "),r("el-form-item",{attrs:{prop:"password"}},[r("el-input",{attrs:{placeholder:"请输入密码","show-password":""},model:{value:e.account.password,callback:function(t){e.$set(e.account,"password",t)},expression:"account.password"}},[r("template",{slot:"prepend"},[r("i",{staticClass:"el-icon-unlock"})])],2)],1),e._v(" "),r("el-form-item",{attrs:{prop:"name"}},[r("el-input",{attrs:{placeholder:"请输入真实姓名"},model:{value:e.account.name,callback:function(t){e.$set(e.account,"name",t)},expression:"account.name"}},[r("template",{slot:"prepend"},[r("i",{staticClass:"el-icon-user"})])],2)],1),e._v(" "),r("el-form-item",{attrs:{prop:"email"}},[r("el-input",{attrs:{placeholder:"请输入邮箱"},model:{value:e.account.email,callback:function(t){e.$set(e.account,"email",t)},expression:"account.email"}},[r("template",{slot:"prepend"},[r("i",{staticClass:"el-icon-receiving"})])],2)],1),e._v(" "),r("el-form-item",{attrs:{prop:"telephone"}},[r("el-input",{attrs:{placeholder:"请输入手机"},model:{value:e.account.telephone,callback:function(t){e.$set(e.account,"telephone",t)},expression:"account.telephone"}},[r("template",{slot:"prepend"},[r("i",{staticClass:"el-icon-phone-outline"})])],2)],1),e._v(" "),r("div",{staticClass:"actions"},[r("el-button",{staticClass:"action_button",attrs:{type:"primary"},on:{click:e.registerAccount}},[e._v("注册")]),e._v(" "),r("el-button",{staticClass:"action_button",on:{click:function(t){return e.$emit("changeMode")}}},[e._v("返回\n ")])],1)],1)],1)},staticRenderFns:[]};var f={name:"Login",components:{LoginForm:m,RegistrationForm:a("VU/8")(d,g,!1,function(e){a("CAsR")},"data-v-1c0696b0",null).exports},data:function(){return{registrationMode:!1}},computed:{nextPath:function(){return this.$route.query.redirect?this.$route.query.redirect:"/"}},methods:l()({},Object(c.d)("user",["setupSession"]),{login:function(e){var t=this;return s()(n.a.mark(function a(){var r,o;return n.a.wrap(function(a){for(;;)switch(a.prev=a.next){case 0:return a.prev=0,a.next=3,u.a.auth.login(e.name,e.password);case 3:r=a.sent,(o=r.data).rememberMe=e.rememberMe,o.language=e.language,t.setupSession(o),t.$router.push(t.nextPath),a.next=14;break;case 11:a.prev=11,a.t0=a.catch(0),t.$alert(a.t0.message,"出现异常");case 14:case"end":return a.stop()}},a,t,[[0,11]])}))()}})},v={render:function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("div",{staticClass:"bg"},[a("div",{staticClass:"dialog dialog-shadow"},[e.registrationMode?e._e():a("LoginForm",{on:{changeMode:function(t){e.registrationMode=!e.registrationMode},login:e.login}}),e._v(" "),e.registrationMode?a("RegistrationForm",{on:{changeMode:function(t){e.registrationMode=!e.registrationMode}}}):e._e()],1)])},staticRenderFns:[]};var h=a("VU/8")(f,v,!1,function(e){a("ZzDk")},"data-v-886325a0",null);t.default=h.exports},ZzDk:function(e,t){},ewBM:function(e,t){},lgL1:function(e,t,a){e.exports=a.p+"static/img/logo-color.5500ec5.png"}}); -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/js/5.c79741f8718eb55113ff.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([5],{DTIM:function(t,e){},GBxx:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var i=n("Xxa5"),r=n.n(i),a=n("exGp"),s=n.n(a),o=n("gyMJ"),c={name:"Carousel",data:function(){return{advertisements:[]}},created:function(){var t=this;return s()(r.a.mark(function e(){return r.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,o.a.warehouse.getAdvertisements();case 2:t.advertisements=e.sent.data;case 3:case"end":return e.stop()}},e,t)}))()},methods:{loadDetail:function(t){this.$notify({title:"提示",message:"这是一个预留接口,去往你想定制的页面",type:"success"})}}},u={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("el-carousel",{attrs:{interval:5e3,type:"card",height:"400px"}},t._l(t.advertisements,function(e){return n("el-carousel-item",{key:e.id},[n("el-image",{staticClass:"image",attrs:{src:e.image},on:{click:function(n){return t.loadDetail(e.productId)}}})],1)}),1)},staticRenderFns:[]};var l=n("VU/8")(c,u,!1,function(t){n("jv0A")},"data-v-5211e6ec",null).exports,d=n("Dd8w"),f=n.n(d),v=n("NYxO"),p={name:"Cabinet",data:function(){return{books:[]}},computed:f()({},Object(v.e)("user",["favorite","account"]),Object(v.e)("cart",["items"])),created:function(){var t=this;return s()(r.a.mark(function e(){return r.a.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,o.a.warehouse.getAllProducts();case 2:t.books=e.sent.data;case 3:case"end":return e.stop()}},e,t)}))()},methods:f()({},Object(v.d)("user",["addFavorite","removeFavorite"]),Object(v.d)("cart",["addCartItem","removeCartItem"]),Object(v.b)("cart",["setupSettlementBillWithDefaultValue"]),{isFavorite:function(t){return this.favorite.includes(t)},isInCart:function(t){return this.items.find(function(e){return e.id===t})},updateFavorite:function(t){this.isFavorite(t)?this.removeFavorite(t):this.addFavorite(t),this.$notify({title:"成功",message:"恭喜你,已成功更新收藏夹",iconClass:"el-icon-star-on",type:"success"})},updateCart:function(t){this.isInCart(t)?this.removeCartItem(t):this.addCartItem(f()({},this.books.find(function(e){return e.id===t}))),this.$notify({title:"成功",message:"恭喜你,已成功更新购物车",iconClass:"el-icon-s-goods",type:"success"})},loadDetail:function(t){this.$router.push("/detail/"+t)},pureText:function(t){return o.a.stringUtil.pureText(t)},goDirectSettlement:function(t){var e=f()({},t,{amount:1});this.setupSettlementBillWithDefaultValue({items:[e]}),this.$router.push("/settle")}})},m={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("el-card",{staticClass:"box-card"},[n("div",{staticClass:"header",attrs:{slot:"header"},slot:"header"},[n("span",[t._v("热销商品")])]),t._v(" "),n("el-row",{attrs:{gutter:0}},t._l(t.books,function(e){return n("el-col",{key:e.id,staticClass:"book-container",attrs:{span:6}},[n("el-image",{staticClass:"image",attrs:{src:e.cover},on:{click:function(n){return t.loadDetail(e.id)}}}),t._v(" "),n("div",{staticStyle:{padding:"14px"}},[n("span",{attrs:{id:"price"}},[t._v("¥ "+t._s(e.price.toFixed(2)))]),t._v(" "),n("span",{attrs:{id:"title"}},[t._v(t._s(e.title))]),t._v(" "),n("span",{attrs:{id:"description"}},[t._v(t._s(t.pureText(e.description)))]),t._v(" "),n("div",{attrs:{id:"actions"}},[n("el-button",{attrs:{icon:"el-icon-money",circle:""},on:{click:function(n){return t.goDirectSettlement(e)}}}),t._v(" "),n("el-button",{attrs:{icon:t.isInCart(e.id)?"el-icon-s-goods":"el-icon-goods",circle:""},on:{click:function(n){return t.updateCart(e.id)}}}),t._v(" "),n("el-button",{attrs:{icon:t.isFavorite(e.id)?"el-icon-star-on":"el-icon-star-off",circle:""},on:{click:function(n){return t.updateFavorite(e.id)}}})],1)])],1)}),1)],1)},staticRenderFns:[]};var h={name:"MainPage",components:{Carousel:l,Cabinet:n("VU/8")(p,m,!1,function(t){n("DTIM")},"data-v-1dc16e2f",null).exports}},_={render:function(){var t=this.$createElement,e=this._self._c||t;return e("div",[e("Carousel"),this._v(" "),e("Cabinet")],1)},staticRenderFns:[]};var C=n("VU/8")(h,_,!1,function(t){n("yZcT")},"data-v-9d6e3ffa",null);e.default=C.exports},jv0A:function(t,e){},yZcT:function(t,e){}}); -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/js/8.c0df9daed0421a7c48c4.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([8],{AugR:function(t,e){},k7C0:function(t,e,a){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var s={render:function(){var t=this.$createElement,e=this._self._c||t;return e("el-card",{staticClass:"box-card",attrs:{"body-style":{padding:"0 10px 0 10px"}}},[e("div",{staticClass:"header",attrs:{slot:"header"},slot:"header"},[e("span",[this._v("讨论")]),this._v(" "),e("span",{staticClass:"comment"},[this._v("本功能通过Gitalk使用GitHub的Issues提供服务,请使用GitHub账号登录")])]),this._v(" "),e("div",[e("iframe",{staticStyle:{width:"1300px",height:"1000px"},attrs:{src:"/static/board/gitalk.html",frameborder:"0"}})])])},staticRenderFns:[]};var i=a("VU/8")({name:"CommentPage"},s,!1,function(t){a("AugR")},"data-v-4a337c49",null);e.default=i.exports}}); -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/js/9.1fd42476fd68c9cb70b0.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([9],{TgyC:function(t,e,a){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=a("Dd8w"),l=a.n(n),i=a("NYxO"),s={name:"CartPage",components:{PayStepIndicator:a("7WGw").a},data:function(){return{multipleSelection:[]}},computed:l()({},Object(i.e)("cart",["items"]),{total:function(){return this.multipleSelection.reduce(function(t,e){return t+e.price*e.amount},0)}}),mounted:function(){this.toggleSelection(this.items)},methods:l()({},Object(i.d)("cart",["adjustCartItems","removeCartItem"]),Object(i.b)("cart",["setupSettlementBillWithDefaultValue"]),{adjustAmount:function(t,e,a){var n=l()({},t);n.amount=e-a,this.adjustCartItems(n)},removeItem:function(t){var e=this;this.removeCartItem(t),this.$nextTick(function(){return e.toggleSelection(e.items)})},toggleSelection:function(t){var e=this;t?t.forEach(function(t){e.$refs.cartTable.toggleRowSelection(t)}):this.$refs.cartTable.clearSelection()},goSettlement:function(){this.setupSettlementBillWithDefaultValue({items:this.multipleSelection}),this.$router.push("/settle")},handleSelectionChange:function(t){this.multipleSelection=t}})},o={render:function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",[a("el-card",{staticClass:"box-card"},[a("div",{staticClass:"header",attrs:{slot:"header"},slot:"header"},[a("span",[t._v("我的购物车")]),t._v(" "),a("span",{staticClass:"comment"},[t._v("温馨提示:产品是否购买成功,以最终支付为准哦,请尽快完成结算")])]),t._v(" "),a("div",{staticClass:"content"},[a("el-table",{ref:"cartTable",staticStyle:{width:"100%"},attrs:{data:t.items},on:{"selection-change":t.handleSelectionChange}},[a("el-table-column",{attrs:{type:"selection",width:"55",fixed:"","show-overflow-tooltip":""}}),t._v(" "),a("el-table-column",{attrs:{label:"图片",width:"150"},scopedSlots:t._u([{key:"default",fn:function(t){return[a("img",{staticStyle:{width:"120px"},attrs:{src:t.row.cover}})]}}])}),t._v(" "),a("el-table-column",{attrs:{prop:"title",label:"商品名称",sortable:""}}),t._v(" "),a("el-table-column",{attrs:{prop:"price",label:"单价",width:"100",sortable:""}}),t._v(" "),a("el-table-column",{attrs:{label:"数量",width:"170",sortable:""},scopedSlots:t._u([{key:"default",fn:function(e){return[a("el-input-number",{attrs:{size:"mini",min:0,max:10,value:e.row.amount},on:{change:function(a,n){t.adjustAmount(e.row,a,n)}}})]}}])}),t._v(" "),a("el-table-column",{attrs:{label:"小计",width:"120",sortable:""},scopedSlots:t._u([{key:"default",fn:function(e){return[a("span",{staticClass:"subtotal"},[t._v(t._s(e.row.price*e.row.amount)+" 元")])]}}])}),t._v(" "),a("el-table-column",{attrs:{label:"操作",width:"120"},scopedSlots:t._u([{key:"default",fn:function(e){return[a("el-button",{attrs:{plain:"",size:"mini",type:"danger"},on:{click:function(a){return t.removeItem(e.row.id)}}},[t._v("删除")])]}}])})],1),t._v(" "),a("div",{staticClass:"actions"},[t._v("\n "+t._s("购物车中共计 "+t.items.length+" 件商品,已选择其中 "+t.multipleSelection.length+" 件")+"\n "),a("div",{staticClass:"total"},[t._v("\n 总计: "),a("span",{staticClass:"pay_price"},[t._v(t._s(this.total))]),t._v(" 元\n "),a("div",{staticClass:"pay_action"},[a("el-button",{staticStyle:{position:"relative",top:"-6px"},attrs:{size:"large",type:"primary",disabled:this.total<=0},on:{click:t.goSettlement}},[t._v("¥ 选好了,去结算\n ")])],1)])])],1)]),t._v(" "),a("PayStepIndicator",{attrs:{step:1}})],1)},staticRenderFns:[]};var r=a("VU/8")(s,o,!1,function(t){a("YeJQ")},"data-v-2f8330db",null);e.default=r.exports},YeJQ:function(t,e){}}); -------------------------------------------------------------------------------- /mmall-platform-gateway/src/main/resources/static/static/js/manifest.c1c199c8b8d75f95329a.js: -------------------------------------------------------------------------------- 1 | !function(e){var n=window.webpackJsonp;window.webpackJsonp=function(r,c,a){for(var f,i,u,d=0,s=[];d