├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── maven.yml ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── coffee-autoconfigure-core ├── pom.xml └── src │ └── main │ ├── java │ └── site │ │ └── zido │ │ └── coffee │ │ └── autoconfigure │ │ └── core │ │ └── PropertiesRunListener.java │ └── resources │ ├── META-INF │ └── spring.factories │ └── banner.txt ├── coffee-autoconfigures ├── coffee-autoconfigure-extra │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── site │ │ │ └── zido │ │ │ └── coffee │ │ │ └── autoconfigure │ │ │ └── extra │ │ │ ├── limiter │ │ │ ├── LimiterAutoConfiguration.java │ │ │ ├── LimiterExceptionAdvice.java │ │ │ ├── LimiterExceptionAutoHandlerConfiguration.java │ │ │ ├── LimiterProperties.java │ │ │ └── LimiterRedisConfiguration.java │ │ │ └── package-info.java │ │ └── resources │ │ └── META-INF │ │ ├── spring-configuration-metadata.json │ │ └── spring.factories ├── coffee-autoconfigure-rest-security │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── site │ │ │ └── zido │ │ │ └── coffee │ │ │ └── autoconfigure │ │ │ └── security │ │ │ └── rest │ │ │ ├── AuthorizationStorageJWTCondition.java │ │ │ ├── CoffeeSecurityProperties.java │ │ │ ├── RestSecurityAutoConfiguration.java │ │ │ ├── RestSecurityFilterAutoConfiguration.java │ │ │ ├── SecureStoreType.java │ │ │ ├── SpringBootRestSecurityConfiguration.java │ │ │ ├── WebSecurityAutoFilter.java │ │ │ └── authorize │ │ │ ├── PhoneCodeAutoConfiguration.java │ │ │ └── RedisPhoneCodeConfiguration.java │ │ └── resources │ │ └── META-INF │ │ ├── spring-configuration-metadata.json │ │ └── spring.factories ├── coffee-autoconfigure-web │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── site │ │ │ └── zido │ │ │ └── coffee │ │ │ └── autoconfigure │ │ │ └── web │ │ │ ├── CoffeeErrorMvcAutoConfiguration.java │ │ │ ├── GlobalExceptionEnablerConfiguration.java │ │ │ ├── GlobalResultEnablerConfiguration.java │ │ │ ├── JavaxExceptionEnablerConfiguration.java │ │ │ ├── JsonAutoConfiguration.java │ │ │ ├── RequestLogEnablerConfiguration.java │ │ │ └── basic │ │ │ └── CoffeeErrorAttributes.java │ │ └── resources │ │ └── META-INF │ │ ├── spring-configuration-metadata.json │ │ └── spring.factories └── pom.xml ├── coffee-dependencies └── pom.xml ├── coffee-modules ├── coffee-core │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── site │ │ │ └── zido │ │ │ └── coffee │ │ │ └── core │ │ │ ├── Coffee.java │ │ │ ├── constants │ │ │ └── Patterns.java │ │ │ ├── message │ │ │ └── CoffeeMessageSource.java │ │ │ ├── utils │ │ │ ├── BeanUtils.java │ │ │ ├── BeanUtilsException.java │ │ │ ├── IdWorker.java │ │ │ ├── InputConverter.java │ │ │ ├── KillServer.java │ │ │ ├── OutputConverter.java │ │ │ ├── RandomUtils.java │ │ │ ├── ReflectionUtils.java │ │ │ ├── SpringUtils.java │ │ │ ├── SystemClock.java │ │ │ └── maps │ │ │ │ └── expire │ │ │ │ └── ExpireMap.java │ │ │ └── validations │ │ │ ├── Phone.java │ │ │ └── PhoneValidator.java │ │ └── test │ │ └── java │ │ └── site │ │ └── zido │ │ └── coffee │ │ └── core │ │ └── common │ │ └── utils │ │ ├── ExpireMapTest.java │ │ └── SystemClockTest.java ├── coffee-extra │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── site │ │ │ └── zido │ │ │ └── coffee │ │ │ └── extra │ │ │ ├── limiter │ │ │ ├── AbstractLimiterInvoker.java │ │ │ ├── AbstractLimiterOperationSource.java │ │ │ ├── AnnotationDrivenLimiterBeanProcessor.java │ │ │ ├── AnnotationLimiterOperationSource.java │ │ │ ├── BeanFactoryLimiterOperationSourceAdvisor.java │ │ │ ├── EnableLimiter.java │ │ │ ├── FrequencyLimiter.java │ │ │ ├── Limiter.java │ │ │ ├── LimiterAnnotationParser.java │ │ │ ├── LimiterConfigurationSelector.java │ │ │ ├── LimiterErrorHandler.java │ │ │ ├── LimiterException.java │ │ │ ├── LimiterInterceptor.java │ │ │ ├── LimiterOperation.java │ │ │ ├── LimiterOperationSource.java │ │ │ ├── LimiterRootObject.java │ │ │ ├── MemoryFrequencyLimiter.java │ │ │ ├── ProxyLimiterConfiguration.java │ │ │ ├── ProxyLimiterConfigurer.java │ │ │ ├── RedisFrequencyLimiter.java │ │ │ ├── SimpleLimiterErrorHandler.java │ │ │ └── SpringLimiterAnnotationParser.java │ │ │ ├── lock │ │ │ ├── AbstractDistributedLock.java │ │ │ ├── DistributedLockFactory.java │ │ │ └── DistributedRedisLock.java │ │ │ ├── package-info.java │ │ │ └── security │ │ │ ├── AbstractSecurity.java │ │ │ └── StringRedisTemplateSecurity.java │ │ └── test │ │ └── java │ │ └── limiter │ │ └── MemoryFrequencyLimiterTest.java ├── coffee-rest-security │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── site │ │ │ └── zido │ │ │ └── coffee │ │ │ └── security │ │ │ ├── authentication │ │ │ ├── RestAuthenticationFailureHandler.java │ │ │ ├── RestAuthenticationSuccessHandler.java │ │ │ └── phone │ │ │ │ ├── CodeGenerator.java │ │ │ │ ├── CodeValidator.java │ │ │ │ ├── CustomCodeGenerator.java │ │ │ │ ├── CustomCodeValidator.java │ │ │ │ ├── MemoryPhoneCodeCache.java │ │ │ │ ├── PhoneAuthUserAuthenticationProvider.java │ │ │ │ ├── PhoneAuthenticationFilter.java │ │ │ │ ├── PhoneCodeAuthenticationToken.java │ │ │ │ ├── PhoneCodeCache.java │ │ │ │ ├── PhoneCodeService.java │ │ │ │ └── SpringRedisPhoneCodeCache.java │ │ │ ├── configurers │ │ │ ├── PhoneCodeLoginConfigurer.java │ │ │ ├── RestAccessDeniedHandlerImpl.java │ │ │ ├── RestExceptionHandlingConfigurer.java │ │ │ ├── RestLogoutConfigurer.java │ │ │ ├── RestLogoutSuccessHandler.java │ │ │ ├── RestSecurityConfigureAdapter.java │ │ │ └── RestSecurityContextConfigurer.java │ │ │ └── token │ │ │ ├── JwtRefreshFilter.java │ │ │ ├── JwtRefreshProperties.java │ │ │ ├── JwtSecurityContextRepository.java │ │ │ ├── JwtTokenProperties.java │ │ │ ├── JwtWriterResponse.java │ │ │ ├── RefreshWriterResponse.java │ │ │ ├── RestAuthenticationEntryPoint.java │ │ │ └── TokenInvalidException.java │ │ └── test │ │ └── java │ │ └── site │ │ └── zido │ │ └── coffee │ │ └── security │ │ └── authentication │ │ └── phone │ │ └── CustomCodeGeneratorTest.java ├── coffee-webmvc │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── site │ │ └── zido │ │ └── coffee │ │ └── mvc │ │ ├── CommonErrorCode.java │ │ ├── exceptions │ │ ├── CommonBusinessException.java │ │ ├── EnableGlobalException.java │ │ ├── EnableJavaxException.java │ │ └── GlobalExceptionConfiguration.java │ │ ├── logger │ │ ├── CoffeeRequestLoggingFilter.java │ │ ├── EnableRequestLogger.java │ │ └── MvcLogConfiguration.java │ │ ├── rest │ │ ├── BaseGlobalExceptionHandler.java │ │ ├── DefaultHttpResponseBodyFactory.java │ │ ├── DefaultResult.java │ │ ├── EnableGlobalResult.java │ │ ├── GlobalExceptionAdvice.java │ │ ├── GlobalRestConfiguration.java │ │ ├── GlobalResultHandler.java │ │ ├── HttpResponseBodyConfiguration.java │ │ ├── HttpResponseBodyFactory.java │ │ ├── JavaxValidationExceptionAdvice.java │ │ ├── OriginalResponse.java │ │ ├── Result.java │ │ ├── StringToResultHttpMessageConverter.java │ │ └── package-info.java │ │ └── utils │ │ └── ResponseUtils.java └── pom.xml ├── coffee-spring-boot-parent └── pom.xml ├── coffee-spring-boot-starter └── pom.xml ├── coffee-starters ├── coffee-starter-extra │ └── pom.xml ├── coffee-starter-rest-security │ └── pom.xml ├── coffee-starter-web │ └── pom.xml └── pom.xml ├── examples ├── Production │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── site │ │ │ └── zido │ │ │ └── demo │ │ │ ├── DemoProdApplication.java │ │ │ ├── api │ │ │ ├── AdminController.java │ │ │ ├── RoomController.java │ │ │ └── UserController.java │ │ │ ├── config │ │ │ ├── AuthConfig.java │ │ │ ├── LimiterConfiguration.java │ │ │ └── RedisConfiguration.java │ │ │ ├── entity │ │ │ ├── Admin.java │ │ │ ├── Record.java │ │ │ ├── Room.java │ │ │ └── User.java │ │ │ ├── pojo │ │ │ ├── AuthUser.java │ │ │ ├── dto │ │ │ │ └── UserDTO.java │ │ │ └── params │ │ │ │ └── UserParams.java │ │ │ ├── repository │ │ │ ├── AdminRepository.java │ │ │ ├── RecordRepository.java │ │ │ ├── RoomRepository.java │ │ │ └── UserRepository.java │ │ │ └── service │ │ │ ├── IRoomService.java │ │ │ ├── IUserService.java │ │ │ └── impl │ │ │ ├── RoomServiceImpl.java │ │ │ └── UserServiceImpl.java │ │ └── test │ │ └── java │ │ └── site │ │ └── zido │ │ └── demo │ │ └── api │ │ └── integration_test │ │ ├── SecurityTest.java │ │ └── package-info.java └── default-use │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── site │ │ │ └── zido │ │ │ └── demo │ │ │ ├── DemoApplication.java │ │ │ ├── api │ │ │ ├── IndexController.java │ │ │ └── LimiterController.java │ │ │ └── config │ │ │ └── AuthConfig.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── site │ └── zido │ └── demo │ └── api │ └── integration_test │ ├── LimiterControllerTest.java │ ├── SecurityTest.java │ └── package-info.java └── pom.xml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 缺陷反馈 3 | about: 报告缺陷以帮助我们改进 4 | title: '' 5 | labels: bug 6 | assignees: zidoshare 7 | 8 | --- 9 | 10 | ### 描述问题 11 | 12 | 请尽量清晰精准地描述你碰到的问题。 13 | 14 | ### 重现步骤 15 | 16 | 请描述如何重现这个问题: 17 | 18 | 1. Go to '...' 19 | 2. use '...' 20 | 3. See error 21 | 22 | ### 期待的结果 23 | 24 | 请尽量清晰精准地描述你所期待的结果。 25 | 26 | ### 截屏或录像 27 | 28 | 如果可能,请尽量附加截图或录像来描述你遇到的问题。 29 | 30 | ### 其他信息 31 | 32 | 请提供其他附加信息帮助我们诊断问题。 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 意见或建议 3 | about: 提出你期待的功能特性或者对已有功能的意见或者建议 4 | title: '' 5 | labels: help wanted 6 | assignees: zidoshare 7 | 8 | --- 9 | 10 | ### 你在什么场景下需要该功能? 11 | 12 | 请尽量清晰精准地描述你碰到的问题。 13 | 14 | ### 描述可能的解决方案 15 | 16 | 请尽量清晰精准地描述你期待我们要做的,描述你想到的实现方案。 17 | 18 | ### 描述你认为的候选方案 19 | 20 | 请尽量清晰精准地描述你能接受的候选解决方案。 21 | 22 | ### 其他信息 23 | 24 | 请提供关于该功能建议的其他附加信息。 25 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 1.8 18 | uses: actions/setup-java@v1 19 | with: 20 | java-version: 1.8 21 | - name: Build with Maven 22 | run: mvn test -Ptest --file pom.xml 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Maven template 3 | target/ 4 | pom.xml.tag 5 | pom.xml.releaseBackup 6 | pom.xml.versionsBackup 7 | pom.xml.next 8 | release.properties 9 | dependency-reduced-pom.xml 10 | buildNumber.properties 11 | .mvn/timing.properties 12 | .idea/ 13 | *.iml 14 | .vscode/ 15 | .settings/ 16 | .project 17 | .classpath 18 | .factorypath 19 | 20 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 21 | !/.mvn/wrapper/maven-wrapper.jar 22 | 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | dist: trusty 5 | script: 6 | - mvn test -Ptest --file pom.xml 7 | cache: 8 | directories: 9 | - $HOME/.m2 -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at wuhongxu1208@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 贡献说明 2 | 3 | > 注意:非常非常欢迎任何意见或者建议在[issues](https://github.com/zidoshare/coffee-spring-boot-builder/issues)中提出,也可以[发送email给我](mailto:wuhongxu1208@gmail.com),谢谢 4 | 5 | ## 介绍 6 | 7 | 欢迎任何类型的贡献,而不仅仅是代码。你可以做出的贡献包括但不限于以下条目: 8 | 9 | * bug反馈:请尽可能提供详细的错误报告(例如:控制台输出、异常堆栈等信息) 10 | 11 | * 撰写文档:所有任何相关文档、博客等,甚至包括对于logo的提议以及制作 12 | 13 | * 交流:在讨论区(包括但不限于issues/论坛/email等)提出任何相关意见火建议 14 | 15 | * 代码:看看未解决的问题。即使你不能编写代码,对它们进行评论,表明你关心某个特定问题也很重要。这能帮助我们对它们进行分类。 16 | 17 | ## 说明、帮助 18 | 19 | 您可以从这个免费系列中了解[如何在GitHub上贡献一个开源项目](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)。 20 | 21 | ## 提交代码 22 | 23 | 任何代码更改都应作为pull request提交。 24 | 25 | 你需要描述解释代码的作用并给出执行它的步骤。 26 | 27 | 你提交的代码应当尽可能的包含测试。 28 | 29 | > 请务必注意,项目开发分支为master分支。进行开发时务必确保你的master分支为最新 30 | 31 | 仅接受github的[pull request](https://help.github.com/articles/about-pull-requests/)工作流程。 32 | 33 | ## 代码审查 34 | 35 | 拉取请求越大,审核和合并所需的时间就越长。你可以从一个大的需求中提取尽可能少的内容来进行实现,这样更易于查看和合并。你还应该描述:你的目的是什么? 36 | 37 | ## FAQ 38 | 39 | 如果您有任何疑问,请创建一个[issue](https://github.com/zidoshare/coffee-spring-boot-builder/issues/new)(提示:请先搜索看看其他人之前是否没有问过同一个问题!)。 40 | 41 | 您也可以通过[wuhongxu1208@gmail.com](mailto:wuhongxu1208@gmail.com)与我们联系。 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zido 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /coffee-autoconfigure-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-common-builder 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-autoconfigure-core 13 | 14 | 15 | 16 | site.zido 17 | coffee-core 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-configuration-processor 22 | true 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-autoconfigure 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /coffee-autoconfigure-core/src/main/java/site/zido/coffee/autoconfigure/core/PropertiesRunListener.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.core; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.SpringApplicationRunListener; 5 | import org.springframework.core.Ordered; 6 | import org.springframework.core.env.ConfigurableEnvironment; 7 | import org.springframework.core.env.MapPropertySource; 8 | import org.springframework.core.env.MutablePropertySources; 9 | import org.springframework.util.StringUtils; 10 | import site.zido.coffee.core.Coffee; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class PropertiesRunListener implements SpringApplicationRunListener, Ordered { 16 | public PropertiesRunListener(SpringApplication application, String[] args) { 17 | } 18 | 19 | @Override 20 | public void environmentPrepared(ConfigurableEnvironment environment) { 21 | MutablePropertySources sources = environment.getPropertySources(); 22 | Map props = new HashMap<>(); 23 | props.put("coffee.version", Coffee.VERSION); 24 | 25 | String property = environment.getProperty("spring.application.name"); 26 | if (StringUtils.hasText(property)) { 27 | property = "${AnsiStyle.NORMAL}${AnsiColor.BRIGHT_BLACK} design for ${AnsiStyle.BOLD}${AnsiColor.DEFAULT}" 28 | + property + 29 | "${AnsiStyle.NORMAL}${AnsiColor.BRIGHT_BLACK} power by "; 30 | }else{ 31 | property = ""; 32 | } 33 | props.put("coffee.project.name", property); 34 | sources.addLast(new MapPropertySource("coffee", props)); 35 | } 36 | 37 | @Override 38 | public int getOrder() { 39 | return Ordered.LOWEST_PRECEDENCE; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /coffee-autoconfigure-core/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.SpringApplicationRunListener=\ 2 | site.zido.coffee.autoconfigure.core.PropertiesRunListener -------------------------------------------------------------------------------- /coffee-autoconfigure-core/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ${AnsiColor.YELLOW} _____ __ __ ${AnsiColor.BRIGHT_CYAN} _____ _ 2 | ${AnsiColor.YELLOW} / ____| / _|/ _| ${AnsiColor.BRIGHT_CYAN} / ____| (_) 3 | ${AnsiColor.YELLOW}| | ___ | |_| |_ ___ ___ ${AnsiColor.BRIGHT_CYAN}| (___ _ __ _ __ _ _ __ __ _ 4 | ${AnsiColor.YELLOW}| | / _ \| _| _/ _ \/ _ \ ${AnsiColor.BRIGHT_CYAN} \___ \| '_ \| '__| | '_ \ / _` | 5 | ${AnsiColor.YELLOW}| |___| (_) | | | || __/ __/ ${AnsiColor.BRIGHT_CYAN} ____) | |_) | | | | | | | (_| | 6 | ${AnsiColor.YELLOW} \_____\___/|_| |_| \___|\___| ${AnsiColor.BRIGHT_CYAN}|_____/| .__/|_| |_|_| |_|\__, | 7 | ${AnsiColor.YELLOW} ${AnsiColor.BRIGHT_CYAN} | | __/ | 8 | ${AnsiColor.YELLOW} ${AnsiColor.BRIGHT_CYAN} |_| |___/ 9 | 10 | 11 | ${AnsiColor.BLUE}:: CoffeeSpring ${AnsiColor.YELLOW}${coffee.version}${coffee.project.name}${AnsiStyle.NORMAL}${AnsiColor.BLUE} :: SpringBoot ${AnsiColor.YELLOW}${spring-boot.version}${AnsiColor.BRIGHT_BLACK} -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-extra/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-autoconfigures 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-autoconfigure-extra 13 | 14 | 15 | site.zido 16 | coffee-extra 17 | 18 | 19 | site.zido 20 | coffee-webmvc 21 | true 22 | 23 | 24 | org.springframework.data 25 | spring-data-redis 26 | compile 27 | true 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-extra/src/main/java/site/zido/coffee/autoconfigure/extra/limiter/LimiterAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.extra.limiter; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.util.StringUtils; 10 | import site.zido.coffee.extra.limiter.EnableLimiter; 11 | import site.zido.coffee.extra.limiter.FrequencyLimiter; 12 | import site.zido.coffee.extra.limiter.MemoryFrequencyLimiter; 13 | 14 | @Configuration 15 | @EnableConfigurationProperties(LimiterProperties.class) 16 | @EnableLimiter 17 | @AutoConfigureAfter(LimiterRedisConfiguration.class) 18 | public class LimiterAutoConfiguration { 19 | 20 | @Bean 21 | @ConditionalOnMissingBean(name = "limiterTemplate", value = FrequencyLimiter.class) 22 | public FrequencyLimiter limiter(@Autowired LimiterProperties properties) { 23 | if (StringUtils.hasLength(properties.getPrefix())) { 24 | return new MemoryFrequencyLimiter(properties.getPrefix()); 25 | } 26 | return new MemoryFrequencyLimiter(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-extra/src/main/java/site/zido/coffee/autoconfigure/extra/limiter/LimiterExceptionAdvice.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.extra.limiter; 2 | 3 | import org.springframework.core.annotation.Order; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.RestControllerAdvice; 8 | import site.zido.coffee.extra.limiter.LimiterException; 9 | import site.zido.coffee.mvc.CommonErrorCode; 10 | import site.zido.coffee.mvc.rest.HttpResponseBodyFactory; 11 | import site.zido.coffee.mvc.rest.OriginalResponse; 12 | 13 | @RestControllerAdvice 14 | @Order(0) 15 | public class LimiterExceptionAdvice { 16 | private final HttpResponseBodyFactory factory; 17 | 18 | public LimiterExceptionAdvice(HttpResponseBodyFactory factory) { 19 | this.factory = factory; 20 | } 21 | 22 | @ExceptionHandler(LimiterException.class) 23 | @OriginalResponse 24 | public ResponseEntity handleLimiterException(LimiterException e) { 25 | return ResponseEntity 26 | .status(HttpStatus.TOO_MANY_REQUESTS) 27 | .body(factory.error(CommonErrorCode.LIMIT, e.getMessage())); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-extra/src/main/java/site/zido/coffee/autoconfigure/extra/limiter/LimiterExceptionAutoHandlerConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.extra.limiter; 2 | 3 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import site.zido.coffee.mvc.rest.HttpResponseBodyFactory; 10 | 11 | @Configuration 12 | @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) 13 | @ConditionalOnBean(HttpResponseBodyFactory.class) 14 | @AutoConfigureAfter(name = "site.zido.coffee.autoconfigure.web.GlobalResultEnablerConfiguration") 15 | public class LimiterExceptionAutoHandlerConfiguration { 16 | @Bean(name = "limiterExceptionHandler") 17 | @ConditionalOnMissingBean(name = "limiterExceptionHandler") 18 | public LimiterExceptionAdvice advice(HttpResponseBodyFactory factory) { 19 | return new LimiterExceptionAdvice(factory); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-extra/src/main/java/site/zido/coffee/autoconfigure/extra/limiter/LimiterProperties.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.extra.limiter; 2 | 3 | 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | @ConfigurationProperties(prefix = "coffee.limiter") 7 | public class LimiterProperties { 8 | private String prefix; 9 | 10 | public String getPrefix() { 11 | return prefix; 12 | } 13 | 14 | public void setPrefix(String prefix) { 15 | this.prefix = prefix; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-extra/src/main/java/site/zido/coffee/autoconfigure/extra/limiter/LimiterRedisConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.extra.limiter; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Qualifier; 5 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.data.redis.connection.RedisConnectionFactory; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.data.redis.serializer.RedisSerializer; 15 | import org.springframework.data.redis.serializer.SerializationException; 16 | import org.springframework.data.redis.serializer.StringRedisSerializer; 17 | import org.springframework.util.StringUtils; 18 | import site.zido.coffee.extra.limiter.FrequencyLimiter; 19 | import site.zido.coffee.extra.limiter.RedisFrequencyLimiter; 20 | 21 | import java.nio.ByteBuffer; 22 | import java.nio.charset.StandardCharsets; 23 | 24 | @ConditionalOnBean(RedisConnectionFactory.class) 25 | @AutoConfigureAfter(RedisAutoConfiguration.class) 26 | @EnableConfigurationProperties(LimiterProperties.class) 27 | @Configuration 28 | public class LimiterRedisConfiguration { 29 | @Bean(name = "limiterTemplate") 30 | @ConditionalOnMissingBean(name = "limiterTemplate") 31 | @ConditionalOnBean(RedisConnectionFactory.class) 32 | public RedisTemplate template(RedisConnectionFactory connectionFactory) { 33 | RedisTemplate template = new RedisTemplate<>(); 34 | template.setConnectionFactory(connectionFactory); 35 | template.setKeySerializer(new StringRedisSerializer(StandardCharsets.UTF_8)); 36 | template.setValueSerializer(new RedisSerializer() { 37 | 38 | @Override 39 | public byte[] serialize(Long value) throws SerializationException { 40 | return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(value).array(); 41 | } 42 | 43 | @Override 44 | public Long deserialize(byte[] bytes) throws SerializationException { 45 | ByteBuffer buffer = ByteBuffer.allocate(8); 46 | buffer.put(bytes, 0, bytes.length); 47 | buffer.flip(); 48 | return buffer.getLong(); 49 | } 50 | }); 51 | return template; 52 | } 53 | 54 | @Bean 55 | @ConditionalOnBean(name = "limiterTemplate") 56 | @ConditionalOnMissingBean(FrequencyLimiter.class) 57 | public FrequencyLimiter limiter(@Autowired LimiterProperties properties, 58 | @Autowired @Qualifier(value = "limiterTemplate") RedisTemplate template) { 59 | if (StringUtils.hasLength(properties.getPrefix())) { 60 | return new RedisFrequencyLimiter(properties.getPrefix(), template); 61 | } 62 | return new RedisFrequencyLimiter(template); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-extra/src/main/java/site/zido/coffee/autoconfigure/extra/package-info.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.extra; -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-extra/src/main/resources/META-INF/spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [ 3 | { 4 | "name": "coffee.limiter", 5 | "type": "site.zido.coffee.common.limiter.LimiterProperties", 6 | "sourceType": "site.zido.coffee.common.limiter.LimiterAutoConfiguration", 7 | "sourceMethod": "createProperties()", 8 | "description": "限流器相关属性" 9 | } 10 | ], 11 | "properties": [ 12 | { 13 | "name": "coffee.limiter.prefix", 14 | "type": "java.lang.String", 15 | "sourceType": "site.zido.coffee.common.limiter.LimiterProperties", 16 | "description": "设置limiter前缀" 17 | } 18 | ], 19 | "hints": [] 20 | } -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-extra/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | site.zido.coffee.autoconfigure.extra.limiter.LimiterRedisConfiguration,\ 3 | site.zido.coffee.autoconfigure.extra.limiter.LimiterAutoConfiguration,\ 4 | site.zido.coffee.autoconfigure.extra.limiter.LimiterExceptionAutoHandlerConfiguration -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-autoconfigures 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-autoconfigure-rest-security 13 | 14 | 15 | site.zido 16 | coffee-webmvc 17 | 18 | 19 | site.zido 20 | coffee-rest-security 21 | 22 | 23 | org.springframework.data 24 | spring-data-redis 25 | compile 26 | true 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/src/main/java/site/zido/coffee/autoconfigure/security/rest/AuthorizationStorageJWTCondition.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.security.rest; 2 | 3 | import org.springframework.context.annotation.Condition; 4 | import org.springframework.context.annotation.ConditionContext; 5 | import org.springframework.core.type.AnnotatedTypeMetadata; 6 | 7 | public class AuthorizationStorageJWTCondition implements Condition { 8 | 9 | @Override 10 | public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 11 | String storeType = context.getEnvironment().getProperty("spring.security.secureStoreType", "JWT"); 12 | return SecureStoreType.JWT.name().equalsIgnoreCase(storeType); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/src/main/java/site/zido/coffee/autoconfigure/security/rest/RestSecurityAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.security.rest; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 5 | import org.springframework.boot.autoconfigure.security.SecurityDataConfiguration; 6 | import org.springframework.boot.autoconfigure.security.SecurityProperties; 7 | import org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration; 8 | import org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration; 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 | import org.springframework.context.ApplicationEventPublisher; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.context.annotation.Import; 14 | import org.springframework.security.authentication.AuthenticationEventPublisher; 15 | import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; 16 | 17 | /** 18 | * rest security自动配置类 19 | *

20 | * SpringBootRestSecurityConfiguration代替原来的{@link SpringBootWebSecurityConfiguration} 21 | *

22 | * 去掉{@link WebSecurityEnablerConfiguration}以取消 23 | * {@link org.springframework.security.config.annotation.web.configuration.EnableWebSecurity}注解所带来的其他自动配置 24 | * 25 | * @author zido 26 | */ 27 | @Configuration(proxyBeanMethods = false) 28 | @ConditionalOnClass(DefaultAuthenticationEventPublisher.class) 29 | @EnableConfigurationProperties(SecurityProperties.class) 30 | @Import({SpringBootRestSecurityConfiguration.class, 31 | WebSecurityEnablerConfiguration.class, 32 | SecurityDataConfiguration.class}) 33 | public class RestSecurityAutoConfiguration { 34 | 35 | @Bean 36 | @ConditionalOnMissingBean(AuthenticationEventPublisher.class) 37 | public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) { 38 | return new DefaultAuthenticationEventPublisher(publisher); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/src/main/java/site/zido/coffee/autoconfigure/security/rest/RestSecurityFilterAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.security.rest; 2 | 3 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; 8 | import org.springframework.boot.autoconfigure.security.SecurityProperties; 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 | import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; 14 | 15 | import javax.servlet.DispatcherType; 16 | import java.util.EnumSet; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * 单独提取filter配置,以确保自动配置顺序 21 | * 22 | * @author zido 23 | */ 24 | @Configuration(proxyBeanMethods = false) 25 | @ConditionalOnWebApplication(type = Type.SERVLET) 26 | @EnableConfigurationProperties(SecurityProperties.class) 27 | @ConditionalOnClass({AbstractSecurityWebApplicationInitializer.class}) 28 | @AutoConfigureAfter(RestSecurityAutoConfiguration.class) 29 | public class RestSecurityFilterAutoConfiguration { 30 | 31 | private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME; 32 | 33 | @Bean 34 | @ConditionalOnBean(name = DEFAULT_FILTER_NAME) 35 | public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration( 36 | SecurityProperties securityProperties) { 37 | DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean( 38 | DEFAULT_FILTER_NAME); 39 | registration.setOrder(securityProperties.getFilter().getOrder()); 40 | registration.setDispatcherTypes(getDispatcherTypes(securityProperties)); 41 | return registration; 42 | } 43 | 44 | private EnumSet getDispatcherTypes(SecurityProperties securityProperties) { 45 | if (securityProperties.getFilter().getDispatcherTypes() == null) { 46 | return null; 47 | } 48 | return securityProperties.getFilter().getDispatcherTypes().stream() 49 | .map((type) -> DispatcherType.valueOf(type.name())) 50 | .collect(Collectors.collectingAndThen(Collectors.toSet(), EnumSet::copyOf)); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/src/main/java/site/zido/coffee/autoconfigure/security/rest/SecureStoreType.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.security.rest; 2 | 3 | public enum SecureStoreType { 4 | // spring security自带的cookie 5 | COOKIE, 6 | //JWT token 标准 7 | JWT, 8 | //TODO 传统token支持 9 | TOKEN 10 | } 11 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/src/main/java/site/zido/coffee/autoconfigure/security/rest/WebSecurityAutoFilter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.security.rest; 2 | 3 | import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter; 4 | import org.springframework.boot.autoconfigure.AutoConfigurationMetadata; 5 | import org.springframework.context.EnvironmentAware; 6 | import org.springframework.core.env.Environment; 7 | 8 | import java.util.Arrays; 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | public class WebSecurityAutoFilter implements AutoConfigurationImportFilter, EnvironmentAware { 13 | private static final Set WEB_SECURITY_SKIP = new HashSet<>( 14 | Arrays.asList("org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration", 15 | "org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration") 16 | ); 17 | 18 | private static final Set REST_SECURITY_SKIP = new HashSet<>( 19 | Arrays.asList("site.zido.coffee.autoconfigure.security.rest.RestSecurityFilterAutoConfiguration", 20 | "site.zido.coffee.autoconfigure.security.rest.RestSecurityAutoConfiguration") 21 | ); 22 | 23 | private boolean useRest = true; 24 | 25 | @Override 26 | public boolean[] match(String[] classNames, AutoConfigurationMetadata metadata) { 27 | boolean[] matches = new boolean[classNames.length]; 28 | 29 | for (int i = 0; i < classNames.length; i++) { 30 | matches[i] = !(useRest ? WEB_SECURITY_SKIP : REST_SECURITY_SKIP).contains(classNames[i]); 31 | } 32 | return matches; 33 | } 34 | 35 | @Override 36 | public void setEnvironment(Environment environment) { 37 | this.useRest = "JWT".equalsIgnoreCase(environment.getProperty("spring.security.secureStoreType", "JWT")); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/src/main/java/site/zido/coffee/autoconfigure/security/rest/authorize/PhoneCodeAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.security.rest.authorize; 2 | 3 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 5 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.util.StringUtils; 9 | import site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties; 10 | import site.zido.coffee.security.authentication.phone.MemoryPhoneCodeCache; 11 | import site.zido.coffee.security.authentication.phone.PhoneCodeCache; 12 | 13 | @Configuration 14 | @EnableConfigurationProperties({CoffeeSecurityProperties.class}) 15 | @AutoConfigureAfter(RedisPhoneCodeConfiguration.class) 16 | public class PhoneCodeAutoConfiguration { 17 | @ConditionalOnMissingBean(PhoneCodeCache.class) 18 | @Bean 19 | public PhoneCodeCache getCache(CoffeeSecurityProperties properties) { 20 | return new MemoryPhoneCodeCache() { 21 | @Override 22 | public void put(String phone, String code) { 23 | super.put(phone, code); 24 | } 25 | 26 | @Override 27 | public String getCode(String phone) { 28 | if (StringUtils.hasText(properties.getPhoneCode().getKeyPrefix())) { 29 | phone = properties.getPhoneCode().getKeyPrefix() + phone; 30 | } 31 | return super.getCode(phone); 32 | } 33 | 34 | @Override 35 | public long getTimeout() { 36 | return properties.getPhoneCode().getTimeout() == null 37 | ? super.getTimeout() 38 | : properties.getPhoneCode().getTimeout(); 39 | } 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/src/main/java/site/zido/coffee/autoconfigure/security/rest/authorize/RedisPhoneCodeConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.security.rest.authorize; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Qualifier; 5 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.data.redis.connection.RedisConnectionFactory; 12 | import org.springframework.data.redis.core.StringRedisTemplate; 13 | import site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties; 14 | import site.zido.coffee.security.authentication.phone.PhoneCodeCache; 15 | import site.zido.coffee.security.authentication.phone.SpringRedisPhoneCodeCache; 16 | 17 | import java.util.concurrent.TimeUnit; 18 | 19 | @Configuration 20 | @ConditionalOnBean(RedisConnectionFactory.class) 21 | @AutoConfigureAfter(RedisAutoConfiguration.class) 22 | public class RedisPhoneCodeConfiguration { 23 | public static final String TEMPLATE_BEAN_NAME = "phoneCodeCacheTemplate"; 24 | 25 | @Bean 26 | @ConditionalOnMissingBean(PhoneCodeCache.class) 27 | public PhoneCodeCache phoneCodeCache(@Autowired CoffeeSecurityProperties properties, 28 | @Autowired @Qualifier(TEMPLATE_BEAN_NAME) StringRedisTemplate template) { 29 | SpringRedisPhoneCodeCache cache = new SpringRedisPhoneCodeCache(); 30 | cache.setKeyPrefix(properties.getPhoneCode().getKeyPrefix()); 31 | if (properties.getPhoneCode().getTimeout() != null) { 32 | cache.setTimeout(properties.getPhoneCode().getTimeout(), TimeUnit.SECONDS); 33 | } 34 | cache.setTemplate(template); 35 | return cache; 36 | } 37 | 38 | @Bean(value = TEMPLATE_BEAN_NAME) 39 | @ConditionalOnMissingBean(name = TEMPLATE_BEAN_NAME) 40 | public StringRedisTemplate createTemplate(RedisConnectionFactory factory) { 41 | return new StringRedisTemplate(factory); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/src/main/resources/META-INF/spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [ 3 | { 4 | "name": "spring.security", 5 | "type": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties", 6 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties" 7 | }, 8 | { 9 | "name": "spring.security.jwt", 10 | "type": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$JwtProperties", 11 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties", 12 | "sourceMethod": "getJwt()" 13 | }, 14 | { 15 | "name": "spring.security.phone-code", 16 | "type": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$AuthorizationPhoneCodeProperties", 17 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties", 18 | "sourceMethod": "getPhoneCode()" 19 | } 20 | ], 21 | "properties": [ 22 | { 23 | "name": "spring.security.jwt.auto-refresh", 24 | "type": "java.lang.Boolean", 25 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$JwtProperties", 26 | "defaultValue": true 27 | }, 28 | { 29 | "name": "spring.security.jwt.expiration", 30 | "type": "java.lang.Long", 31 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$JwtProperties" 32 | }, 33 | { 34 | "name": "spring.security.jwt.header", 35 | "type": "java.lang.String", 36 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$JwtProperties", 37 | "defaultValue": "Authorization" 38 | }, 39 | { 40 | "name": "spring.security.jwt.refresh-header", 41 | "type": "java.lang.String", 42 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$JwtProperties", 43 | "defaultValue": "Refresh-Token" 44 | }, 45 | { 46 | "name": "spring.security.jwt.refresh-secret", 47 | "type": "java.lang.String", 48 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$JwtProperties" 49 | }, 50 | { 51 | "name": "spring.security.jwt.refresh-support", 52 | "type": "java.lang.Boolean", 53 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$JwtProperties", 54 | "defaultValue": false 55 | }, 56 | { 57 | "name": "spring.security.jwt.renew-in-ms", 58 | "type": "java.lang.Long", 59 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$JwtProperties" 60 | }, 61 | { 62 | "name": "spring.security.jwt.secret", 63 | "type": "java.lang.String", 64 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$JwtProperties" 65 | }, 66 | { 67 | "name": "spring.security.phone-code-enable", 68 | "type": "java.lang.Boolean", 69 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties", 70 | "defaultValue": true 71 | }, 72 | { 73 | "name": "spring.security.phone-code.key-prefix", 74 | "type": "java.lang.String", 75 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$AuthorizationPhoneCodeProperties" 76 | }, 77 | { 78 | "name": "spring.security.phone-code.timeout", 79 | "type": "java.lang.Long", 80 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties$AuthorizationPhoneCodeProperties", 81 | "defaultValue": 0 82 | }, 83 | { 84 | "name": "spring.security.secure-store-type", 85 | "type": "site.zido.coffee.autoconfigure.security.rest.SecureStoreType", 86 | "sourceType": "site.zido.coffee.autoconfigure.security.rest.CoffeeSecurityProperties" 87 | } 88 | ], 89 | "hints": [] 90 | } -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-rest-security/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | site.zido.coffee.autoconfigure.security.rest.RestSecurityAutoConfiguration,\ 3 | site.zido.coffee.autoconfigure.security.rest.RestSecurityFilterAutoConfiguration,\ 4 | site.zido.coffee.autoconfigure.security.rest.authorize.PhoneCodeAutoConfiguration,\ 5 | site.zido.coffee.autoconfigure.security.rest.authorize.RedisPhoneCodeConfiguration 6 | org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ 7 | site.zido.coffee.autoconfigure.security.rest.WebSecurityAutoFilter -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-autoconfigures 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-autoconfigure-web 13 | 14 | 15 | site.zido 16 | coffee-webmvc 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-web/src/main/java/site/zido/coffee/autoconfigure/web/CoffeeErrorMvcAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.web; 2 | 3 | import org.springframework.beans.factory.ObjectProvider; 4 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 7 | import org.springframework.boot.autoconfigure.condition.SearchStrategy; 8 | import org.springframework.boot.autoconfigure.web.ServerProperties; 9 | import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; 10 | import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; 11 | import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; 12 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 13 | import org.springframework.boot.web.servlet.error.ErrorAttributes; 14 | import org.springframework.boot.web.servlet.error.ErrorController; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.context.annotation.Import; 18 | import org.springframework.http.HttpStatus; 19 | import org.springframework.http.MediaType; 20 | import org.springframework.http.ResponseEntity; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import site.zido.coffee.autoconfigure.web.basic.CoffeeErrorAttributes; 23 | import site.zido.coffee.mvc.rest.HttpResponseBodyConfiguration; 24 | import site.zido.coffee.mvc.rest.HttpResponseBodyFactory; 25 | import site.zido.coffee.mvc.rest.OriginalResponse; 26 | 27 | import javax.servlet.http.HttpServletRequest; 28 | import java.util.Map; 29 | import java.util.stream.Collectors; 30 | 31 | @Configuration 32 | @EnableConfigurationProperties({ServerProperties.class}) 33 | @Import(HttpResponseBodyConfiguration.class) 34 | @AutoConfigureBefore(ErrorMvcAutoConfiguration.class) 35 | @ConditionalOnProperty(value = "spring.coffee.web.global-exception", matchIfMissing = true, havingValue = "true") 36 | class CoffeeErrorMvcAutoConfiguration { 37 | private final ServerProperties serverProperties; 38 | 39 | public CoffeeErrorMvcAutoConfiguration(ServerProperties serverProperties) { 40 | this.serverProperties = serverProperties; 41 | } 42 | 43 | @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) 44 | @Bean 45 | public CoffeeErrorAttributes errorAttributes(HttpResponseBodyFactory factory) { 46 | return new CoffeeErrorAttributes(this.serverProperties.getError().isIncludeException(), factory); 47 | } 48 | 49 | @Bean 50 | @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) 51 | public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, 52 | ObjectProvider errorViewResolvers) { 53 | return new BasicErrorController(errorAttributes, this.serverProperties.getError(), 54 | errorViewResolvers.orderedStream().collect(Collectors.toList())) { 55 | @RequestMapping 56 | @OriginalResponse 57 | public ResponseEntity> error(HttpServletRequest request) { 58 | HttpStatus status = getStatus(request); 59 | if (status == HttpStatus.NO_CONTENT) { 60 | return new ResponseEntity<>(status); 61 | } 62 | Map body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); 63 | return new ResponseEntity<>(body, status); 64 | } 65 | }; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-web/src/main/java/site/zido/coffee/autoconfigure/web/GlobalExceptionEnablerConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.web; 2 | 3 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import site.zido.coffee.mvc.exceptions.EnableGlobalException; 9 | 10 | @EnableGlobalException 11 | @Configuration 12 | @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) 13 | @ConditionalOnProperty(value = "spring.coffee.web.global-exception", matchIfMissing = true, havingValue = "true") 14 | @AutoConfigureAfter(GlobalResultEnablerConfiguration.class) 15 | public class GlobalExceptionEnablerConfiguration { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-web/src/main/java/site/zido/coffee/autoconfigure/web/GlobalResultEnablerConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.web; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | import site.zido.coffee.mvc.rest.EnableGlobalResult; 8 | 9 | @EnableGlobalResult 10 | @Configuration 11 | @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) 12 | @ConditionalOnProperty(value = "spring.coffee.web.global-result", matchIfMissing = true, havingValue = "true") 13 | @Import(JavaxExceptionEnablerConfiguration.class) 14 | public class GlobalResultEnablerConfiguration { 15 | } 16 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-web/src/main/java/site/zido/coffee/autoconfigure/web/JavaxExceptionEnablerConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.web; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @author zido 9 | */ 10 | @Configuration 11 | @ConditionalOnClass(name = "javax.validation.ConstraintViolationException") 12 | @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) 13 | public class JavaxExceptionEnablerConfiguration { 14 | } 15 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-web/src/main/java/site/zido/coffee/autoconfigure/web/JsonAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.web; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 12 | import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; 13 | import org.springframework.context.annotation.Configuration; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * json配置 19 | * 20 | * @author zido 21 | */ 22 | @Configuration(proxyBeanMethods = false) 23 | @ConditionalOnBean({ObjectMapper.class}) 24 | @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) 25 | @ConditionalOnProperty(prefix = "spring.coffee.json.auto-switch", 26 | value = "enable", 27 | havingValue = "true", 28 | matchIfMissing = true) 29 | @AutoConfigureAfter(JacksonAutoConfiguration.class) 30 | public class JsonAutoConfiguration { 31 | 32 | /** 33 | * 配置全局json序列化(开发模式) 34 | *

    35 | *
  • null参与序列化
  • 36 | *
37 | */ 38 | @Autowired 39 | public void setMapper(@Value("${spring.profiles.active:prod}") List profiles, 40 | ObjectMapper mapper) { 41 | if (profiles.contains("prod")) { 42 | prodObjectMapper(mapper); 43 | } 44 | } 45 | 46 | /** 47 | * 配置全局json序列化 48 | *
    49 | *
  • null不参与序列化
  • 50 | *
  • 属性不匹配会失败
  • 51 | *
52 | */ 53 | private void prodObjectMapper(ObjectMapper mapper) { 54 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 55 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-web/src/main/java/site/zido/coffee/autoconfigure/web/RequestLogEnablerConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.autoconfigure.web; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 5 | import site.zido.coffee.mvc.logger.EnableRequestLogger; 6 | 7 | @EnableRequestLogger 8 | @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) 9 | @ConditionalOnProperty(value = "spring.coffee.web.request-log", havingValue = "true") 10 | public class RequestLogEnablerConfiguration { 11 | } 12 | -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-web/src/main/resources/META-INF/spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [], 3 | "properties": [ 4 | { 5 | "name": "spring.coffee.json.auto-switch", 6 | "type": "java.lang.Boolean", 7 | "description": "设置是否自动根据环境切换json序列化行为" 8 | }, 9 | { 10 | "name": "spring.coffee.web.global-result", 11 | "type": "java.lang.Boolean", 12 | "description": "设置是否使用全局封装统一返回对象" 13 | }, 14 | { 15 | "name": "spring.coffee.web.global-exception", 16 | "type": "java.lang.Boolean", 17 | "description": "设置是否使用全局封装统一异常返回对象" 18 | } 19 | ], 20 | "hints": [] 21 | } -------------------------------------------------------------------------------- /coffee-autoconfigures/coffee-autoconfigure-web/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | site.zido.coffee.autoconfigure.web.JsonAutoConfiguration,\ 3 | site.zido.coffee.autoconfigure.web.GlobalResultEnablerConfiguration,\ 4 | site.zido.coffee.autoconfigure.web.GlobalExceptionEnablerConfiguration,\ 5 | site.zido.coffee.autoconfigure.web.CoffeeErrorMvcAutoConfiguration,\ 6 | site.zido.coffee.autoconfigure.web.RequestLogEnablerConfiguration -------------------------------------------------------------------------------- /coffee-autoconfigures/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-common-builder 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-autoconfigures 13 | pom 14 | 15 | 16 | coffee-autoconfigure-web 17 | coffee-autoconfigure-rest-security 18 | coffee-autoconfigure-extra 19 | 20 | 21 | 22 | site.zido 23 | coffee-autoconfigure-core 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-configuration-processor 28 | true 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-autoconfigure 33 | 34 | 35 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-modules 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-core 13 | 14 | 15 | 16 | org.springframework 17 | spring-context 18 | 19 | 20 | javax.validation 21 | validation-api 22 | true 23 | 24 | 25 | net.jcip 26 | jcip-annotations 27 | 1.0 28 | 29 | 30 | org.junit.jupiter 31 | junit-jupiter-engine 32 | 33 | 34 | org.junit.platform 35 | junit-platform-runner 36 | test 37 | 38 | 39 | org.assertj 40 | assertj-core 41 | test 42 | 43 | 44 | org.mockito 45 | mockito-core 46 | test 47 | 48 | 49 | org.mockito 50 | mockito-junit-jupiter 51 | test 52 | 53 | 54 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/Coffee.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core; 2 | 3 | public class Coffee { 4 | public static final String VERSION = "0.3.0-SNAPSHOT"; 5 | } 6 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/constants/Patterns.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.constants; 2 | 3 | public class Patterns { 4 | public static final String PHONE_PATTERN = "^1[123456789][\\d]{9}$"; 5 | } 6 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/message/CoffeeMessageSource.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.message; 2 | 3 | import org.springframework.context.support.MessageSourceAccessor; 4 | import org.springframework.context.support.ResourceBundleMessageSource; 5 | 6 | public class CoffeeMessageSource extends ResourceBundleMessageSource { 7 | public CoffeeMessageSource() { 8 | setBasename("spring.coffee.messages"); 9 | } 10 | 11 | public static MessageSourceAccessor getAccessor() { 12 | return new MessageSourceAccessor(new CoffeeMessageSource()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/utils/BeanUtilsException.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.utils; 2 | 3 | /** 4 | * BeanUtils exception. 5 | * 6 | * @author zido 7 | */ 8 | public class BeanUtilsException extends RuntimeException { 9 | 10 | public BeanUtilsException(String message) { 11 | super(message); 12 | } 13 | 14 | public BeanUtilsException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/utils/InputConverter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.utils; 2 | 3 | import org.springframework.lang.Nullable; 4 | 5 | import java.lang.reflect.ParameterizedType; 6 | import java.util.Objects; 7 | 8 | /** 9 | * 输入转换器接口,提供一个convertTo方法,转换到目标pojo 10 | *

11 | * 一般用于controller中传递对象转换到实际处理的entity或者pojo 12 | * 13 | * @param 14 | */ 15 | public interface InputConverter { 16 | 17 | /** 18 | * Convert to domain.(shallow) 19 | * 20 | * @return new domain with same value(not null) 21 | */ 22 | @SuppressWarnings("unchecked") 23 | default DOMAIN convertTo() { 24 | ParameterizedType currentType = parameterizedType(); 25 | 26 | Objects.requireNonNull(currentType, "Cannot fetch actual type because parameterized type is null"); 27 | 28 | Class domainClass = (Class) currentType.getActualTypeArguments()[0]; 29 | 30 | return BeanUtils.transformFrom(this, domainClass); 31 | } 32 | 33 | default void update(DOMAIN domain) { 34 | BeanUtils.updateProperties(this, domain); 35 | } 36 | 37 | /** 38 | * Get parameterized type. 39 | * 40 | * @return parameterized type or null 41 | */ 42 | @Nullable 43 | default ParameterizedType parameterizedType() { 44 | return ReflectionUtils.getParameterizedType(InputConverter.class, this.getClass()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/utils/OutputConverter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.utils; 2 | 3 | import org.springframework.lang.NonNull; 4 | 5 | /** 6 | * 输出转换器接口,提供一个convertFrom方法,将pojo转换到其他对象 7 | *

8 | * 一般用于在逻辑处理完后返回DTO对象 9 | * 10 | * @param the implementation class type 11 | * @param domain type 12 | * @author zido 13 | */ 14 | public interface OutputConverter, DOMAIN> { 15 | 16 | /** 17 | * Convert from domain.(shallow) 18 | * 19 | * @param domain domain data 20 | * @return converted dto data 21 | */ 22 | @SuppressWarnings("unchecked") 23 | @NonNull 24 | default DTO convertFrom(@NonNull DOMAIN domain) { 25 | 26 | BeanUtils.updateProperties(domain, this); 27 | 28 | return (DTO) this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/utils/RandomUtils.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.utils; 2 | 3 | import java.util.Random; 4 | import java.util.concurrent.ThreadLocalRandom; 5 | 6 | public final class RandomUtils { 7 | 8 | private RandomUtils() { 9 | } 10 | 11 | private static Random getRandom() { 12 | return ThreadLocalRandom.current(); 13 | } 14 | 15 | public static String lowerStr(int len) { 16 | return str(len, 'a', 'z'); 17 | } 18 | 19 | public static String upperStr(int len) { 20 | return str(len, 'A', 'Z'); 21 | } 22 | 23 | public static String numeric(int len) { 24 | return str(len, '0', '9'); 25 | } 26 | 27 | public static String ascii(int len) { 28 | return str(len, 32, 126); 29 | } 30 | 31 | public static String str(int len, int leftLimit, int rightLimit) { 32 | Random random = getRandom(); 33 | return random.ints(leftLimit, rightLimit + 1) 34 | .limit(len) 35 | .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) 36 | .toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/utils/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.utils; 2 | 3 | import org.springframework.lang.NonNull; 4 | import org.springframework.lang.Nullable; 5 | import org.springframework.util.Assert; 6 | 7 | import java.lang.reflect.ParameterizedType; 8 | import java.lang.reflect.Type; 9 | 10 | /** 11 | * Reflection utilities. 12 | * 13 | * @author zido 14 | */ 15 | public class ReflectionUtils { 16 | 17 | private ReflectionUtils() { 18 | } 19 | 20 | /** 21 | * Gets parameterized type. 22 | * 23 | * @param superType super type must not be null (super class or super interface) 24 | * @param genericTypes generic type array 25 | * @return parameterized type of the interface or null if it is mismatch 26 | */ 27 | @Nullable 28 | public static ParameterizedType getParameterizedType(@NonNull Class superType, Type... genericTypes) { 29 | Assert.notNull(superType, "Interface or super type must not be null"); 30 | 31 | ParameterizedType currentType = null; 32 | 33 | for (Type genericType : genericTypes) { 34 | if (genericType instanceof ParameterizedType) { 35 | ParameterizedType parameterizedType = (ParameterizedType) genericType; 36 | if (parameterizedType.getRawType().getTypeName().equals(superType.getTypeName())) { 37 | currentType = parameterizedType; 38 | break; 39 | } 40 | } 41 | } 42 | 43 | return currentType; 44 | } 45 | 46 | /** 47 | * Gets parameterized type. 48 | * 49 | * @param interfaceType interface type must not be null 50 | * @param implementationClass implementation class of the interface must not be null 51 | * @return parameterized type of the interface or null if it is mismatch 52 | */ 53 | @Nullable 54 | public static ParameterizedType getParameterizedType(@NonNull Class interfaceType, Class implementationClass) { 55 | Assert.notNull(interfaceType, "Interface type must not be null"); 56 | Assert.isTrue(interfaceType.isInterface(), "The give type must be an interface"); 57 | 58 | if (implementationClass == null) { 59 | // If the super class is Object parent then return null 60 | return null; 61 | } 62 | 63 | // Get parameterized type 64 | ParameterizedType currentType = getParameterizedType(interfaceType, implementationClass.getGenericInterfaces()); 65 | 66 | if (currentType != null) { 67 | // return the current type 68 | return currentType; 69 | } 70 | 71 | Class superclass = implementationClass.getSuperclass(); 72 | 73 | return getParameterizedType(interfaceType, superclass); 74 | } 75 | 76 | /** 77 | * Gets parameterized type by super class. 78 | * 79 | * @param superClassType super class type must not be null 80 | * @param extensionClass extension class 81 | * @return parameterized type or null 82 | */ 83 | @Nullable 84 | public static ParameterizedType getParameterizedTypeBySuperClass(@NonNull Class superClassType, Class extensionClass) { 85 | 86 | if (extensionClass == null) { 87 | return null; 88 | } 89 | 90 | return getParameterizedType(superClassType, extensionClass.getGenericSuperclass()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/utils/SpringUtils.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.utils; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | 5 | public final class SpringUtils { 6 | private SpringUtils() { 7 | } 8 | 9 | public static T getBeanOrNull(ApplicationContext context, Class type) { 10 | String[] userDetailsBeanNames = context.getBeanNamesForType(type); 11 | if (userDetailsBeanNames.length != 1) { 12 | return null; 13 | } 14 | 15 | return context.getBean(userDetailsBeanNames[0], type); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/utils/SystemClock.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.utils; 2 | 3 | import java.util.concurrent.ScheduledExecutorService; 4 | import java.util.concurrent.ScheduledThreadPoolExecutor; 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.concurrent.atomic.AtomicLong; 7 | 8 | /** 9 | * 内存缓存的高并发适用钟摆 10 | * 11 | * @author zido 12 | */ 13 | public class SystemClock { 14 | private final long period; 15 | private final AtomicLong now; 16 | 17 | private SystemClock(long period) { 18 | this.period = period; 19 | this.now = new AtomicLong(System.currentTimeMillis()); 20 | scheduleClockUpdating(); 21 | } 22 | 23 | private static SystemClock instance() { 24 | return InstanceHolder.INSTANCE; 25 | } 26 | 27 | /** 28 | * Now time millis. 29 | * 30 | * @return the long 31 | */ 32 | public static long now() { 33 | return instance().currentTimeMillis(); 34 | } 35 | 36 | private void scheduleClockUpdating() { 37 | 38 | ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1, 39 | r -> { 40 | Thread thread = new Thread(r, "System Clock"); 41 | thread.setDaemon(true); 42 | return thread; 43 | }); 44 | scheduler.scheduleAtFixedRate(() -> now.set(System.currentTimeMillis()), period, period, TimeUnit.MILLISECONDS); 45 | } 46 | 47 | private long currentTimeMillis() { 48 | return now.get(); 49 | } 50 | 51 | private static class InstanceHolder { 52 | /** 53 | * The Instance. 54 | */ 55 | static final SystemClock INSTANCE = new SystemClock(1); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/validations/Phone.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.validations; 2 | 3 | 4 | import javax.validation.Constraint; 5 | import javax.validation.Payload; 6 | import java.lang.annotation.*; 7 | 8 | /** 9 | * 手机号校验注解 10 | * 11 | * @author zido 12 | */ 13 | @Target({ElementType.PARAMETER, ElementType.FIELD}) 14 | @Documented 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Constraint(validatedBy = PhoneValidator.class) 17 | public @interface Phone { 18 | String message() default "手机号错误"; 19 | 20 | Class[] groups() default {}; 21 | 22 | Class[] payload() default {}; 23 | } 24 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/main/java/site/zido/coffee/core/validations/PhoneValidator.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.validations; 2 | 3 | 4 | import javax.validation.ConstraintValidator; 5 | import javax.validation.ConstraintValidatorContext; 6 | 7 | import static site.zido.coffee.core.constants.Patterns.PHONE_PATTERN; 8 | 9 | /** 10 | * @author zido 11 | */ 12 | public class PhoneValidator implements ConstraintValidator { 13 | 14 | @Override 15 | public void initialize(Phone constraintAnnotation) { 16 | 17 | } 18 | 19 | @Override 20 | public boolean isValid(String value, ConstraintValidatorContext context) { 21 | return value == null || (value.matches(PHONE_PATTERN)); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/test/java/site/zido/coffee/core/common/utils/ExpireMapTest.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.common.utils; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.RepeatedTest; 5 | import site.zido.coffee.core.utils.maps.expire.ExpireMap; 6 | 7 | public class ExpireMapTest { 8 | 9 | @RepeatedTest(20) 10 | public void testTTL() { 11 | ExpireMap map = new ExpireMap<>(); 12 | map.set("user", "1", 60 * 1000); 13 | long ttl = map.ttl("user"); 14 | Assertions.assertTrue(ttl > 0, "返回超时时间应大于0且小于等于60000"); 15 | Assertions.assertTrue(ttl <= 60 * 1000, "返回超时时间应小于等于60000"); 16 | ttl = map.ttl("user"); 17 | Assertions.assertTrue(ttl > 0, "第二次获取ttl,同样应返回超时时间应大于0且小于等于60000"); 18 | Assertions.assertTrue(ttl <= 60 * 1000, "第二次获取ttl,同样应返回超时时间应小于等于60000"); 19 | } 20 | 21 | @RepeatedTest(20) 22 | public void testTTLWhenForever() { 23 | ExpireMap map = new ExpireMap<>(); 24 | map.set("user", "1"); 25 | long ttl = map.ttl("user"); 26 | Assertions.assertEquals(-1, ttl, "返回超时时间应为-1"); 27 | ttl = map.ttl("user"); 28 | Assertions.assertEquals(-1, ttl, "第二次操作返回超时时间应为-1"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /coffee-modules/coffee-core/src/test/java/site/zido/coffee/core/common/utils/SystemClockTest.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.core.common.utils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import site.zido.coffee.core.utils.SystemClock; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | public class SystemClockTest { 9 | @Test 10 | public void testClock() throws InterruptedException { 11 | long now = SystemClock.now(); 12 | Thread.sleep(20); 13 | long last = SystemClock.now(); 14 | assertThat(last).isNotEqualTo(now); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-modules 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-extra 13 | 14 | Some Tools For Coffee Spring Boot 15 | 16 | 17 | 18 | site.zido 19 | coffee-core 20 | 21 | 22 | com.github.kstyrc 23 | embedded-redis 24 | test 25 | 26 | 27 | redis.clients 28 | jedis 29 | test 30 | 31 | 32 | junit 33 | junit 34 | test 35 | 36 | 37 | org.mockito 38 | mockito-core 39 | test 40 | 41 | 42 | org.slf4j 43 | slf4j-simple 44 | test 45 | 46 | 47 | org.springframework.data 48 | spring-data-redis 49 | compile 50 | true 51 | 52 | 53 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/AbstractLimiterInvoker.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | /** 4 | * aop调用invoker 5 | * 6 | * @author zido 7 | */ 8 | public class AbstractLimiterInvoker { 9 | private LimiterErrorHandler errorHandler; 10 | 11 | protected AbstractLimiterInvoker() { 12 | this(new SimpleLimiterErrorHandler()); 13 | } 14 | 15 | protected AbstractLimiterInvoker(LimiterErrorHandler errorHandler) { 16 | this.errorHandler = errorHandler; 17 | } 18 | 19 | public LimiterErrorHandler getErrorHandler() { 20 | return errorHandler; 21 | } 22 | 23 | public void setErrorHandler(LimiterErrorHandler errorHandler) { 24 | this.errorHandler = errorHandler; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/AbstractLimiterOperationSource.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.core.BridgeMethodResolver; 6 | import org.springframework.core.MethodClassKey; 7 | import org.springframework.util.ClassUtils; 8 | 9 | import java.lang.reflect.Method; 10 | import java.lang.reflect.Modifier; 11 | import java.util.Collection; 12 | import java.util.Collections; 13 | import java.util.Map; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | 16 | /** 17 | * 注解扫描结果的缓存中心,包括类,方法 18 | * 19 | * @author zido 20 | */ 21 | public abstract class AbstractLimiterOperationSource implements LimiterOperationSource { 22 | private final static Collection NULL_CACHING_ATTRIBUTE = Collections.emptyList(); 23 | private final Logger logger = LoggerFactory.getLogger(getClass()); 24 | private final Map> attributeCache = new ConcurrentHashMap<>(16); 25 | 26 | @Override 27 | public Collection getLimiterOperations(Method method, Class targetClass) { 28 | if (method.getDeclaringClass() == Object.class) { 29 | return null; 30 | } 31 | Object limiterKey = new MethodClassKey(method, targetClass); 32 | Collection cached = this.attributeCache.get(limiterKey); 33 | if (cached != null) { 34 | return (cached != NULL_CACHING_ATTRIBUTE ? cached : null); 35 | } else { 36 | Collection cacheOps = computeLimiterOperation(method, targetClass); 37 | if (cacheOps != null) { 38 | logger.debug("Adding limiter method '{}' with attribute: {}", method.getName(), cacheOps); 39 | this.attributeCache.put(limiterKey, cacheOps); 40 | } else { 41 | this.attributeCache.put(limiterKey, NULL_CACHING_ATTRIBUTE); 42 | } 43 | return cacheOps; 44 | } 45 | } 46 | 47 | private Collection computeLimiterOperation(Method method, Class targetClass) { 48 | if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { 49 | return null; 50 | } 51 | Method specMethod = ClassUtils.getMostSpecificMethod(method, targetClass); 52 | specMethod = BridgeMethodResolver.findBridgedMethod(specMethod); 53 | Collection opDef = resolve(specMethod, specMethod.getDeclaringClass()); 54 | if (opDef != null) { 55 | return opDef; 56 | } 57 | if (specMethod != method) { 58 | return resolve(method, method.getDeclaringClass()); 59 | } 60 | return null; 61 | } 62 | 63 | private Collection resolve(Method method, Class clazz) { 64 | Collection opDef = findLimiterOperations(method); 65 | if (opDef != null) { 66 | return opDef; 67 | } 68 | opDef = findLimiterOperations(clazz.getDeclaringClass()); 69 | if (opDef != null && ClassUtils.isUserLevelMethod(method)) { 70 | return opDef; 71 | } 72 | return null; 73 | } 74 | 75 | protected abstract Collection findLimiterOperations(Method method); 76 | 77 | protected abstract Collection findLimiterOperations(Class clazz); 78 | 79 | protected boolean allowPublicMethodsOnly() { 80 | return false; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/AnnotationDrivenLimiterBeanProcessor.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import org.springframework.aop.config.AopConfigUtils; 4 | import org.springframework.beans.factory.config.BeanDefinition; 5 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 6 | import org.springframework.beans.factory.support.RootBeanDefinition; 7 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 8 | import org.springframework.core.type.AnnotationMetadata; 9 | 10 | /** 11 | * 向容器中注入PointcutAdvisor切点{@link BeanFactoryLimiterOperationSourceAdvisor} 12 | * 13 | * @author zido 14 | */ 15 | public class AnnotationDrivenLimiterBeanProcessor implements ImportBeanDefinitionRegistrar { 16 | private static final String LIMITER_ASPECT_CLASS_NAME = "site.zido.common.AnnotationLimiterAspect"; 17 | 18 | @Override 19 | public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 20 | AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); 21 | RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanFactoryLimiterOperationSourceAdvisor.class); 22 | //设置标识为服务 23 | beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 24 | registry.registerBeanDefinition(LIMITER_ASPECT_CLASS_NAME, beanDefinition); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/AnnotationLimiterOperationSource.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.LinkedHashSet; 7 | import java.util.Set; 8 | 9 | /** 10 | * 注解扫描执行 11 | * 12 | * @author zido 13 | */ 14 | public class AnnotationLimiterOperationSource extends AbstractLimiterOperationSource { 15 | private final Set annotationParsers; 16 | private final boolean publicMethodsOnly; 17 | 18 | public AnnotationLimiterOperationSource() { 19 | this(true); 20 | } 21 | 22 | public AnnotationLimiterOperationSource(boolean publicMethodsOnly) { 23 | this.publicMethodsOnly = publicMethodsOnly; 24 | this.annotationParsers = new LinkedHashSet<>(1); 25 | this.annotationParsers.add(new SpringLimiterAnnotationParser()); 26 | } 27 | 28 | @Override 29 | protected Collection findLimiterOperations(Method method) { 30 | return determine(parser -> parser.parseLimiterAnnotations(method)); 31 | } 32 | 33 | @Override 34 | protected Collection findLimiterOperations(Class clazz) { 35 | return determine(parser -> parser.parseLimiterAnnotations(clazz)); 36 | } 37 | 38 | protected Collection determine(LimiterOperationProvider provider) { 39 | ArrayList ops = null; 40 | for (LimiterAnnotationParser parser : annotationParsers) { 41 | Collection operations = provider.getLimiterOperations(parser); 42 | if (operations != null) { 43 | if (ops == null) { 44 | ops = new ArrayList<>(); 45 | } 46 | ops.addAll(operations); 47 | } 48 | } 49 | return ops; 50 | } 51 | 52 | @Override 53 | protected boolean allowPublicMethodsOnly() { 54 | return this.publicMethodsOnly; 55 | } 56 | 57 | protected interface LimiterOperationProvider { 58 | 59 | /** 60 | * Return the {@link LimiterOperation} instance(s) provided by the specified parser. 61 | * 62 | * @param parser the parser to use 63 | * @return the cache operations, or {@code null} if none found 64 | */ 65 | Collection getLimiterOperations(LimiterAnnotationParser parser); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/BeanFactoryLimiterOperationSourceAdvisor.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import org.springframework.aop.Pointcut; 4 | import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor; 5 | import org.springframework.aop.support.StaticMethodMatcherPointcut; 6 | import org.springframework.util.CollectionUtils; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * advisor 12 | * 13 | * @author zido 14 | */ 15 | public class BeanFactoryLimiterOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { 16 | private static final long serialVersionUID = 3093592396602137911L; 17 | private LimiterOperationSource limiterOperationSource; 18 | private final StaticMethodMatcherPointcut pointcut = new StaticMethodMatcherPointcut() { 19 | @Override 20 | public boolean matches(Method method, Class targetClass) { 21 | return (getLimiterOperationSource() != null 22 | && !CollectionUtils.isEmpty(getLimiterOperationSource().getLimiterOperations(method, targetClass))); 23 | } 24 | }; 25 | 26 | @Override 27 | public Pointcut getPointcut() { 28 | return pointcut; 29 | } 30 | 31 | public LimiterOperationSource getLimiterOperationSource() { 32 | return limiterOperationSource; 33 | } 34 | 35 | public void setLimiterOperationSource(LimiterOperationSource limiterOperationSource) { 36 | this.limiterOperationSource = limiterOperationSource; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/EnableLimiter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import org.springframework.context.annotation.AdviceMode; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.core.Ordered; 6 | 7 | import java.lang.annotation.*; 8 | 9 | /** 10 | * 启用limiter,与{@link org.springframework.cache.annotation.EnableCaching}类似 11 | * 可以选择是否使用cglib 12 | * 13 | * @author zido 14 | */ 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Documented 18 | @Import(LimiterConfigurationSelector.class) 19 | public @interface EnableLimiter { 20 | 21 | /** 22 | * 是否强制使用cglib代理 23 | * 24 | * @return true/false 25 | */ 26 | boolean proxyTargetClass() default false; 27 | 28 | /** 29 | * 选择代理模式 30 | */ 31 | AdviceMode mode() default AdviceMode.PROXY; 32 | 33 | /** 34 | * 代理顺序 35 | */ 36 | int order() default Ordered.LOWEST_PRECEDENCE; 37 | } 38 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/FrequencyLimiter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | /** 4 | * 节点频率限制器,限制某个用户(key区分)的某个动作在特定时间内只能有一次 5 | *

6 | * 秒为单位 7 | * 8 | * @author zido 9 | */ 10 | public interface FrequencyLimiter { 11 | 12 | /** 13 | * 尝试执行 14 | * 15 | * @param key key 16 | * @param timeout 设置如果本次拿到执行权的有效时间 17 | * @return 如果拿到了执行权则返回0,否则返回还需等待的时间 18 | */ 19 | long tryGet(String key, long timeout); 20 | } 21 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/Limiter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import java.lang.annotation.*; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | /** 7 | * 限流器注解,可以根据任何资源进行限流{@link #key()} 8 | * 9 | * @author zido 10 | */ 11 | @Target({ElementType.TYPE, ElementType.METHOD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Inherited 14 | @Documented 15 | public @interface Limiter { 16 | String key() default ""; 17 | 18 | long timeout() default 55; 19 | 20 | TimeUnit unit() default TimeUnit.SECONDS; 21 | } 22 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/LimiterAnnotationParser.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Collection; 5 | 6 | /** 7 | * 注解解析器 8 | * 9 | * @author zido 10 | */ 11 | public interface LimiterAnnotationParser { 12 | Collection parseLimiterAnnotations(Class type); 13 | 14 | Collection parseLimiterAnnotations(Method method); 15 | } 16 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/LimiterConfigurationSelector.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import org.springframework.context.annotation.AdviceMode; 4 | import org.springframework.context.annotation.AdviceModeImportSelector; 5 | import org.springframework.context.annotation.AutoProxyRegistrar; 6 | 7 | public class LimiterConfigurationSelector extends AdviceModeImportSelector { 8 | @Override 9 | protected String[] selectImports(AdviceMode adviceMode) { 10 | return new String[]{ 11 | AutoProxyRegistrar.class.getName(), 12 | ProxyLimiterConfiguration.class.getName() 13 | }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/LimiterErrorHandler.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | /** 4 | * 异常处理器 5 | * 6 | * @author zido 7 | */ 8 | public interface LimiterErrorHandler { 9 | /** 10 | * 处理通用异常 11 | * 12 | * @param exception exception 13 | * @param key 注解的key 14 | */ 15 | void handleError(RuntimeException exception, Object key); 16 | 17 | /** 18 | * 被限流时的处理 19 | * 20 | * @param exception exception 21 | */ 22 | void handleOnLimited(LimiterException exception); 23 | } 24 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/LimiterException.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | /** 4 | * 当方法调用频率被限制时,抛出的异常 5 | * 6 | * @author zido 7 | * @see LimiterErrorHandler 8 | * @see FrequencyLimiter 9 | */ 10 | public class LimiterException extends RuntimeException { 11 | private static final long serialVersionUID = -8703559654766706392L; 12 | /** 13 | * 限制的key 14 | */ 15 | private Object key; 16 | /** 17 | * 剩余时间 18 | */ 19 | private long last; 20 | /** 21 | * 下次需要的时间 22 | */ 23 | private long requireTime; 24 | 25 | public LimiterException() { 26 | 27 | } 28 | 29 | public LimiterException(Object key, long last, long requireTime) { 30 | super(String.format("频率过高,请在 %d 秒后重试", last)); 31 | this.key = key; 32 | this.last = last; 33 | this.requireTime = requireTime; 34 | } 35 | 36 | public Object getKey() { 37 | return key; 38 | } 39 | 40 | public long getLast() { 41 | return last; 42 | } 43 | 44 | public long getRequireTime() { 45 | return requireTime; 46 | } 47 | 48 | public void setKey(Object key) { 49 | this.key = key; 50 | } 51 | 52 | public void setLast(long last) { 53 | this.last = last; 54 | } 55 | 56 | public void setRequireTime(long requireTime) { 57 | this.requireTime = requireTime; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/LimiterOperation.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public class LimiterOperation { 6 | private final String key; 7 | private final long timeout; 8 | private final TimeUnit unit; 9 | private final String name; 10 | 11 | public LimiterOperation(Builder builder) { 12 | this.key = builder.getKey(); 13 | this.timeout = builder.getTimeout(); 14 | this.unit = builder.getUnit(); 15 | name = builder.getName(); 16 | } 17 | 18 | public String getKey() { 19 | return key; 20 | } 21 | 22 | public long getTimeout() { 23 | return timeout; 24 | } 25 | 26 | public TimeUnit getUnit() { 27 | return unit; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public static class Builder { 35 | private String key = ""; 36 | private long timeout = 55; 37 | private TimeUnit unit = TimeUnit.SECONDS; 38 | private String name = ""; 39 | 40 | public String getKey() { 41 | return key; 42 | } 43 | 44 | public void setKey(String key) { 45 | this.key = key; 46 | } 47 | 48 | public long getTimeout() { 49 | return timeout; 50 | } 51 | 52 | public void setTimeout(long timeout) { 53 | this.timeout = timeout; 54 | } 55 | 56 | public TimeUnit getUnit() { 57 | return unit; 58 | } 59 | 60 | public void setUnit(TimeUnit unit) { 61 | this.unit = unit; 62 | } 63 | 64 | 65 | public LimiterOperation build() { 66 | return new LimiterOperation(this); 67 | } 68 | 69 | public String getName() { 70 | return name; 71 | } 72 | 73 | public void setName(String name) { 74 | this.name = name; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/LimiterOperationSource.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Collection; 5 | 6 | public interface LimiterOperationSource { 7 | Collection getLimiterOperations(Method method, Class targetClass); 8 | } 9 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/LimiterRootObject.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | public class LimiterRootObject { 6 | 7 | private final Method method; 8 | 9 | private final Object[] args; 10 | 11 | private final Object target; 12 | 13 | private final Class targetClass; 14 | 15 | public LimiterRootObject(Method method, Object[] args, Object target, Class targetClass) { 16 | this.method = method; 17 | this.args = args; 18 | this.target = target; 19 | this.targetClass = targetClass; 20 | } 21 | 22 | public Method getMethod() { 23 | return method; 24 | } 25 | 26 | public Object[] getArgs() { 27 | return args; 28 | } 29 | 30 | public Object getTarget() { 31 | return target; 32 | } 33 | 34 | public Class getTargetClass() { 35 | return targetClass; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/MemoryFrequencyLimiter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import site.zido.coffee.core.utils.maps.expire.ExpireMap; 4 | 5 | /** 6 | * 基于内存的频率限制 7 | * 8 | * @author zido 9 | */ 10 | public class MemoryFrequencyLimiter implements FrequencyLimiter { 11 | private static final Object PRESENT = new Object(); 12 | private ExpireMap expireMap = new ExpireMap<>(); 13 | private String prefix = ""; 14 | 15 | public MemoryFrequencyLimiter() { 16 | 17 | } 18 | 19 | public MemoryFrequencyLimiter(String prefix) { 20 | this.prefix = prefix; 21 | } 22 | 23 | @Override 24 | public long tryGet(String key, long timeout) { 25 | key = getKey(key); 26 | 27 | if (expireMap.setNx(key, PRESENT, timeout)) { 28 | return 0; 29 | } 30 | long ttl = expireMap.ttl(key); 31 | if (ttl == -1) { 32 | throw new IllegalStateException(String.format("key:%s永久存在,无法获取执行", key)); 33 | } 34 | return ttl; 35 | } 36 | 37 | protected String getKey(String key) { 38 | return prefix + key; 39 | } 40 | 41 | public void setExpireMap(ExpireMap expireMap) { 42 | this.expireMap = expireMap; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/ProxyLimiterConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import org.springframework.beans.factory.config.BeanDefinition; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Role; 7 | 8 | /** 9 | * @author zido 10 | */ 11 | @Configuration 12 | @Role(BeanDefinition.ROLE_INFRASTRUCTURE) 13 | public class ProxyLimiterConfiguration { 14 | @Bean 15 | @Role(BeanDefinition.ROLE_INFRASTRUCTURE) 16 | public BeanFactoryLimiterOperationSourceAdvisor limiterAdvisor(LimiterInterceptor interceptor) { 17 | BeanFactoryLimiterOperationSourceAdvisor advisor = new BeanFactoryLimiterOperationSourceAdvisor(); 18 | advisor.setAdvice(interceptor); 19 | advisor.setLimiterOperationSource(limiterOperationSource()); 20 | return advisor; 21 | } 22 | 23 | @Bean 24 | @Role(BeanDefinition.ROLE_INFRASTRUCTURE) 25 | public LimiterOperationSource limiterOperationSource() { 26 | return new AnnotationLimiterOperationSource(); 27 | } 28 | 29 | @Bean 30 | @Role(BeanDefinition.ROLE_INFRASTRUCTURE) 31 | public LimiterInterceptor interceptor(FrequencyLimiter limiter) { 32 | LimiterInterceptor interceptor = new LimiterInterceptor(); 33 | interceptor.setLimiterOperationSource(limiterOperationSource()); 34 | interceptor.setLimiter(limiter); 35 | return interceptor; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/ProxyLimiterConfigurer.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | /** 4 | * limiter配置 5 | */ 6 | public interface ProxyLimiterConfigurer { 7 | FrequencyLimiter limiter(); 8 | } 9 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/RedisFrequencyLimiter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import org.springframework.data.redis.core.RedisTemplate; 4 | import org.springframework.util.Assert; 5 | import site.zido.coffee.core.utils.SystemClock; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * 基于redis的频率限制器 11 | *

12 | * 使用场景:手机号发送短信验证码一分钟不能超过一次(建议时间设置比实际情况略小 13 | * 14 | * @author zido 15 | */ 16 | public class RedisFrequencyLimiter implements FrequencyLimiter { 17 | 18 | private static final String PRE = "coffee:limiter:"; 19 | private final String prefix; 20 | private final RedisTemplate template; 21 | 22 | public RedisFrequencyLimiter(String prefix, RedisTemplate template) { 23 | this.prefix = prefix; 24 | this.template = template; 25 | } 26 | 27 | public RedisFrequencyLimiter(RedisTemplate template) { 28 | this(PRE, template); 29 | } 30 | 31 | @Override 32 | public long tryGet(String key, long timeout) { 33 | Assert.isTrue(timeout > 1, "超时时间设定以秒为单位,并且需要大于一秒"); 34 | Assert.isTrue(timeout <= Integer.MAX_VALUE, "超时时间需要小于等于" + Integer.MAX_VALUE); 35 | long now = SystemClock.now(); 36 | now = now / 1000; 37 | String prefixedKey = prefix + key; 38 | Long expire = template.getExpire(prefixedKey, TimeUnit.MILLISECONDS); 39 | //redis集群可能无法准确过期,用ttl判断更准确 40 | if (expire != null) { 41 | //如果值永久有效将永远无法有效获取 42 | if (expire == -1) { 43 | throw new IllegalStateException(String.format("键[%s]永久有效,需要排查", prefixedKey)); 44 | } 45 | if (expire > 0) { 46 | return expire; 47 | } 48 | } 49 | createTag(key, now, timeout); 50 | return 0; 51 | } 52 | 53 | private void createTag(String key, long date, long timeout) { 54 | template.opsForValue().set(prefix + key, date, timeout); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/SimpleLimiterErrorHandler.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | /** 4 | * @author zido 5 | */ 6 | public class SimpleLimiterErrorHandler implements LimiterErrorHandler { 7 | @Override 8 | public void handleError(RuntimeException exception, Object key) { 9 | throw exception; 10 | } 11 | 12 | @Override 13 | public void handleOnLimited(LimiterException exception) { 14 | throw exception; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/limiter/SpringLimiterAnnotationParser.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.limiter; 2 | 3 | import org.springframework.core.annotation.AnnotatedElementUtils; 4 | 5 | import java.lang.reflect.AnnotatedElement; 6 | import java.lang.reflect.Method; 7 | import java.util.ArrayList; 8 | import java.util.Collection; 9 | 10 | /** 11 | * 限流器注解解析器,负责解析限流器,以提供注解式限流支持 12 | * 13 | * @author zido 14 | * @see Limiter 15 | */ 16 | public class SpringLimiterAnnotationParser implements LimiterAnnotationParser { 17 | 18 | /** 19 | * 解析类上注解 20 | * 21 | * @param type target class type 22 | * @return operations 23 | */ 24 | @Override 25 | public Collection parseLimiterAnnotations(Class type) { 26 | return parse(type); 27 | } 28 | 29 | /** 30 | * 解析方法上的注解 31 | * 32 | * @param method target method 33 | * @return operations 34 | */ 35 | @Override 36 | public Collection parseLimiterAnnotations(Method method) { 37 | return parse(method); 38 | } 39 | 40 | protected Collection parse(AnnotatedElement ae) { 41 | Collection ops = new ArrayList<>(1); 42 | 43 | Collection limiters = AnnotatedElementUtils.getAllMergedAnnotations(ae, Limiter.class); 44 | if (!limiters.isEmpty()) { 45 | for (Limiter limiter : limiters) { 46 | ops.add(parseLimiterAnnotation(ae, limiter)); 47 | } 48 | } 49 | return ops; 50 | } 51 | 52 | private LimiterOperation parseLimiterAnnotation(AnnotatedElement ae, Limiter limiter) { 53 | LimiterOperation.Builder builder = new LimiterOperation.Builder(); 54 | builder.setName(ae.toString()); 55 | builder.setKey(limiter.key()); 56 | builder.setTimeout(limiter.timeout()); 57 | builder.setUnit(limiter.unit()); 58 | return builder.build(); 59 | } 60 | 61 | @Override 62 | public boolean equals(Object other) { 63 | return other instanceof SpringLimiterAnnotationParser; 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return SpringLimiterAnnotationParser.class.hashCode(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/lock/DistributedLockFactory.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.lock; 2 | 3 | import org.springframework.data.redis.connection.RedisConnectionFactory; 4 | import org.springframework.util.Assert; 5 | 6 | import java.nio.charset.Charset; 7 | import java.util.Map; 8 | import java.util.Objects; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.locks.Lock; 12 | 13 | public class DistributedLockFactory { 14 | private Map lockCache = new ConcurrentHashMap<>(); 15 | private RedisConnectionFactory redisConnectionFactory; 16 | private Charset charset; 17 | 18 | public RedisConnectionFactory getRedisConnectionFactory() { 19 | return redisConnectionFactory; 20 | } 21 | 22 | public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory) { 23 | this.redisConnectionFactory = redisConnectionFactory; 24 | } 25 | 26 | public Charset getCharset() { 27 | return charset; 28 | } 29 | 30 | public void setCharset(Charset charset) { 31 | this.charset = charset; 32 | } 33 | 34 | public Lock getLock(String key, long timeout, TimeUnit unit) { 35 | return lockCache.computeIfAbsent(new MultiKey(key, timeout, unit), multiKey -> new DistributedRedisLock(key, redisConnectionFactory, timeout, unit)); 36 | } 37 | 38 | static class MultiKey { 39 | private String key; 40 | private long timeout; 41 | private TimeUnit unit; 42 | 43 | MultiKey(String key, long timeout, TimeUnit unit) { 44 | Assert.hasLength(key, "lock key can't be null or empty"); 45 | Assert.state(timeout > 0, "超时时间还必须大于0"); 46 | this.key = key; 47 | this.timeout = timeout; 48 | this.unit = unit; 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) { 54 | return true; 55 | } 56 | if (o == null || getClass() != o.getClass()) { 57 | return false; 58 | } 59 | MultiKey multiKey = (MultiKey) o; 60 | return timeout == multiKey.timeout && 61 | Objects.equals(key, multiKey.key) && 62 | unit == multiKey.unit; 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(key, timeout, unit); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/package-info.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra; -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/security/AbstractSecurity.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.security; 2 | 3 | 4 | import java.nio.charset.Charset; 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.util.Arrays; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * 防重放攻击解决方案 12 | * 13 | * @author zido 14 | */ 15 | public abstract class AbstractSecurity { 16 | private static final String[] HEX_DIG_ITS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; 17 | private static Charset defaultCharset = Charset.forName("utf-8"); 18 | private String token; 19 | private long timeDifference = TimeUnit.MINUTES.toMillis(1); 20 | private long lastModifyTime = System.currentTimeMillis(); 21 | 22 | public AbstractSecurity(String token) { 23 | this.token = token; 24 | } 25 | 26 | private static String bytesToStr(byte[] bytes) { 27 | StringBuilder sb = new StringBuilder(); 28 | for (byte b : bytes) { 29 | sb.append(byteToHex(b)); 30 | } 31 | return sb.toString(); 32 | } 33 | 34 | private static String byteToHex(byte b) { 35 | int n = (int) b; 36 | if (n < 0) { 37 | n += 256; 38 | } 39 | final int div = n / 16; 40 | final int mod = n % 16; 41 | return HEX_DIG_ITS[div] + HEX_DIG_ITS[mod]; 42 | } 43 | 44 | /** 45 | * 校验签名 46 | * 47 | * @param timeStamp 时间戳 48 | * @param sign 签名串 49 | * @param params 待验证的参数 50 | * @return 是否验证通过 51 | */ 52 | public boolean validate(long timeStamp, String sign, Object... params) { 53 | return encode(timeStamp, params).equalsIgnoreCase(sign); 54 | } 55 | 56 | /** 57 | * 检测重放 58 | * 59 | * @param nonce nonce字段 60 | * @param timestamp 时间戳 61 | * @return false为重复/超时的请求,true为合理请求 62 | */ 63 | public boolean checkNonce(Object nonce, long timestamp) { 64 | long currentTimeMillis = System.currentTimeMillis(); 65 | //判断请求是否超时 66 | if (currentTimeMillis - timestamp > timeDifference) { 67 | return false; 68 | } 69 | //清空60s前的nonce集合 70 | if (currentTimeMillis - lastModifyTime > timeDifference) { 71 | clearNonce(); 72 | } 73 | //判断nonce是否存在 74 | if (addNonce(timestamp + "" + nonce)) { 75 | lastModifyTime = currentTimeMillis; 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | protected abstract void clearNonce(); 82 | 83 | protected abstract boolean addNonce(String nonce); 84 | 85 | /** 86 | * 加密数据 87 | * 88 | * @param timeStamp 时间戳 89 | * @param params 参数 90 | * @return 加密串 91 | */ 92 | public String encode(long timeStamp, Object... params) { 93 | String originValue = token + handleParams(params) + timeStamp + token; 94 | byte[] secretBytes; 95 | try { 96 | secretBytes = MessageDigest.getInstance("md5").digest( 97 | originValue.getBytes(defaultCharset)); 98 | } catch (NoSuchAlgorithmException e) { 99 | throw new RuntimeException(e); 100 | } 101 | return bytesToStr(secretBytes); 102 | } 103 | 104 | private String handleParams(Object... params) { 105 | return Arrays.stream(params) 106 | .map(Object::toString) 107 | .sorted() 108 | .reduce((String target, String current) -> target + current) 109 | .orElseThrow(() -> new IllegalArgumentException("params error")); 110 | } 111 | 112 | public void setTimeDifference(long timeDifference) { 113 | this.timeDifference = timeDifference; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/main/java/site/zido/coffee/extra/security/StringRedisTemplateSecurity.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.extra.security; 2 | 3 | import org.springframework.data.redis.core.StringRedisTemplate; 4 | 5 | /** 6 | * 可以直接注入使用 7 | *

8 | * 注意不同的业务(例如对接网易与对接其他第三方接口), 9 | * 在注入bean的时候,记得使用命名bean或者可以直接继承实现,注入下一层的bean 10 | * 11 | * @author zido 12 | */ 13 | public class StringRedisTemplateSecurity extends AbstractSecurity { 14 | 15 | private String nonceKey; 16 | private StringRedisTemplate template; 17 | 18 | public StringRedisTemplateSecurity(String token, String nonceKey, StringRedisTemplate template) { 19 | super(token); 20 | this.nonceKey = nonceKey; 21 | this.template = template; 22 | } 23 | 24 | @Override 25 | protected void clearNonce() { 26 | template.delete(nonceKey); 27 | } 28 | 29 | @Override 30 | protected boolean addNonce(String nonce) { 31 | return 1 == template.opsForSet().add(nonceKey, nonce); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /coffee-modules/coffee-extra/src/test/java/limiter/MemoryFrequencyLimiterTest.java: -------------------------------------------------------------------------------- 1 | package limiter; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import site.zido.coffee.core.utils.maps.expire.ExpireMap; 6 | import site.zido.coffee.extra.limiter.MemoryFrequencyLimiter; 7 | 8 | import static org.mockito.ArgumentMatchers.any; 9 | import static org.mockito.ArgumentMatchers.eq; 10 | import static org.mockito.Mockito.*; 11 | 12 | public class MemoryFrequencyLimiterTest { 13 | @Test 14 | public void testWhenTimeoutIsEqual() throws InterruptedException { 15 | @SuppressWarnings("unchecked") ExpireMap expireMap = mock(ExpireMap.class); 16 | MemoryFrequencyLimiter limiter = new MemoryFrequencyLimiter(); 17 | limiter.setExpireMap(expireMap); 18 | //当第一次设置必定成功 19 | when(expireMap.setNx(eq("test"), any(), eq(1L))).thenReturn(true); 20 | long test = limiter.tryGet("test", 1); 21 | verify(expireMap, never()).ttl(any()); 22 | Assertions.assertEquals(0L, test); 23 | //第二次设置时,因key已存在,则必定失败 24 | when(expireMap.setNx(eq("test"), any(), eq(1L))).thenReturn(false); 25 | when(expireMap.ttl("test")).thenReturn(1L); 26 | long last = limiter.tryGet("test", 1); 27 | Assertions.assertEquals(1L, last); 28 | } 29 | 30 | @Test 31 | public void testWhenTimeoutIsNotEqual() throws InterruptedException { 32 | @SuppressWarnings("unchecked") ExpireMap expireMap = mock(ExpireMap.class); 33 | MemoryFrequencyLimiter limiter = new MemoryFrequencyLimiter(); 34 | limiter.setExpireMap(expireMap); 35 | when(expireMap.setNx(eq("test"), any(), eq(1L))).thenReturn(true); 36 | long test = limiter.tryGet("test", 1); 37 | verify(expireMap, never()).ttl(any()); 38 | Assertions.assertEquals(0L, test); 39 | when(expireMap.setNx(eq("test"), any(), eq(2L))).thenReturn(false); 40 | when(expireMap.ttl("test")).thenReturn(1L); 41 | long last = limiter.tryGet("test", 2); 42 | Assertions.assertEquals(1, last); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-modules 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-rest-security 13 | 14 | 15 | 16 | site.zido 17 | coffee-core 18 | 19 | 20 | site.zido 21 | coffee-extra 22 | true 23 | 24 | 25 | site.zido 26 | coffee-webmvc 27 | 28 | 29 | org.springframework.security 30 | spring-security-web 31 | 32 | 33 | org.springframework.security 34 | spring-security-config 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | org.springframework 43 | spring-webmvc 44 | 45 | 46 | javax.servlet 47 | servlet-api 48 | 2.3 49 | provided 50 | 51 | 52 | io.jsonwebtoken 53 | jjwt 54 | 55 | 56 | org.springframework.data 57 | spring-data-redis 58 | provided 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/RestAuthenticationFailureHandler.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.security.core.AuthenticationException; 5 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 6 | import org.springframework.util.StringUtils; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author zido 15 | */ 16 | public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler { 17 | private MediaType mediaType; 18 | private String successBody; 19 | 20 | public RestAuthenticationFailureHandler() { 21 | } 22 | 23 | public RestAuthenticationFailureHandler(String successBody) { 24 | this(MediaType.APPLICATION_JSON, successBody); 25 | } 26 | 27 | public RestAuthenticationFailureHandler(MediaType mediaType, String successBody) { 28 | this.mediaType = mediaType; 29 | this.successBody = successBody; 30 | } 31 | 32 | public void setMediaType(MediaType mediaType) { 33 | this.mediaType = mediaType; 34 | } 35 | 36 | public void setSuccessBody(String successBody) { 37 | this.successBody = successBody; 38 | } 39 | 40 | @Override 41 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { 42 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 43 | if (this.mediaType != null) { 44 | response.setHeader("Content-Type", this.mediaType.toString()); 45 | } 46 | if (StringUtils.hasLength(successBody)) { 47 | response.getWriter().write(successBody); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/RestAuthenticationSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 6 | import org.springframework.util.StringUtils; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author zido 15 | */ 16 | public class RestAuthenticationSuccessHandler implements 17 | AuthenticationSuccessHandler { 18 | private MediaType mediaType; 19 | private String successBody; 20 | 21 | public RestAuthenticationSuccessHandler() { 22 | } 23 | 24 | public RestAuthenticationSuccessHandler(String successBody) { 25 | this(MediaType.APPLICATION_JSON, successBody); 26 | } 27 | 28 | public RestAuthenticationSuccessHandler(MediaType mediaType, String successBody) { 29 | this.mediaType = mediaType; 30 | this.successBody = successBody; 31 | } 32 | 33 | @Override 34 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { 35 | if (this.mediaType != null) { 36 | response.setHeader("Content-Type", this.mediaType.toString()); 37 | } 38 | if (StringUtils.hasLength(successBody)) { 39 | response.getWriter().write(successBody); 40 | } 41 | } 42 | 43 | public void setMediaType(MediaType mediaType) { 44 | this.mediaType = mediaType; 45 | } 46 | 47 | public void setSuccessBody(String successBody) { 48 | this.successBody = successBody; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/phone/CodeGenerator.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | /** 4 | * 验证码生成器 5 | * 6 | * @author zido 7 | */ 8 | public interface CodeGenerator { 9 | /** 10 | * 生成验证码 11 | * 12 | * @param phone 手机号 13 | * @return code 14 | */ 15 | String generateCode(String phone); 16 | } 17 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/phone/CodeValidator.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | /** 4 | * 验证码比对 5 | * 6 | * @author zido 7 | */ 8 | public interface CodeValidator { 9 | 10 | /** 11 | * 验证code 12 | * 13 | * @param originalCode 原code 14 | * @param inputCode 输入的code 15 | * @return true/false 16 | */ 17 | boolean validate(String originalCode, String inputCode); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/phone/CustomCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | import io.jsonwebtoken.lang.Assert; 4 | import org.springframework.beans.factory.InitializingBean; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | /** 11 | * 默认验证码生成器 12 | *

13 | * 具有一定可自定义功能,例如可控的动态长度,字符/数组组合 14 | * 15 | * @author zido 16 | */ 17 | public class CustomCodeGenerator implements CodeGenerator, InitializingBean { 18 | private static final char[] ARR_NUMBER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; 19 | private static final char[] ARR_LOWER_CHAR = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 20 | 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 21 | private static final char[] ARR_UPPER_CHAR = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 22 | 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; 23 | private int minLength = 6; 24 | private int maxLength = 6; 25 | private final List arr = new ArrayList<>(); 26 | private final Random random = new Random(); 27 | 28 | public CustomCodeGenerator(Mode... modes) { 29 | if (modes == null || modes.length == 0) { 30 | this.setMode(Mode.NUMBER); 31 | } else { 32 | this.setMode(modes); 33 | } 34 | } 35 | 36 | @Override 37 | public String generateCode(String phone) { 38 | StringBuilder sb = new StringBuilder(); 39 | for (int i = 0; i < maxLength; i++) { 40 | int index = random.nextInt(arr.size()); 41 | sb.append(arr.get(index)); 42 | if (sb.length() >= minLength) { 43 | if (random.nextBoolean()) { 44 | break; 45 | } 46 | } 47 | } 48 | return sb.toString(); 49 | } 50 | 51 | public void setMode(Mode... modes) { 52 | this.arr.clear(); 53 | addMode(modes); 54 | } 55 | 56 | public void addMode(Mode... modes) { 57 | if (modes != null && modes.length > 0) { 58 | for (Mode mode : modes) { 59 | switch (mode) { 60 | case NUMBER: 61 | addArr(ARR_NUMBER); 62 | break; 63 | case LOWER_CHAR: 64 | addArr(ARR_LOWER_CHAR); 65 | break; 66 | case UPPER_CHAR: 67 | addArr(ARR_UPPER_CHAR); 68 | break; 69 | default: 70 | throw new IllegalStateException("unreachable"); 71 | } 72 | } 73 | } 74 | } 75 | 76 | public void addArr(char[] chars) { 77 | for (char c : chars) { 78 | this.arr.add(c); 79 | } 80 | } 81 | 82 | public void setMinLength(int minLength) { 83 | this.minLength = minLength; 84 | } 85 | 86 | public void setMaxLength(int maxLength) { 87 | this.maxLength = maxLength; 88 | } 89 | 90 | @Override 91 | public void afterPropertiesSet() throws Exception { 92 | Assert.notEmpty(this.arr, "char array cannot be empty"); 93 | } 94 | 95 | /** 96 | * 验证码生成模式 97 | */ 98 | public enum Mode { 99 | /** 100 | * 数组 101 | */ 102 | NUMBER, 103 | /** 104 | * 字符 105 | */ 106 | LOWER_CHAR, 107 | /** 108 | * 字符加数字 109 | */ 110 | UPPER_CHAR, 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/phone/CustomCodeValidator.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * @author zido 7 | */ 8 | public class CustomCodeValidator implements CodeValidator { 9 | private boolean ignoreCase; 10 | 11 | public CustomCodeValidator() { 12 | this(true); 13 | } 14 | 15 | public CustomCodeValidator(boolean ignoreCase) { 16 | this.ignoreCase = ignoreCase; 17 | } 18 | 19 | @Override 20 | public boolean validate(String originalCode, String inputCode) { 21 | return ignoreCase ? (originalCode != null && originalCode.equalsIgnoreCase(inputCode)) : Objects.equals(originalCode, inputCode); 22 | } 23 | 24 | public void setIgnoreCase(boolean ignoreCase) { 25 | this.ignoreCase = ignoreCase; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/phone/MemoryPhoneCodeCache.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | import site.zido.coffee.core.utils.maps.expire.ExpireMap; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | 7 | public class MemoryPhoneCodeCache implements PhoneCodeCache { 8 | private final ExpireMap expireMap; 9 | private long timeout = 60; 10 | 11 | public MemoryPhoneCodeCache() { 12 | this(new ExpireMap<>(1, TimeUnit.MINUTES)); 13 | } 14 | 15 | public MemoryPhoneCodeCache(ExpireMap expireMap) { 16 | this.expireMap = expireMap; 17 | } 18 | 19 | @Override 20 | public void put(String phone, String code) { 21 | expireMap.set(phone, code, timeout * 1000); 22 | } 23 | 24 | @Override 25 | public String getCode(String phone) { 26 | return expireMap.get(phone); 27 | } 28 | 29 | public void setTimeout(long timeout) { 30 | this.timeout = timeout; 31 | } 32 | 33 | public long getTimeout() { 34 | return timeout; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/phone/PhoneCodeAuthenticationToken.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.SpringSecurityCoreVersion; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @author zido 11 | */ 12 | public class PhoneCodeAuthenticationToken extends UsernamePasswordAuthenticationToken { 13 | private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; 14 | 15 | public PhoneCodeAuthenticationToken(String phone, String code) { 16 | super(phone, code); 17 | } 18 | 19 | public PhoneCodeAuthenticationToken(String phone, String code, Collection authorities) { 20 | super(phone, code, authorities); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/phone/PhoneCodeCache.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | /** 4 | * 缓存手机号验证码接口 5 | * 6 | * @author zido 7 | */ 8 | public interface PhoneCodeCache { 9 | /** 10 | * 放入手机号所属验证码 11 | * 12 | * @param phone 手机号 13 | * @param code 验证码 14 | */ 15 | void put(String phone, String code); 16 | 17 | /** 18 | * 获取手机号验证码 19 | * 20 | * @param phone 手机号 21 | * @return 验证码 22 | */ 23 | String getCode(String phone); 24 | } 25 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/phone/PhoneCodeService.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | /** 4 | * 手机号验证码发送接口 5 | * 6 | * @author zido 7 | */ 8 | public interface PhoneCodeService { 9 | /** 10 | * 发送验证码 11 | * 12 | * @param phone 手机号 13 | * @param code 验证码 14 | */ 15 | void sendCode(String phone, String code); 16 | } 17 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/authentication/phone/SpringRedisPhoneCodeCache.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | import io.jsonwebtoken.lang.Assert; 4 | import org.springframework.beans.factory.InitializingBean; 5 | import org.springframework.data.redis.core.StringRedisTemplate; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * 使用spring redis template实现的手机号验证码缓存 11 | * 12 | * @author zido 13 | */ 14 | public class SpringRedisPhoneCodeCache implements PhoneCodeCache, InitializingBean { 15 | private StringRedisTemplate template; 16 | private String keyPrefix; 17 | private long timeout; 18 | private TimeUnit unit; 19 | 20 | public SpringRedisPhoneCodeCache() { 21 | this.setKeyPrefix("coffee:phone:"); 22 | this.setTimeout(60, TimeUnit.SECONDS); 23 | } 24 | 25 | @Override 26 | public void put(String phone, String code) { 27 | template.opsForValue().set(getKey(phone), code, timeout, unit); 28 | } 29 | 30 | @Override 31 | public String getCode(String phone) { 32 | return template.opsForValue().get(getKey(phone)); 33 | } 34 | 35 | public void setTemplate(StringRedisTemplate template) { 36 | this.template = template; 37 | } 38 | 39 | protected String getKey(String phone) { 40 | return keyPrefix + phone; 41 | } 42 | 43 | public void setKeyPrefix(String keyPrefix) { 44 | this.keyPrefix = keyPrefix; 45 | } 46 | 47 | public void setTimeout(long timeout, TimeUnit unit) { 48 | this.timeout = timeout; 49 | this.unit = unit; 50 | } 51 | 52 | @Override 53 | public void afterPropertiesSet() { 54 | Assert.notNull(template, "redis template cannot be null"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/configurers/RestAccessDeniedHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.configurers; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.security.access.AccessDeniedException; 5 | import org.springframework.security.web.access.AccessDeniedHandler; 6 | import org.springframework.util.StringUtils; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | 13 | public class RestAccessDeniedHandlerImpl implements AccessDeniedHandler { 14 | private MediaType mediaType; 15 | private String successBody; 16 | 17 | public RestAccessDeniedHandlerImpl() { 18 | } 19 | 20 | public RestAccessDeniedHandlerImpl(String successBody) { 21 | this(MediaType.APPLICATION_JSON, successBody); 22 | } 23 | 24 | public RestAccessDeniedHandlerImpl(MediaType mediaType, String successBody) { 25 | this.mediaType = mediaType; 26 | this.successBody = successBody; 27 | } 28 | 29 | public void setMediaType(MediaType mediaType) { 30 | this.mediaType = mediaType; 31 | } 32 | 33 | public void setSuccessBody(String successBody) { 34 | this.successBody = successBody; 35 | } 36 | 37 | @Override 38 | public void handle(HttpServletRequest request, HttpServletResponse response, 39 | AccessDeniedException accessDeniedException) throws IOException, ServletException { 40 | response.setStatus(HttpServletResponse.SC_FORBIDDEN); 41 | if (this.mediaType != null) { 42 | response.setHeader("Content-Type", this.mediaType.toString()); 43 | } 44 | if (StringUtils.hasLength(successBody)) { 45 | response.getWriter().write(successBody); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/configurers/RestLogoutSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.configurers; 2 | 3 | import org.springframework.security.core.Authentication; 4 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; 5 | import site.zido.coffee.mvc.utils.ResponseUtils; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.io.IOException; 11 | 12 | /** 13 | * @author zido 14 | */ 15 | public class RestLogoutSuccessHandler implements LogoutSuccessHandler { 16 | @Override 17 | public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) 18 | throws IOException, ServletException { 19 | ResponseUtils.json(response, 200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/configurers/RestSecurityConfigureAdapter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.configurers; 2 | 3 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 4 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 5 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 6 | import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; 7 | import site.zido.coffee.security.authentication.RestAuthenticationFailureHandler; 8 | import site.zido.coffee.security.authentication.RestAuthenticationSuccessHandler; 9 | import site.zido.coffee.security.token.RestAuthenticationEntryPoint; 10 | 11 | /** 12 | * 包装WebSecurityConfigurerAdapter,加入关注restful api的默认配置 13 | */ 14 | public class RestSecurityConfigureAdapter extends WebSecurityConfigurerAdapter { 15 | private boolean disableDefaults = false; 16 | 17 | public RestSecurityConfigureAdapter() { 18 | super(true); 19 | } 20 | 21 | protected void configure(HttpSecurity http) throws Exception { 22 | if (!disableDefaults) { 23 | http.csrf(AbstractHttpConfigurer::disable).addFilter(new WebAsyncManagerIntegrationFilter()) 24 | .exceptionHandling(handling -> handling.accessDeniedHandler(new RestAccessDeniedHandlerImpl()) 25 | .authenticationEntryPoint(new RestAuthenticationEntryPoint())) 26 | .headers().and().apply(new RestSecurityContextConfigurer<>()).and() 27 | .formLogin(form -> form.successHandler(new RestAuthenticationSuccessHandler()) 28 | .failureHandler(new RestAuthenticationFailureHandler()) 29 | .loginProcessingUrl("/users/sessions")) 30 | .anonymous().and().servletApi().and().logout(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/token/JwtRefreshProperties.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.token; 2 | 3 | public class JwtRefreshProperties { 4 | private long refreshTokenExpirationInMs; 5 | private String issue; 6 | private String refreshSecret; 7 | 8 | public long getRefreshTokenExpirationInMs() { 9 | return refreshTokenExpirationInMs; 10 | } 11 | 12 | public void setRefreshTokenExpirationInMs(long refreshTokenExpirationInMs) { 13 | this.refreshTokenExpirationInMs = refreshTokenExpirationInMs; 14 | } 15 | 16 | public String getIssue() { 17 | return issue; 18 | } 19 | 20 | public void setIssue(String issue) { 21 | this.issue = issue; 22 | } 23 | 24 | public String getRefreshSecret() { 25 | return refreshSecret; 26 | } 27 | 28 | public void setRefreshSecret(String refreshSecret) { 29 | this.refreshSecret = refreshSecret; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/token/JwtTokenProperties.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.token; 2 | 3 | /** 4 | * jwt token相关属性 5 | */ 6 | public class JwtTokenProperties { 7 | private long jwtExpirationInMs; 8 | private String issue; 9 | private String secret; 10 | private String authHeaderName; 11 | private JwtRefreshProperties refreshment; 12 | 13 | public long getJwtExpirationInMs() { 14 | return jwtExpirationInMs; 15 | } 16 | 17 | public void setJwtExpirationInMs(long jwtExpirationInMs) { 18 | this.jwtExpirationInMs = jwtExpirationInMs; 19 | } 20 | 21 | public String getIssue() { 22 | return issue; 23 | } 24 | 25 | public void setIssue(String issue) { 26 | this.issue = issue; 27 | } 28 | 29 | public String getSecret() { 30 | return secret; 31 | } 32 | 33 | public void setSecret(String secret) { 34 | this.secret = secret; 35 | } 36 | 37 | public String getAuthHeaderName() { 38 | return authHeaderName; 39 | } 40 | 41 | public void setAuthHeaderName(String authHeaderName) { 42 | this.authHeaderName = authHeaderName; 43 | } 44 | 45 | public JwtRefreshProperties getRefreshment() { 46 | return refreshment; 47 | } 48 | 49 | public void setRefreshment(JwtRefreshProperties refreshment) { 50 | this.refreshment = refreshment; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/token/JwtWriterResponse.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.token; 2 | 3 | import io.jsonwebtoken.JwtBuilder; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import org.springframework.security.authentication.AnonymousAuthenticationToken; 7 | import org.springframework.security.core.context.SecurityContext; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.security.web.util.OnCommittedResponseWrapper; 10 | 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.util.Date; 13 | 14 | public class JwtWriterResponse extends OnCommittedResponseWrapper { 15 | private final long jwtExpirationInMs; 16 | private final String issue; 17 | private final String secret; 18 | private final String authHeaderName; 19 | 20 | public JwtWriterResponse(HttpServletResponse response, long jwtExpirationInMs, String issue, String secret, String authHeaderName) { 21 | super(response); 22 | this.jwtExpirationInMs = jwtExpirationInMs; 23 | this.issue = issue; 24 | this.secret = secret; 25 | this.authHeaderName = authHeaderName; 26 | } 27 | 28 | @Override 29 | protected void onResponseCommitted() { 30 | writeToken(SecurityContextHolder.getContext()); 31 | this.disableOnResponseCommitted(); 32 | } 33 | 34 | protected void writeToken(SecurityContext context) { 35 | if (isDisableOnResponseCommitted()) { 36 | return; 37 | } 38 | if (context.getAuthentication() != null) { 39 | String newToken = generateNewToken(context); 40 | addTokenToResponse(getHttpResponse(), newToken); 41 | doAfterWriteToken(context); 42 | } 43 | } 44 | 45 | protected void doAfterWriteToken(SecurityContext context) { 46 | 47 | } 48 | 49 | protected void addTokenToResponse(HttpServletResponse response, String token) { 50 | response.setHeader(authHeaderName, token); 51 | } 52 | 53 | protected String generateNewToken(SecurityContext subject) { 54 | Date now = new Date(); 55 | Date expiryDate = new Date(now.getTime() + jwtExpirationInMs); 56 | 57 | JwtBuilder builder = Jwts.builder(); 58 | //如果开启匿名,则写入匿名token配置 59 | if (subject instanceof AnonymousAuthenticationToken) { 60 | builder.claim("role", "anonymous"); 61 | } 62 | //如果是匿名用户则subject为空字符串 63 | return builder 64 | .setSubject(subject.getAuthentication().getName()) 65 | .setIssuedAt(now) 66 | .setIssuer(issue) 67 | .setExpiration(expiryDate) 68 | .signWith(SignatureAlgorithm.HS512, secret) 69 | .compact(); 70 | } 71 | 72 | protected HttpServletResponse getHttpResponse() { 73 | return (HttpServletResponse) getResponse(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/token/RefreshWriterResponse.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.token; 2 | 3 | import io.jsonwebtoken.Jwts; 4 | import io.jsonwebtoken.SignatureAlgorithm; 5 | import org.springframework.security.core.context.SecurityContext; 6 | 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.util.Date; 9 | 10 | public class RefreshWriterResponse extends JwtWriterResponse { 11 | private long refreshTokenExpirationInMs; 12 | private String issue; 13 | private String refreshSecret; 14 | private String refreshHeaderName; 15 | 16 | public RefreshWriterResponse(HttpServletResponse response, 17 | long jwtExpirationInMs, 18 | String issue, 19 | String jwtSecret, 20 | String authHeaderName, 21 | long refreshTokenExpirationInMs, 22 | String refreshSecret) { 23 | super(response, jwtExpirationInMs, issue, jwtSecret, authHeaderName); 24 | this.refreshTokenExpirationInMs = refreshTokenExpirationInMs; 25 | this.refreshSecret = refreshSecret; 26 | } 27 | 28 | public RefreshWriterResponse(HttpServletResponse response, 29 | long jwtExpirationInMs, 30 | String issue, 31 | String jwtSecret, 32 | String authHeaderName, 33 | long refreshTokenExpirationInMs) { 34 | super(response, jwtExpirationInMs, issue, jwtSecret, authHeaderName); 35 | this.refreshTokenExpirationInMs = refreshTokenExpirationInMs; 36 | this.refreshSecret = jwtSecret; 37 | } 38 | 39 | protected String generateRefreshToken(SecurityContext subject) { 40 | Date now = new Date(); 41 | Date expiryDate = new Date(now.getTime() + refreshTokenExpirationInMs); 42 | 43 | return Jwts.builder() 44 | .setSubject(subject.getAuthentication().getName()) 45 | .setIssuedAt(now) 46 | .setIssuer(issue) 47 | .setExpiration(expiryDate) 48 | .claim("scope", "refresh") 49 | .signWith(SignatureAlgorithm.HS512, refreshSecret) 50 | .compact(); 51 | } 52 | 53 | @Override 54 | protected void doAfterWriteToken(SecurityContext context) { 55 | getHttpResponse().setHeader(refreshHeaderName, generateRefreshToken(context)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/token/RestAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.token; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.security.core.AuthenticationException; 5 | import org.springframework.security.web.AuthenticationEntryPoint; 6 | import org.springframework.util.StringUtils; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author zido 15 | */ 16 | public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { 17 | private MediaType mediaType; 18 | private String content; 19 | 20 | public RestAuthenticationEntryPoint() { 21 | } 22 | 23 | public RestAuthenticationEntryPoint(String content) { 24 | this(MediaType.APPLICATION_JSON, content); 25 | } 26 | 27 | public RestAuthenticationEntryPoint(MediaType mediaType, String content) { 28 | this.mediaType = mediaType; 29 | this.content = content; 30 | } 31 | 32 | public void setMediaType(MediaType mediaType) { 33 | this.mediaType = mediaType; 34 | } 35 | 36 | public void setContent(String content) { 37 | this.content = content; 38 | } 39 | 40 | @Override 41 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { 42 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 43 | if (this.mediaType != null) { 44 | response.setHeader("Content-Type", this.mediaType.toString()); 45 | } 46 | if (StringUtils.hasLength(content)) { 47 | response.getWriter().write(content); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/main/java/site/zido/coffee/security/token/TokenInvalidException.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.token; 2 | 3 | /** 4 | * token 失效异常 5 | * 6 | * @author zido 7 | */ 8 | public class TokenInvalidException extends RuntimeException { 9 | public TokenInvalidException(String msg, Throwable t) { 10 | super(msg, t); 11 | } 12 | 13 | public TokenInvalidException(String msg) { 14 | super(msg); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /coffee-modules/coffee-rest-security/src/test/java/site/zido/coffee/security/authentication/phone/CustomCodeGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.security.authentication.phone; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class CustomCodeGeneratorTest { 7 | @Test 8 | public void testGenerate() { 9 | CustomCodeGenerator generator = new CustomCodeGenerator(CustomCodeGenerator.Mode.NUMBER); 10 | String code = generator.generateCode("xxx"); 11 | Assertions.assertEquals(6, code.length()); 12 | String code2 = generator.generateCode("xxx"); 13 | Assertions.assertNotEquals(code, code2); 14 | } 15 | 16 | @Test 17 | public void testMaxAndMinGenerate() { 18 | CustomCodeGenerator generator = new CustomCodeGenerator(CustomCodeGenerator.Mode.NUMBER); 19 | generator.setMaxLength(10); 20 | generator.setMinLength(1); 21 | String code = generator.generateCode(""); 22 | Assertions.assertTrue(1 <= code.length() && code.length() <= 10); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-modules 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-webmvc 13 | Spring Web Mvc 增强 14 | 15 | 16 | 17 | site.zido 18 | coffee-core 19 | 20 | 21 | org.springframework 22 | spring-webmvc 23 | 24 | 25 | javax.validation 26 | validation-api 27 | true 28 | 29 | 30 | javax.servlet 31 | javax.servlet-api 32 | 33 | 34 | org.slf4j 35 | slf4j-api 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-databind 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/CommonErrorCode.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc; 2 | 3 | /** 4 | * 异常错误码 5 | * 6 | * @author zido 7 | */ 8 | public class CommonErrorCode { 9 | /** 10 | * 未知异常错误码 11 | */ 12 | public static final int UNKNOWN = 1; 13 | /** 14 | * 参数异常错误码 15 | */ 16 | public static final int VALIDATION_FAILED = 2; 17 | /** 18 | * 被限制访问的异常错误码 19 | */ 20 | public static final int LIMIT = 3; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/exceptions/CommonBusinessException.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.exceptions; 2 | 3 | /** 4 | * 业务异常 5 | * 6 | * @author zido 7 | */ 8 | public class CommonBusinessException extends RuntimeException { 9 | private static final long serialVersionUID = -8246671096687096493L; 10 | /** 11 | * 一般业务异常不建议使用500及以上http status 12 | */ 13 | private int httpStatus; 14 | /** 15 | * 补充错误码,请注意,当允许全局返回{@link site.zido.coffee.mvc.rest.Result}时, 16 | * code默认为0,所以不建议占用code为0,请设置其他的code 17 | */ 18 | private int code; 19 | private String msg; 20 | 21 | public CommonBusinessException(int httpStatus, int code, String msg) { 22 | super(buildMsg(httpStatus, code, msg)); 23 | this.httpStatus = httpStatus; 24 | this.code = code; 25 | this.msg = msg; 26 | } 27 | 28 | public CommonBusinessException(int httpStatus, int code, Throwable t) { 29 | super( 30 | buildMsg(httpStatus, 31 | code, 32 | t instanceof CommonBusinessException 33 | ? ((CommonBusinessException) t).getMsg() 34 | : t.getMessage()), 35 | t); 36 | this.httpStatus = httpStatus; 37 | this.code = code; 38 | if (t instanceof CommonBusinessException) { 39 | this.msg = ((CommonBusinessException) t).getMsg(); 40 | } else { 41 | this.msg = "未知异常"; 42 | } 43 | } 44 | 45 | protected CommonBusinessException(Throwable t) { 46 | this(400, 1, t); 47 | } 48 | 49 | public CommonBusinessException(int httpStatus, String msg) { 50 | this(httpStatus, 1, msg); 51 | } 52 | 53 | public CommonBusinessException(int httpStatus) { 54 | this(httpStatus, 1, "未知异常"); 55 | } 56 | 57 | 58 | public CommonBusinessException() { 59 | this(400); 60 | } 61 | 62 | private static String buildMsg(int httpStatus, int code, String msg) { 63 | return msg + "(http status:" + httpStatus + ",code:" + code + ")"; 64 | } 65 | 66 | public int getCode() { 67 | return code; 68 | } 69 | 70 | protected void setCode(int code) { 71 | this.code = code; 72 | } 73 | 74 | public String getMsg() { 75 | return msg; 76 | } 77 | 78 | protected void setMsg(String msg) { 79 | this.msg = msg; 80 | } 81 | 82 | public int getHttpStatus() { 83 | return httpStatus; 84 | } 85 | 86 | public void setHttpStatus(int httpStatus) { 87 | this.httpStatus = httpStatus; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/exceptions/EnableGlobalException.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.exceptions; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Import; 5 | 6 | import java.lang.annotation.*; 7 | 8 | 9 | /** 10 | * 是否允许全局使用统一异常处理,会将异常使用{@link site.zido.coffee.mvc.rest.HttpResponseBodyFactory}进行处理 11 | *

12 | * 13 | * @author zido 14 | */ 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target({ElementType.TYPE}) 17 | @Documented 18 | @Import(GlobalExceptionConfiguration.class) 19 | @Configuration 20 | public @interface EnableGlobalException { 21 | } 22 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/exceptions/EnableJavaxException.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.exceptions; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Import; 5 | import site.zido.coffee.mvc.rest.JavaxValidationExceptionAdvice; 6 | 7 | import java.lang.annotation.*; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.TYPE}) 11 | @Documented 12 | @Import({JavaxValidationExceptionAdvice.class}) 13 | @Configuration 14 | public @interface EnableJavaxException { 15 | } 16 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/exceptions/GlobalExceptionConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.exceptions; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | import site.zido.coffee.mvc.rest.GlobalExceptionAdvice; 7 | import site.zido.coffee.mvc.rest.HttpResponseBodyConfiguration; 8 | import site.zido.coffee.mvc.rest.HttpResponseBodyFactory; 9 | 10 | @Configuration 11 | @Import(HttpResponseBodyConfiguration.class) 12 | public class GlobalExceptionConfiguration { 13 | @Bean 14 | public GlobalExceptionAdvice advice(HttpResponseBodyFactory factory) { 15 | return new GlobalExceptionAdvice(factory); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/logger/CoffeeRequestLoggingFilter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.logger; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.web.filter.AbstractRequestLoggingFilter; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | 9 | public class CoffeeRequestLoggingFilter extends AbstractRequestLoggingFilter { 10 | final Logger logger = LoggerFactory.getLogger("RequestLog"); 11 | 12 | @Override 13 | protected boolean shouldLog(HttpServletRequest request) { 14 | return logger.isInfoEnabled(); 15 | } 16 | 17 | @Override 18 | protected void beforeRequest(HttpServletRequest httpServletRequest, String s) { 19 | logger.info(s); 20 | } 21 | 22 | @Override 23 | protected void afterRequest(HttpServletRequest httpServletRequest, String s) { 24 | logger.info(s); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/logger/EnableRequestLogger.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.logger; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Import; 5 | 6 | import java.lang.annotation.*; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE}) 10 | @Documented 11 | @Import(MvcLogConfiguration.class) 12 | @Configuration 13 | public @interface EnableRequestLogger { 14 | } 15 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/logger/MvcLogConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.logger; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.filter.AbstractRequestLoggingFilter; 6 | 7 | /** 8 | * 请求日志配置 9 | */ 10 | @Configuration 11 | public class MvcLogConfiguration { 12 | @Bean 13 | public AbstractRequestLoggingFilter filter() { 14 | AbstractRequestLoggingFilter filter = new CoffeeRequestLoggingFilter(); 15 | filter.setIncludeClientInfo(true); 16 | filter.setIncludeQueryString(true); 17 | return filter; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/DefaultHttpResponseBodyFactory.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * 默认rest响应体实现类 9 | * 10 | * @author zido 11 | */ 12 | public class DefaultHttpResponseBodyFactory implements HttpResponseBodyFactory { 13 | @Override 14 | public boolean isExceptedClass(Class clazz) { 15 | return Result.class.isAssignableFrom(clazz); 16 | } 17 | 18 | @Override 19 | public Object success(Object data) { 20 | return Result.success(data); 21 | } 22 | 23 | @Override 24 | public Object error(int code, String message, Collection errors) { 25 | return Result.error(code, message, errors); 26 | } 27 | 28 | @Override 29 | public Map errorToMap(int code, String message, Collection errors) { 30 | Map result = new HashMap<>(3); 31 | result.put("code", code); 32 | result.put("message", message); 33 | result.put("errors", errors); 34 | return result; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/DefaultResult.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import java.util.Collection; 4 | 5 | public class DefaultResult implements Result { 6 | private static final long serialVersionUID = -3266931205943696705L; 7 | private int code = 0; 8 | private String message; 9 | private T result; 10 | private Collection errors; 11 | 12 | public int getCode() { 13 | return code; 14 | } 15 | 16 | public void setCode(int code) { 17 | this.code = code; 18 | } 19 | 20 | public String getMessage() { 21 | return message; 22 | } 23 | 24 | public void setMessage(String message) { 25 | this.message = message; 26 | } 27 | 28 | public T getResult() { 29 | return result; 30 | } 31 | 32 | public void setResult(T result) { 33 | this.result = result; 34 | } 35 | 36 | public Collection getErrors() { 37 | return errors; 38 | } 39 | 40 | public void setErrors(Collection errors) { 41 | this.errors = errors; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/EnableGlobalResult.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Import; 5 | 6 | import java.lang.annotation.*; 7 | 8 | /** 9 | * 是否允许全局使用统一返回值,enable为开关 10 | *

11 | * 如果全局使用统一返回值,则成功和失败结果均使用{@link Result}进行统一封装, 12 | * 否则仅当失败时使用统一的Result返回失败异常 13 | * 14 | * @author zido 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.TYPE}) 18 | @Documented 19 | @Import(GlobalRestConfiguration.class) 20 | @Configuration 21 | public @interface EnableGlobalResult { 22 | } 23 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/GlobalExceptionAdvice.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | 4 | import org.springframework.core.annotation.Order; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.RestControllerAdvice; 8 | import org.springframework.web.context.request.WebRequest; 9 | import site.zido.coffee.mvc.exceptions.CommonBusinessException; 10 | 11 | import javax.servlet.http.HttpServletResponse; 12 | import javax.validation.ConstraintViolationException; 13 | 14 | /** 15 | * server 异常处理,兜底处理 16 | * 17 | * @author zido 18 | */ 19 | @RestControllerAdvice 20 | @Order 21 | public class GlobalExceptionAdvice extends BaseGlobalExceptionHandler { 22 | public GlobalExceptionAdvice(HttpResponseBodyFactory factory) { 23 | super(factory); 24 | } 25 | 26 | @ExceptionHandler(CommonBusinessException.class) 27 | @Override 28 | protected ResponseEntity handleCommonBusinessException(CommonBusinessException e, WebRequest request, HttpServletResponse response) { 29 | return super.handleCommonBusinessException(e, request, response); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/GlobalRestConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | import org.springframework.http.converter.HttpMessageConverter; 10 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 12 | import site.zido.coffee.mvc.exceptions.EnableGlobalException; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * 全局rest配置类 18 | * 19 | *
    20 | *
  • 开启全局异常处理
  • 21 | *
  • 开启全局rest响应体封装
  • 22 | *
  • rest
  • 23 | *
24 | * 25 | * @author zido 26 | */ 27 | @Configuration 28 | @Import({HttpResponseBodyConfiguration.class}) 29 | @EnableGlobalException 30 | public class GlobalRestConfiguration { 31 | private static final Logger LOGGER = LoggerFactory.getLogger(GlobalRestConfiguration.class); 32 | 33 | /** 34 | * 全局响应体封装类 35 | * 36 | * @return result annotations 37 | */ 38 | @Bean 39 | public GlobalResultHandler handler(HttpResponseBodyFactory factory) { 40 | LOGGER.debug("The global result is enabled"); 41 | return new GlobalResultHandler(factory); 42 | } 43 | 44 | /** 45 | * 配合全局响应封装的消息转换器 46 | */ 47 | @Configuration 48 | @EnableWebMvc 49 | public static class StringToResultHttpConfiguration implements WebMvcConfigurer { 50 | private final ObjectMapper mapper; 51 | 52 | public StringToResultHttpConfiguration(ObjectMapper mapper) { 53 | this.mapper = mapper; 54 | } 55 | 56 | @Override 57 | public void configureMessageConverters(List> converters) { 58 | LOGGER.debug("Add json string http message converter"); 59 | converters.add(0, new StringToResultHttpMessageConverter(mapper)); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/GlobalResultHandler.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import org.springframework.core.MethodParameter; 4 | import org.springframework.http.MediaType; 5 | import org.springframework.http.converter.HttpMessageConverter; 6 | import org.springframework.http.server.ServerHttpRequest; 7 | import org.springframework.http.server.ServerHttpResponse; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; 10 | 11 | import java.lang.reflect.Method; 12 | 13 | /** 14 | * 统一全局响应封装 15 | * 16 | * @author zido 17 | */ 18 | @RestControllerAdvice 19 | public class GlobalResultHandler implements ResponseBodyAdvice { 20 | public static final String ORIGINAL_RESPONSE = "ORIGINAL_RESPONSE"; 21 | private final HttpResponseBodyFactory factory; 22 | 23 | public GlobalResultHandler(HttpResponseBodyFactory factory) { 24 | this.factory = factory; 25 | } 26 | 27 | @Override 28 | public boolean supports(MethodParameter returnType, Class> converterType) { 29 | //支持Optional包装 30 | return !factory.isExceptedClass(returnType.nestedIfOptional().getParameterType()); 31 | } 32 | 33 | @Override 34 | public Object beforeBodyWrite(Object body, MethodParameter returnType, 35 | MediaType selectedContentType, 36 | Class> selectedConverterType, 37 | ServerHttpRequest request, 38 | ServerHttpResponse response) { 39 | OriginalResponse methodAnnotation = returnType.getMethodAnnotation(OriginalResponse.class); 40 | if (methodAnnotation != null || returnType.getDeclaringClass().getAnnotation(OriginalResponse.class) != null) { 41 | return body; 42 | } 43 | Method method = returnType.getMethod(); 44 | if (method == null) { 45 | return null; 46 | } 47 | Class returnClass = method.getReturnType(); 48 | if (returnClass.equals(String.class) && body == null) { 49 | return null; 50 | } 51 | return factory.success(body); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/HttpResponseBodyConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | public class HttpResponseBodyConfiguration { 8 | @Bean 9 | public HttpResponseBodyFactory bodyFactory() { 10 | return new DefaultHttpResponseBodyFactory(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/HttpResponseBodyFactory.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import site.zido.coffee.core.utils.BeanUtils; 4 | import site.zido.coffee.mvc.CommonErrorCode; 5 | import site.zido.coffee.mvc.exceptions.CommonBusinessException; 6 | 7 | import java.util.Collection; 8 | import java.util.Map; 9 | 10 | /** 11 | * http 相应结果生成工厂 12 | * 13 | * @author zido 14 | */ 15 | public interface HttpResponseBodyFactory { 16 | /** 17 | * 是否是期望的类型 18 | * 19 | * @param clazz 目标类型 20 | * @return true/false 21 | */ 22 | boolean isExceptedClass(Class clazz); 23 | 24 | /** 25 | * 成功 26 | * 27 | * @param data 响应数据 28 | * @return object 29 | */ 30 | Object success(Object data); 31 | 32 | /** 33 | * 失败 34 | * 35 | * @param code code 36 | * @param message message 37 | * @param errors 异常详细信息,可选 38 | * @return object 39 | */ 40 | Object error(int code, String message, Collection errors); 41 | 42 | /** 43 | * 失败 44 | * 45 | * @param code code 46 | * @param message message 47 | * @return object 48 | */ 49 | default Object error(int code, String message) { 50 | return error(code, message, null); 51 | } 52 | 53 | default Map errorToMap(int code, String message, Collection errors) { 54 | Object error = error(code, message, errors); 55 | return BeanUtils.objectToMap(error); 56 | } 57 | 58 | /** 59 | * 失败 60 | * 61 | * @param t ex 62 | * @return object 63 | */ 64 | default Object error(Throwable t) { 65 | if (t instanceof CommonBusinessException) { 66 | CommonBusinessException cbe = (CommonBusinessException) t; 67 | return error(cbe.getCode(), cbe.getMsg(), null); 68 | } 69 | return error(CommonErrorCode.UNKNOWN, t.getMessage(), null); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/JavaxValidationExceptionAdvice.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import org.springframework.core.annotation.Order; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | import org.springframework.web.context.request.WebRequest; 10 | import site.zido.coffee.mvc.CommonErrorCode; 11 | 12 | import javax.validation.ConstraintViolation; 13 | import javax.validation.ConstraintViolationException; 14 | import javax.validation.Path; 15 | import java.util.ArrayList; 16 | import java.util.Iterator; 17 | import java.util.List; 18 | import java.util.Set; 19 | 20 | @RestControllerAdvice 21 | @Order 22 | public class JavaxValidationExceptionAdvice extends BaseGlobalExceptionHandler { 23 | public JavaxValidationExceptionAdvice(HttpResponseBodyFactory factory) { 24 | super(factory); 25 | } 26 | 27 | /** 28 | * parameter参数校验异常处理 29 | * 30 | * @param e 校验异常 31 | * @return result 32 | */ 33 | @ExceptionHandler(value = ConstraintViolationException.class) 34 | public ResponseEntity handleConstraintViolationException(ConstraintViolationException e, WebRequest request) { 35 | Set> constraintViolations = e.getConstraintViolations(); 36 | Iterator> iterator = constraintViolations.iterator(); 37 | List errors = new ArrayList<>(); 38 | while (iterator.hasNext()) { 39 | ConstraintViolation next = iterator.next(); 40 | Path propertyPath = next.getPropertyPath(); 41 | String name = "unknown"; 42 | //获取参数名 43 | for (Path.Node node : propertyPath) { 44 | name = node.getName(); 45 | } 46 | //参数错误提示 47 | String message = "[" + name + "] " + next.getMessage(); 48 | errors.add(message); 49 | } 50 | return handleExceptionInternal( 51 | e, 52 | factory.error(CommonErrorCode.VALIDATION_FAILED, 53 | messages.getMessage("ValidationFailed", "Validation Failed"), 54 | errors), 55 | new HttpHeaders(), 56 | HttpStatus.BAD_REQUEST, 57 | request 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/OriginalResponse.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 设置返回原响应内容,亦即不是用{@link Result} 进行数据包装 7 | * 8 | * @author zido 9 | */ 10 | @Documented 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.TYPE, ElementType.METHOD}) 13 | public @interface OriginalResponse { 14 | } 15 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/Result.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import site.zido.coffee.mvc.CommonErrorCode; 4 | 5 | import java.io.Serializable; 6 | import java.util.Collection; 7 | 8 | /** 9 | * 通用http响应结果 10 | *

11 | * 关于result和details为何是同一泛型的说明: 12 | *

13 | * result和details为二选一,一般而言,result表示成功数据,而details表示失败细节。 14 | * 而如果采用两个类型,会显得类型声明太多余。 15 | *

16 | * 这里为正确和错误选择同一类型的原因是为后续微服务化能够更方便统一处理有关 17 | * 18 | * @param data类型 19 | * @author zido 20 | */ 21 | public interface Result extends Serializable { 22 | 23 | int getCode(); 24 | 25 | String getMessage(); 26 | 27 | T getResult(); 28 | 29 | Collection getErrors(); 30 | 31 | static Result success(T result) { 32 | DefaultResult response = new DefaultResult<>(); 33 | response.setResult(result); 34 | return response; 35 | } 36 | 37 | static Result success() { 38 | return new DefaultResult<>(); 39 | } 40 | 41 | static Result error() { 42 | DefaultResult result = new DefaultResult<>(); 43 | result.setCode(CommonErrorCode.UNKNOWN); 44 | return result; 45 | } 46 | 47 | static Result error(int code, String message) { 48 | return error(code, message, null); 49 | } 50 | 51 | static Result error(int code) { 52 | return error(code, null); 53 | } 54 | 55 | static Result error(int code, String message, Collection errors) { 56 | DefaultResult result = new DefaultResult<>(); 57 | result.setCode(code); 58 | result.setMessage(message); 59 | result.setErrors(errors); 60 | return result; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/StringToResultHttpMessageConverter.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.rest; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.http.HttpInputMessage; 5 | import org.springframework.http.HttpOutputMessage; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.http.converter.AbstractHttpMessageConverter; 8 | import org.springframework.http.converter.HttpMessageNotReadableException; 9 | import org.springframework.http.converter.HttpMessageNotWritableException; 10 | import org.springframework.util.StreamUtils; 11 | 12 | import java.io.IOException; 13 | import java.nio.charset.Charset; 14 | 15 | /** 16 | * 为string类型返回值定制的消息处理器,配合补充{@link GlobalResultHandler}。 17 | *

18 | * 默认spring对string的处理经过GlobalResultHandler之后会返回text/plain类型的json对象,在这个消息转换器中,专门处理string类型, 19 | * 给前端正确的application/json响应头。 20 | * 21 | * @author zido 22 | */ 23 | public class StringToResultHttpMessageConverter extends AbstractHttpMessageConverter { 24 | private final ObjectMapper mapper; 25 | 26 | /** 27 | * 不使用utf-8 charset参数的原因参考{@link MediaType#APPLICATION_JSON_UTF8} 28 | * @param mapper json mapper,用于序列化对象 29 | */ 30 | public StringToResultHttpMessageConverter(ObjectMapper mapper) { 31 | super(MediaType.APPLICATION_JSON, MediaType.ALL); 32 | this.mapper = mapper; 33 | } 34 | 35 | @Override 36 | protected boolean supports(Class clazz) { 37 | return String.class == clazz; 38 | } 39 | 40 | @Override 41 | protected String readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { 42 | Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType()); 43 | return StreamUtils.copyToString(inputMessage.getBody(), charset); 44 | } 45 | 46 | @Override 47 | protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { 48 | Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType()); 49 | if (o instanceof String) { 50 | outputMessage.getBody().write(((String) o).getBytes(charset)); 51 | } else { 52 | outputMessage.getBody().write(mapper.writeValueAsBytes(o)); 53 | } 54 | } 55 | 56 | private Charset getContentTypeCharset(MediaType contentType) { 57 | if (contentType != null && contentType.getCharset() != null) { 58 | return contentType.getCharset(); 59 | } else { 60 | return getDefaultCharset(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/rest/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * restful api配置 3 | *

4 | * 封装返回对象,封装异常 5 | */ 6 | package site.zido.coffee.mvc.rest; -------------------------------------------------------------------------------- /coffee-modules/coffee-webmvc/src/main/java/site/zido/coffee/mvc/utils/ResponseUtils.java: -------------------------------------------------------------------------------- 1 | package site.zido.coffee.mvc.utils; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.http.MediaType; 5 | 6 | import javax.servlet.http.HttpServletResponse; 7 | import java.io.IOException; 8 | 9 | /** 10 | * 响应工具类 11 | * 12 | * @author zido 13 | */ 14 | public class ResponseUtils { 15 | private static final ObjectMapper mapper = new ObjectMapper(); 16 | 17 | public static void json(HttpServletResponse response, Object body) throws IOException { 18 | response.setCharacterEncoding("UTF-8"); 19 | response.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE); 20 | if (body != null) { 21 | response.getWriter().write(mapper.writeValueAsString(body)); 22 | } 23 | } 24 | 25 | public static void json(HttpServletResponse response, String body) throws IOException { 26 | response.setCharacterEncoding("UTF-8"); 27 | response.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE); 28 | if (body != null) { 29 | response.getWriter().write(body); 30 | } 31 | } 32 | 33 | public static void json(HttpServletResponse response, int status) throws IOException { 34 | json(response, status, null); 35 | } 36 | 37 | public static void json(HttpServletResponse response, int status, String body) throws IOException { 38 | response.setStatus(status); 39 | response.setCharacterEncoding("UTF-8"); 40 | response.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE); 41 | if (body != null) { 42 | response.getWriter().write(body); 43 | } 44 | } 45 | 46 | public static void json(HttpServletResponse response, int status, Object body) throws IOException { 47 | response.setStatus(status); 48 | response.setCharacterEncoding("UTF-8"); 49 | response.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE); 50 | if (body != null) { 51 | response.getWriter().write(mapper.writeValueAsString(body)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /coffee-modules/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-common-builder 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-modules 13 | pom 14 | 15 | 16 | coffee-core 17 | coffee-webmvc 18 | coffee-extra 19 | coffee-rest-security 20 | 21 | -------------------------------------------------------------------------------- /coffee-spring-boot-parent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.3.9.RELEASE 9 | 10 | 11 | 4.0.0 12 | site.zido 13 | coffee-spring-boot-parent 14 | 0.3.0-SNAPSHOT 15 | pom 16 | Coffee Spring Boot Parent 17 | 18 | 19 | 20 | 21 | 22 | site.zido 23 | coffee-dependencies 24 | 0.3.0-SNAPSHOT 25 | pom 26 | import 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /coffee-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-common-builder 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-spring-boot-starter 13 | A Spring Boot Starter For Coffee 14 | jar 15 | 16 | 17 | site.zido 18 | coffee-autoconfigure-web 19 | 20 | 21 | site.zido 22 | coffee-autoconfigure-extra 23 | 24 | 25 | site.zido 26 | coffee-autoconfigure-rest-security 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /coffee-starters/coffee-starter-extra/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-starters 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-starter-extra 13 | 14 | 15 | 16 | site.zido 17 | coffee-autoconfigure-extra 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-aop 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | -------------------------------------------------------------------------------- /coffee-starters/coffee-starter-rest-security/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-starters 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-starter-rest-security 13 | 14 | 15 | 16 | site.zido 17 | coffee-autoconfigure-rest-security 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-security 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | -------------------------------------------------------------------------------- /coffee-starters/coffee-starter-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-starters 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-starter-web 13 | 14 | 15 | 16 | site.zido 17 | coffee-autoconfigure-web 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-web 22 | 23 | 24 | -------------------------------------------------------------------------------- /coffee-starters/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-common-builder 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | coffee-starters 13 | pom 14 | 15 | coffee-starter-web 16 | coffee-starter-extra 17 | coffee-starter-rest-security 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/Production/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | coffee-spring-boot-parent 7 | site.zido 8 | 0.3.0-SNAPSHOT 9 | ../../coffee-spring-boot-parent 10 | 11 | 4.0.0 12 | Production 13 | 14 | 15 | central 16 | Central Repository 17 | https://maven.aliyun.com/repository/central 18 | default 19 | 20 | false 21 | 22 | 23 | 24 | 25 | 26 | central 27 | Central Repository 28 | https://maven.aliyun.com/repository/central 29 | default 30 | 31 | false 32 | 33 | 34 | 35 | 36 | 37 | site.zido 38 | coffee-spring-boot-starter 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-web 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-data-jpa 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-data-redis 51 | 52 | 53 | org.hibernate 54 | hibernate-validator 55 | 6.0.1.Final 56 | 57 | 58 | ai.grakn 59 | redis-mock 60 | 0.1.6 61 | 62 | 63 | com.h2database 64 | h2 65 | runtime 66 | 67 | 68 | org.projectlombok 69 | lombok 70 | 1.18.12 71 | provided 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-test 76 | test 77 | 78 | 79 | org.springframework.security 80 | spring-security-test 81 | test 82 | 83 | 84 | org.junit.jupiter 85 | junit-jupiter-engine 86 | test 87 | 88 | 89 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/DemoProdApplication.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoProdApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(DemoProdApplication.class); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/api/AdminController.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.api; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import site.zido.demo.pojo.dto.UserDTO; 8 | import site.zido.demo.service.IUserService; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("/admin") 15 | public class AdminController { 16 | private final IUserService userService; 17 | 18 | public AdminController(IUserService userService) { 19 | this.userService = userService; 20 | } 21 | 22 | @GetMapping("/users") 23 | @PreAuthorize("hasAuthority('admin')") 24 | public List users() { 25 | return userService.getUsers(Arrays.asList(1, 2, 3, 4)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/api/RoomController.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.api; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | import site.zido.demo.entity.Room; 7 | import site.zido.demo.service.IRoomService; 8 | 9 | import java.util.List; 10 | 11 | @RestController 12 | @RequestMapping("/rooms") 13 | public class RoomController { 14 | private IRoomService roomService; 15 | 16 | public RoomController(IRoomService roomService) { 17 | this.roomService = roomService; 18 | } 19 | 20 | @GetMapping 21 | public List rooms() { 22 | return roomService.getRooms(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/api/UserController.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.api; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.security.access.prepost.PreAuthorize; 5 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 6 | import org.springframework.web.bind.annotation.*; 7 | import site.zido.demo.entity.Admin; 8 | import site.zido.demo.entity.User; 9 | import site.zido.demo.pojo.params.UserParams; 10 | import site.zido.demo.service.IUserService; 11 | 12 | @RestController 13 | @RequestMapping("/users") 14 | public class UserController { 15 | private IUserService userService; 16 | 17 | public UserController(IUserService userService) { 18 | this.userService = userService; 19 | } 20 | 21 | @PreAuthorize("hasAuthority('a') || authentication.principal instanceof T(site.zido.demo.entity.User)") 22 | @GetMapping("/me") 23 | public User user(@AuthenticationPrincipal(expression = "user") User user) { 24 | return user; 25 | } 26 | 27 | @PreAuthorize("hasAuthority('admin')") 28 | @PostMapping 29 | @ResponseStatus(HttpStatus.CREATED) 30 | public void addUser(UserParams params, @AuthenticationPrincipal Admin admin) { 31 | userService.addUser(params, admin); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/config/LimiterConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import site.zido.coffee.extra.limiter.EnableLimiter; 5 | 6 | /** 7 | * 启动限流器注解支持 8 | * 9 | * @author zido 10 | */ 11 | @EnableLimiter 12 | @Configuration 13 | public class LimiterConfiguration { 14 | } 15 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/config/RedisConfiguration.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.config; 2 | 3 | import ai.grakn.redismock.RedisServer; 4 | import org.springframework.beans.factory.InitializingBean; 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.lettuce.LettuceConnectionFactory; 9 | 10 | import javax.annotation.PreDestroy; 11 | import java.io.IOException; 12 | 13 | /** 14 | * 配置一个内存redis链接 15 | * 16 | * @author zido 17 | */ 18 | @Configuration 19 | public class RedisConfiguration implements InitializingBean { 20 | 21 | private static RedisServer redisServer = null; 22 | private int port; 23 | 24 | public RedisConfiguration() throws IOException { 25 | redisServer = RedisServer.newRedisServer(); 26 | } 27 | 28 | public void startServer() throws IOException { 29 | redisServer.start(); 30 | port = redisServer.getBindPort(); 31 | } 32 | 33 | @Bean 34 | public RedisConnectionFactory factory() { 35 | return new LettuceConnectionFactory("127.0.0.1", port); 36 | } 37 | 38 | @Override 39 | public void afterPropertiesSet() throws Exception { 40 | startServer(); 41 | } 42 | 43 | @PreDestroy 44 | public void destroyServer() { 45 | redisServer.stop(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/entity/Admin.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 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 javax.persistence.Entity; 10 | import javax.persistence.Id; 11 | import java.util.Collection; 12 | import java.util.Collections; 13 | 14 | @Data 15 | @Entity 16 | public class Admin implements UserDetails { 17 | @Id 18 | private String username; 19 | private String password; 20 | private Boolean enabled = true; 21 | 22 | @Builder 23 | private Admin(String username, String password) { 24 | this.username = username; 25 | this.password = password; 26 | } 27 | 28 | @Override 29 | public Collection getAuthorities() { 30 | return Collections.singletonList(new SimpleGrantedAuthority("admin")); 31 | } 32 | 33 | @Override 34 | public boolean isAccountNonExpired() { 35 | return true; 36 | } 37 | 38 | @Override 39 | public boolean isAccountNonLocked() { 40 | return true; 41 | } 42 | 43 | @Override 44 | public boolean isCredentialsNonExpired() { 45 | return true; 46 | } 47 | 48 | @Override 49 | public boolean isEnabled() { 50 | return enabled; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/entity/Record.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.entity; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.ToString; 6 | 7 | import javax.persistence.*; 8 | import java.util.Date; 9 | 10 | /** 11 | * 入住记录 12 | */ 13 | @Entity 14 | @Data 15 | @EqualsAndHashCode 16 | @ToString(callSuper = true) 17 | public class Record { 18 | @Id 19 | @GeneratedValue 20 | private Integer id; 21 | 22 | /** 23 | * 入住用户id 24 | */ 25 | private Integer userId; 26 | 27 | /** 28 | * 本次入住人数 29 | */ 30 | private Integer count; 31 | 32 | /** 33 | * 入住结束时间 34 | */ 35 | @Column(columnDefinition = "timestamp default CURRENT_TIMESTAMP") 36 | @Temporal(TemporalType.TIMESTAMP) 37 | private Date endTime; 38 | } 39 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/entity/Room.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.entity; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.ToString; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.Id; 9 | 10 | @Entity 11 | @Data 12 | @EqualsAndHashCode 13 | @ToString(callSuper = true) 14 | public class Room { 15 | /** 16 | * 房间号 17 | */ 18 | @Id 19 | private String no; 20 | 21 | /** 22 | * 楼层 23 | */ 24 | private Integer floor; 25 | 26 | /** 27 | * 当前房间是否入住 28 | */ 29 | private Boolean status; 30 | } 31 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/entity/User.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | import java.util.Date; 11 | 12 | /** 13 | * 用户 14 | */ 15 | @Entity 16 | @Data 17 | public class User { 18 | @GeneratedValue 19 | @Id 20 | private Integer id; 21 | @Column(nullable = false, length = 30) 22 | private String username; 23 | @Column(nullable = false, length = 20) 24 | private String phone; 25 | /** 26 | * 性别,0:男,1:女 27 | */ 28 | private Integer sex = 0; 29 | /** 30 | * 密码 31 | */ 32 | private String password; 33 | 34 | /** 35 | * 卡号 36 | */ 37 | private String card; 38 | 39 | /** 40 | * vip 41 | */ 42 | private Boolean vip; 43 | 44 | private Boolean enabled = true; 45 | 46 | private Date createTime = new Date(); 47 | 48 | @Builder(builderMethodName = "registerBuilder") 49 | private User(String username, String phone, Integer sex, String password, String card, Boolean vip, Date createTime) { 50 | this.username = username; 51 | this.phone = phone; 52 | this.sex = sex; 53 | this.password = password; 54 | this.card = card; 55 | this.vip = vip; 56 | this.createTime = createTime; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/pojo/AuthUser.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.pojo; 2 | 3 | import lombok.Data; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import site.zido.demo.entity.User; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collection; 11 | import java.util.Collections; 12 | 13 | @Data 14 | public class AuthUser implements UserDetails { 15 | private static final Collection vip = 16 | Arrays.asList(new SimpleGrantedAuthority("a"), new SimpleGrantedAuthority("b")); 17 | private User user; 18 | 19 | public AuthUser(User user) { 20 | this.user = user; 21 | } 22 | 23 | @Override 24 | @SuppressWarnings("unchecked") 25 | public Collection getAuthorities() { 26 | return user.getVip() ? vip : Collections.EMPTY_LIST; 27 | } 28 | 29 | @Override 30 | public String getPassword() { 31 | return user.getPassword(); 32 | } 33 | 34 | @Override 35 | public String getUsername() { 36 | return user.getUsername(); 37 | } 38 | 39 | @Override 40 | public boolean isAccountNonExpired() { 41 | return true; 42 | } 43 | 44 | @Override 45 | public boolean isAccountNonLocked() { 46 | return true; 47 | } 48 | 49 | @Override 50 | public boolean isCredentialsNonExpired() { 51 | return true; 52 | } 53 | 54 | @Override 55 | public boolean isEnabled() { 56 | return user.getEnabled(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/pojo/dto/UserDTO.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.pojo.dto; 2 | 3 | import lombok.Data; 4 | import site.zido.coffee.core.utils.OutputConverter; 5 | import site.zido.demo.entity.User; 6 | 7 | @Data 8 | public class UserDTO implements OutputConverter { 9 | private Integer id; 10 | private String username; 11 | /** 12 | * 性别,0:男,1:女 13 | */ 14 | private Integer sex = 0; 15 | /** 16 | * vip 17 | */ 18 | private Boolean vip; 19 | 20 | private Boolean enabled = true; 21 | } 22 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/pojo/params/UserParams.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.pojo.params; 2 | 3 | import lombok.Data; 4 | import site.zido.coffee.core.utils.InputConverter; 5 | import site.zido.coffee.core.validations.Phone; 6 | import site.zido.demo.entity.User; 7 | 8 | import javax.validation.constraints.Size; 9 | 10 | @Data 11 | public class UserParams implements InputConverter { 12 | @Size(min = 6, max = 10) 13 | private String username; 14 | @Size(min = 6, max = 30) 15 | private String password; 16 | @Size(min = 11, max = 11) 17 | @Phone 18 | private String mobile; 19 | private Integer sex = 0; 20 | private String card; 21 | private String vip; 22 | private Boolean enabled = true; 23 | } 24 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/repository/AdminRepository.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import site.zido.demo.entity.Admin; 5 | 6 | public interface AdminRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/repository/RecordRepository.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import site.zido.demo.entity.Record; 5 | 6 | public interface RecordRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/repository/RoomRepository.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import site.zido.demo.entity.Room; 5 | 6 | public interface RoomRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.repository; 2 | 3 | import org.springframework.data.domain.Sort; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import site.zido.demo.entity.User; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public interface UserRepository extends JpaRepository { 11 | 12 | Optional findByUsername(String username); 13 | 14 | Optional findByPhone(String phone); 15 | 16 | List findAllByIdIn(List ids, Sort createTime); 17 | } 18 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/service/IRoomService.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.service; 2 | 3 | import site.zido.demo.entity.Room; 4 | 5 | import java.util.List; 6 | 7 | public interface IRoomService { 8 | List getRooms(); 9 | } 10 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/service/IUserService.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.service; 2 | 3 | import site.zido.demo.entity.Admin; 4 | import site.zido.demo.pojo.dto.UserDTO; 5 | import site.zido.demo.pojo.params.UserParams; 6 | 7 | import java.util.List; 8 | 9 | public interface IUserService { 10 | void addUser(UserParams userParams, Admin opUser); 11 | 12 | List getUsers(List ids); 13 | } 14 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/service/impl/RoomServiceImpl.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.service.impl; 2 | 3 | import org.springframework.stereotype.Service; 4 | import site.zido.demo.entity.Room; 5 | import site.zido.demo.repository.RoomRepository; 6 | import site.zido.demo.service.IRoomService; 7 | 8 | import java.util.List; 9 | 10 | @Service 11 | public class RoomServiceImpl implements IRoomService { 12 | private final RoomRepository roomRepository; 13 | 14 | public RoomServiceImpl(RoomRepository roomRepository) { 15 | this.roomRepository = roomRepository; 16 | } 17 | 18 | @Override 19 | public List getRooms() { 20 | return roomRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/Production/src/main/java/site/zido/demo/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.service.impl; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.data.domain.Sort; 5 | import org.springframework.stereotype.Service; 6 | import site.zido.demo.entity.Admin; 7 | import site.zido.demo.pojo.dto.UserDTO; 8 | import site.zido.demo.pojo.params.UserParams; 9 | import site.zido.demo.repository.UserRepository; 10 | import site.zido.demo.service.IUserService; 11 | 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | import static org.springframework.data.domain.Sort.Direction.DESC; 16 | 17 | @Service 18 | @Slf4j 19 | public class UserServiceImpl implements IUserService { 20 | private final UserRepository userRepository; 21 | 22 | public UserServiceImpl(UserRepository userRepository) { 23 | this.userRepository = userRepository; 24 | } 25 | 26 | @Override 27 | public void addUser(UserParams userParams, Admin opUser) { 28 | log.info("管理员:{}添加一个用户{}", opUser.getUsername(), userParams); 29 | userRepository.save(userParams.convertTo()); 30 | } 31 | 32 | public List getUsers(List ids) { 33 | return userRepository 34 | .findAllByIdIn(ids, Sort.by(DESC, "createTime")) 35 | .stream() 36 | .map(user -> new UserDTO().convertFrom(user)) 37 | .collect(Collectors.toList()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/Production/src/test/java/site/zido/demo/api/integration_test/SecurityTest.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.api.integration_test; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestEntityManager; 8 | import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; 9 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.mock.web.MockHttpServletResponse; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.test.context.junit.jupiter.SpringExtension; 15 | import org.springframework.test.web.servlet.MockMvc; 16 | import org.springframework.transaction.annotation.Transactional; 17 | import site.zido.demo.entity.Admin; 18 | import site.zido.demo.entity.User; 19 | 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 22 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; 24 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 25 | 26 | @ExtendWith(SpringExtension.class) 27 | @SpringBootTest 28 | @AutoConfigureTestEntityManager 29 | @AutoConfigureMockMvc 30 | @Transactional 31 | public class SecurityTest { 32 | @Autowired 33 | private MockMvc mvc; 34 | @Autowired 35 | private TestEntityManager manager; 36 | @Autowired 37 | private PasswordEncoder encoder; 38 | 39 | @BeforeEach 40 | public void setUp() { 41 | User user = User.registerBuilder() 42 | .username("user") 43 | .password(encoder.encode("user")) 44 | .phone("13512341234") 45 | .build(); 46 | manager.persist(user); 47 | Admin admin = Admin.builder() 48 | .username("admin") 49 | .password(encoder.encode("admin")) 50 | .build(); 51 | manager.persist(admin); 52 | } 53 | 54 | @Test 55 | public void testAnonymous() throws Exception { 56 | mvc.perform(get("/rooms")) 57 | .andExpect(status().isOk()); 58 | } 59 | 60 | @Test 61 | public void testAuthenticated() throws Exception { 62 | mvc.perform(post("/users/sessions") 63 | .header("role", "admin") 64 | .param("username", "admin") 65 | .param("password", "admin")) 66 | .andExpect(status().isOk()) 67 | .andExpect(header().exists("Authorization")) 68 | .andDo(result -> { 69 | MockHttpServletResponse response = result.getResponse(); 70 | String authorization = response.getHeader("Authorization"); 71 | this.mvc.perform(get("/admin/users") 72 | .header("Authorization", authorization) 73 | .accept(MediaType.APPLICATION_JSON)) 74 | .andExpect(status().isOk()) 75 | .andDo(print()); 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/Production/src/test/java/site/zido/demo/api/integration_test/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 集成测试包 3 | * 4 | * @author zido 5 | */ 6 | package site.zido.demo.api.integration_test; -------------------------------------------------------------------------------- /examples/default-use/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | default-use 7 | 全默认集成使用 8 | 9 | coffee-spring-boot-parent 10 | site.zido 11 | 0.3.0-SNAPSHOT 12 | ../../coffee-spring-boot-parent 13 | 14 | 15 | 16 | central 17 | Central Repository 18 | https://maven.aliyun.com/repository/central 19 | default 20 | 21 | false 22 | 23 | 24 | 25 | 26 | 27 | central 28 | Central Repository 29 | https://maven.aliyun.com/repository/central 30 | default 31 | 32 | false 33 | 34 | 35 | 36 | 37 | 38 | site.zido 39 | coffee-spring-boot-starter 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-security 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | org.springframework.security 56 | spring-security-test 57 | test 58 | 59 | 60 | org.junit.jupiter 61 | junit-jupiter-engine 62 | test 63 | 64 | 65 | org.hibernate 66 | hibernate-validator 67 | 6.0.1.Final 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/default-use/src/main/java/site/zido/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * 启动器 10 | *

11 | * 本模块是完全采取默认规则进行集成的项目 12 | *

13 | * 完全与spring boot同样的使用,能带来更多的restful配置 14 | * 15 | * @author zido 16 | */ 17 | @SpringBootApplication 18 | public class DemoApplication { 19 | 20 | public static void main(String[] args) throws IOException { 21 | SpringApplication.run(DemoApplication.class); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/default-use/src/main/java/site/zido/demo/api/IndexController.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.api; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 5 | import org.springframework.security.core.userdetails.UserDetails; 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 | /** 11 | * 一些简单接口使用 12 | * 13 | * @author zido 14 | */ 15 | @RestController 16 | public class IndexController { 17 | 18 | @GetMapping("/global-result") 19 | public String testGlobalResult() { 20 | return "someString"; 21 | } 22 | 23 | @PreAuthorize("hasAuthority('ROLE_user')") 24 | @RequestMapping("/hello") 25 | public String index(@AuthenticationPrincipal UserDetails user) { 26 | return "hello world : " + user.getUsername(); 27 | } 28 | 29 | @PreAuthorize("hasAuthority('ROLE_admin')") 30 | @RequestMapping("/admin") 31 | public String admin(@AuthenticationPrincipal UserDetails user) { 32 | return "hello world : " + user.getUsername(); 33 | } 34 | 35 | @GetMapping("/runtime-err") 36 | public String throwNewRuntimeError() { 37 | throw new RuntimeException(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/default-use/src/main/java/site/zido/demo/api/LimiterController.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.api; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.cache.annotation.EnableCaching; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import site.zido.coffee.extra.limiter.Limiter; 10 | 11 | /** 12 | * 注解限制频率 13 | * 14 | * @author zido 15 | */ 16 | @RequestMapping("/limit") 17 | @RestController 18 | @EnableCaching 19 | public class LimiterController { 20 | 21 | @RequestMapping 22 | @Limiter(timeout = 5) 23 | public String limit() { 24 | return "limit content"; 25 | } 26 | 27 | /** 28 | * 模拟发送短信的接口,能够根据手机号进行频率限制 29 | * 30 | * @param phone phone 31 | * @return content 32 | */ 33 | @RequestMapping("/sms") 34 | @Limiter(key = "'content:' + #phone", timeout = 5) 35 | public String limit(String phone) { 36 | return "limit content"; 37 | } 38 | 39 | /** 40 | * restful api 限制频率 41 | * 42 | * @param phone phone 43 | * @return content 44 | */ 45 | @RequestMapping("/{phone}/sms") 46 | @Limiter(key = "'content:path:' + #phone", timeout = 5) 47 | public String inlineLimit(@PathVariable String phone) { 48 | return limit("inline"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/default-use/src/main/java/site/zido/demo/config/AuthConfig.java: -------------------------------------------------------------------------------- 1 | package site.zido.demo.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 10 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 11 | 12 | /** 13 | * 认证配置类,restful风格,使用jwt方案 14 | * 15 | * @author zido 16 | */ 17 | @Configuration 18 | @EnableWebSecurity(debug = true) 19 | public class AuthConfig { 20 | /** 21 | * 创建几个内存用户,正常使用时,需要自定义userDetailsService 22 | * 23 | * @return userDetailsService 24 | */ 25 | @Bean 26 | protected UserDetailsService userDetailsService() { 27 | UserDetails user = User.builder().username("user") 28 | .password("user") 29 | .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode) 30 | .roles("user") 31 | .build(); 32 | UserDetails user2 = User.builder().username("13512341234") 33 | .password("xxx") 34 | .roles("user") 35 | .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode) 36 | .build(); 37 | UserDetails user3 = User.builder().username("13512341235") 38 | .password("xxx") 39 | .roles("admin") 40 | .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode) 41 | .build(); 42 | return new InMemoryUserDetailsManager(user, user2, user3); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /examples/default-use/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=SimpleDemo -------------------------------------------------------------------------------- /examples/default-use/src/test/java/site/zido/demo/api/integration_test/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 集成测试包 3 | * 4 | * @author zido 5 | */ 6 | package site.zido.demo.api.integration_test; --------------------------------------------------------------------------------