mrbird před 6 roky
revize
31fda68838
100 změnil soubory, kde provedl 5243 přidání a 0 odebrání
  1. 10 0
      .github/issue_template.md
  2. 4 0
      .gitignore
  3. 201 0
      LICENSE
  4. 138 0
      Readme.md
  5. 5 0
      febs-auth/Dockerfile
  6. 58 0
      febs-auth/pom.xml
  7. 4 0
      febs-auth/run.sh
  8. 22 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/FebsAuthApplication.java
  9. 78 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/configure/FebsAuthorizationServerConfigurer.java
  10. 48 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/configure/FebsResourceServerConfigurer.java
  11. 53 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/configure/FebsSecurityConfigure.java
  12. 25 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/configure/JWTConfigure.java
  13. 45 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/controller/SecurityController.java
  14. 84 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/filter/ValidateCodeFilter.java
  15. 14 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/handler/GlobalExceptionHandler.java
  16. 46 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/manager/UserManager.java
  17. 14 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/mapper/MenuMapper.java
  18. 12 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/mapper/UserMapper.java
  19. 29 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/properties/FebsAuthProperties.java
  20. 34 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/properties/FebsClientsProperties.java
  21. 38 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/properties/FebsValidateCodeProperties.java
  22. 34 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/runner/StartedUpRunner.java
  23. 44 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/service/FebsUserDetailService.java
  24. 93 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/service/ValidateCodeService.java
  25. 46 0
      febs-auth/src/main/java/cc/mrbird/febs/auth/translator/FebsWebResponseExceptionTranslator.java
  26. 8 0
      febs-auth/src/main/resources/banner.txt
  27. 26 0
      febs-auth/src/main/resources/bootstrap.yml
  28. 21 0
      febs-auth/src/main/resources/febs-auth.properties
  29. 61 0
      febs-auth/src/main/resources/logback-spring.xml
  30. 16 0
      febs-auth/src/main/resources/mapper/MenuMapper.xml
  31. 31 0
      febs-auth/src/main/resources/mapper/UserMapper.xml
  32. 36 0
      febs-cloud/docker compose/elk/docker-compose.yml
  33. 119 0
      febs-cloud/docker compose/febs-cloud/docker-compose.yml
  34. 30 0
      febs-cloud/docker compose/third-part/docker-compose.yml
  35. 49 0
      febs-cloud/pom.xml
  36. 85 0
      febs-common/pom.xml
  37. 16 0
      febs-common/src/main/java/cc/mrbird/febs/common/annotation/EnableFebsAuthExceptionHandler.java
  38. 16 0
      febs-common/src/main/java/cc/mrbird/febs/common/annotation/EnableFebsLettuceRedis.java
  39. 16 0
      febs-common/src/main/java/cc/mrbird/febs/common/annotation/EnableFebsOauth2FeignClient.java
  40. 16 0
      febs-common/src/main/java/cc/mrbird/febs/common/annotation/EnableFebsServerProtect.java
  41. 20 0
      febs-common/src/main/java/cc/mrbird/febs/common/annotation/Fallback.java
  42. 16 0
      febs-common/src/main/java/cc/mrbird/febs/common/annotation/FebsCloudApplication.java
  43. 18 0
      febs-common/src/main/java/cc/mrbird/febs/common/annotation/Helper.java
  44. 25 0
      febs-common/src/main/java/cc/mrbird/febs/common/annotation/IsMobile.java
  45. 12 0
      febs-common/src/main/java/cc/mrbird/febs/common/annotation/Log.java
  46. 26 0
      febs-common/src/main/java/cc/mrbird/febs/common/configure/FebsAuthExceptionConfigure.java
  47. 55 0
      febs-common/src/main/java/cc/mrbird/febs/common/configure/FebsLettuceRedisConfigure.java
  48. 32 0
      febs-common/src/main/java/cc/mrbird/febs/common/configure/FebsOAuth2FeignConfigure.java
  49. 34 0
      febs-common/src/main/java/cc/mrbird/febs/common/configure/FebsServerProtectConfigure.java
  50. 31 0
      febs-common/src/main/java/cc/mrbird/febs/common/converter/TimeConverter.java
  51. 15 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/DeptTree.java
  52. 51 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/FebsAuthUser.java
  53. 45 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/FebsConstant.java
  54. 35 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/FebsResponse.java
  55. 18 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/FebsServerConstant.java
  56. 20 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/MenuTree.java
  57. 32 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/QueryRequest.java
  58. 13 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/RegexpConstant.java
  59. 32 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/Tree.java
  60. 23 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/router/RouterMeta.java
  61. 44 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/router/VueRouter.java
  62. 30 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Column.java
  63. 50 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Dept.java
  64. 47 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Eximport.java
  65. 123 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/GeneratorConfig.java
  66. 72 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/GeneratorConstant.java
  67. 89 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Log.java
  68. 163 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/LoginLog.java
  69. 115 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Menu.java
  70. 51 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Role.java
  71. 22 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/RoleMenu.java
  72. 158 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/SystemUser.java
  73. 30 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Table.java
  74. 24 0
      febs-common/src/main/java/cc/mrbird/febs/common/entity/system/UserRole.java
  75. 15 0
      febs-common/src/main/java/cc/mrbird/febs/common/exception/FebsException.java
  76. 14 0
      febs-common/src/main/java/cc/mrbird/febs/common/exception/FileDownloadException.java
  77. 15 0
      febs-common/src/main/java/cc/mrbird/febs/common/exception/ValidateCodeException.java
  78. 91 0
      febs-common/src/main/java/cc/mrbird/febs/common/handler/BaseExceptionHandler.java
  79. 25 0
      febs-common/src/main/java/cc/mrbird/febs/common/handler/FebsAccessDeniedHandler.java
  80. 27 0
      febs-common/src/main/java/cc/mrbird/febs/common/handler/FebsAuthExceptionEntryPoint.java
  81. 35 0
      febs-common/src/main/java/cc/mrbird/febs/common/interceptor/FebsServerProtectInterceptor.java
  82. 24 0
      febs-common/src/main/java/cc/mrbird/febs/common/selector/FebsCloudApplicationSelector.java
  83. 557 0
      febs-common/src/main/java/cc/mrbird/febs/common/service/RedisService.java
  84. 84 0
      febs-common/src/main/java/cc/mrbird/febs/common/utils/DateUtil.java
  85. 114 0
      febs-common/src/main/java/cc/mrbird/febs/common/utils/FebsUtil.java
  86. 157 0
      febs-common/src/main/java/cc/mrbird/febs/common/utils/FileUtil.java
  87. 19 0
      febs-common/src/main/java/cc/mrbird/febs/common/utils/HttpContextUtil.java
  88. 34 0
      febs-common/src/main/java/cc/mrbird/febs/common/utils/IPUtil.java
  89. 125 0
      febs-common/src/main/java/cc/mrbird/febs/common/utils/SortUtil.java
  90. 46 0
      febs-common/src/main/java/cc/mrbird/febs/common/utils/SpringContextUtil.java
  91. 93 0
      febs-common/src/main/java/cc/mrbird/febs/common/utils/TreeUtil.java
  92. 35 0
      febs-common/src/main/java/cc/mrbird/febs/common/validator/MobileValidator.java
  93. 5 0
      febs-config/Dockerfile
  94. 46 0
      febs-config/pom.xml
  95. 60 0
      febs-config/repository/config/FEBS-Auth-dev.yml
  96. 61 0
      febs-config/repository/config/FEBS-Auth-prod.yml
  97. 60 0
      febs-config/repository/config/FEBS-Auth-test.yml
  98. 57 0
      febs-config/repository/config/FEBS-Gateway.yml
  99. 74 0
      febs-config/repository/config/FEBS-Server-System-dev.yml
  100. 74 0
      febs-config/repository/config/FEBS-Server-System-prod.yml

+ 10 - 0
.github/issue_template.md

@@ -0,0 +1,10 @@
+### 问题描述
+简单描述下遇到的问题
+
+### 复现步骤
+简单描述下问题复现的步骤
+
+### 相关截图
+提供相关截图,以协助问题分析
+
+> issue模板,by MrBird.

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+**/target/
+**/.idea/
+**/log/
+**/*.iml

+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [2019] [febs & mrbird]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 138 - 0
Readme.md

@@ -0,0 +1,138 @@
+### FEBS Cloud 微服务权限系统
+![https://img.shields.io/badge/license-Apache%202.0-blue.svg?longCache=true&style=flat-square](https://img.shields.io/badge/license-Apache%202.0-blue.svg?longCache=true&style=flat-square)
+![https://img.shields.io/badge/springcloud-Greenwich.SR1-yellow.svg?style=flat-square](https://img.shields.io/badge/springcloud-Greenwich.SR1-yellow.svg?style=flat-square)
+![https://img.shields.io/badge/springboot-2.1.6.RELEASE-brightgreen.svg?style=flat-square](https://img.shields.io/badge/springboot-2.1.6.RELEASE-brightgreen.svg?style=flat-square)
+![https://img.shields.io/badge/vue-2.6.10-orange.svg?style=flat-square](https://img.shields.io/badge/vue-2.6.10-orange.svg?style=flat-square)
+
+FEBS Cloud是一款使用Spring Cloud Greenwich.SR1、Spring Cloud OAuth2和Spring Cloud Security构建的权限管理系统,前端(FEBS Cloud Web)采用vue element admin构建。FEBS意指:**F**ast,**E**asy use,**B**eautiful和**S**afe。该系统具有如下特点:
+
+1. 前后端分离架构,客户端和服务端纯Token交互;
+ 
+2. 认证服务器与资源服务器分离,方便接入自己的微服务系统;
+
+3. 微服务防护,客户端请求资源只能通过微服务网关获取;
+
+4. 集成Spring Boot Admin,多维度监控微服务;
+
+5. 集成Zipkin,方便跟踪Feign调用链;
+
+6. 集成ELK,集中管理日志,便于问题分析;
+
+7. 微服务Docker化,使用Docker Compose一键部署;
+
+8. 提供详细的使用文档和搭建教程;
+
+9. 前后端请求参数校验,Excel导入导出,代码生成等。
+
+### 文档与教程
+
+项目文档及手摸手搭建教程地址:[https://www.kancloud.cn/mrbird/spring-cloud/1263679](https://www.kancloud.cn/mrbird/spring-cloud/1263679)
+
+### 系统架构
+
+系统架构如下图所示(右键在新标签页中打开图片):
+
+![febs-cloud.png](images/febs-cloud.png)
+
+### 项目地址
+
+ 平台  | FEBS Cloud(后端)|FEBS Cloud Web(前端)
+---|---|---
+GitHub | [https://github.com/wuyouzhuguli/FEBS-Cloud](https://github.com/wuyouzhuguli/FEBS-Cloud)|[https://github.com/wuyouzhuguli/FEBS-Cloud-Web](https://github.com/wuyouzhuguli/FEBS-Cloud-Web)
+Gitee  | [https://gitee.com/mrbirdd/FEBS-Cloud](https://gitee.com/mrbirdd/FEBS-Cloud)|[https://gitee.com/mrbirdd/FEBS-Cloud-Web](https://gitee.com/mrbirdd/FEBS-Cloud-Web)
+
+### 演示地址
+
+演示地址(服务器资源有限,没有搭建ELK):[http://49.234.20.223:9527](http://49.234.20.223:9527)
+
+演示环境账号密码:
+
+账号 | 密码| 权限
+---|---|---
+scott | 1234qwer | 注册账户,拥有查看权限
+
+本地部署账号密码:
+
+账号 | 密码| 权限
+---|---|---
+mrbird | 1234qwer |超级管理员,拥有所有增删改查权限
+scott | 1234qwer | 注册账户,拥有查看,新增权限(新增用户除外)和导出Excel权限
+jane | 1234qwer |系统监测员,负责整个系统监控模块
+
+### 服务模块
+
+FEBS模块:
+
+服务名称 | 端口 | 描述
+---|---|---
+FEBS-Register| 8001 |微服务注册中心 
+FEBS-Auth| 8101| 微服务认证服务器 
+FEBS-Server-System| 8201 | 微服务子系统(资源服务器)
+FEBS-Server-Test|8202 | 微服务子系统(资源服务器)
+FEBS-Gateway|8301|微服务网关
+FEBS-Monitor-Admin|8401|微服务监控子系统
+Zipkin-Server|8402|Zipkin服务器
+FEBS-Config|8501|微服务配置子系统
+
+第三方模块:
+
+服务名称 | 端口 | 描述
+---|---|---
+MySQL| 3306 |MySQL 数据库 
+RabbitMQ|5672|RabbitMQ 消息中间件 
+Redis| 6379 | K-V 缓存数据库 
+Elasticsearch|9200 | 日志存储
+Logstash|4560|日志收集
+Kibana|5601|日志展示
+
+### 目录结构
+```
+├─febs-auth                       ------ 微服务认证服务器
+├─febs-cloud                      ------ 整个项目的父模块
+│  └─docker compose               ------ 存放docker compose文件
+│      ├─elk                      ------ ELK docker compose文件
+│      ├─febs-cloud               ------ 聚合所有微服务子项目的docker compose文件
+│      └─third-part               ------ 第三方服务(MySQL,Redis等)docker compose文件
+├─febs-common                     ------ 通用模块
+├─febs-config                     ------ 微服务配置中心
+├─febs-gateway                    ------ 微服务网关
+├─febs-monitor                    ------ 微服务监控父模块
+│  ├─febs-monitor-admin           ------ 微服务监控中心
+│  └─zipkin-server                ------ zipkin 服务
+├─febs-register                   ------ 微服务注册中心
+└─febs-server                     ------ 资源服务器
+   ├─febs-server-system           ------- 资源服务器系统模块
+   └─febs-server-test             ------ 资源服务器demo,演示如何整合自己的微服务系统
+```
+### 系统截图
+
+![1](images/1.png)
+
+![2](images/2.png)
+
+![3](images/3.png)
+
+![4](images/4.png)
+
+![5](images/5.png)
+
+![6](images/6.png)
+
+![7](images/7.png)
+
+![8](images/8.png)
+
+### 参与贡献
+
+欢迎提交PR一起完善项目,以下为提PR并合并的小伙伴(排名不分先后):
+
+<a href="https://github.com/sonake">
+    <img src="https://avatars3.githubusercontent.com/u/46209482?s=400&v=4" width="45px"></a>
+<a href="https://github.com/mgzu">
+    <img src="https://avatars1.githubusercontent.com/u/29629221?s=400&v=4" width="45px"></a>
+
+### 反馈交流
+
+加入QQ群和大家一起~~交流~~吹水:
+
+![qq](images/QQ.jpg)

+ 5 - 0
febs-auth/Dockerfile

@@ -0,0 +1,5 @@
+FROM openjdk:8u212-jre
+MAINTAINER MrBird 852252810@qq.com
+
+COPY ./target/febs-auth-1.0-SNAPSHOT.jar /febs/febs-auth-1.0-SNAPSHOT.jar
+ENTRYPOINT ["java", "-Xmx256m", "-jar", "/febs/febs-auth-1.0-SNAPSHOT.jar"]

+ 58 - 0
febs-auth/pom.xml

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cc.mrbird</groupId>
+        <artifactId>febs-cloud</artifactId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../febs-cloud/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>febs-auth</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>FEBS-Auth</name>
+    <description>FEBS-Cloud认证服务器</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>cc.mrbird</groupId>
+            <artifactId>febs-common</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+            <version>2.5.4</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.whvcse</groupId>
+            <artifactId>easy-captcha</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.logstash.logback</groupId>
+            <artifactId>logstash-logback-encoder</artifactId>
+            <version>6.1</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 4 - 0
febs-auth/run.sh

@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+mvn package
+
+docker build -t febs-auth .

+ 22 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/FebsAuthApplication.java

@@ -0,0 +1,22 @@
+package cc.mrbird.febs.auth;
+
+import cc.mrbird.febs.common.annotation.EnableFebsAuthExceptionHandler;
+import cc.mrbird.febs.common.annotation.EnableFebsLettuceRedis;
+import cc.mrbird.febs.common.annotation.EnableFebsServerProtect;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+@EnableDiscoveryClient
+@EnableFebsLettuceRedis
+@EnableFebsAuthExceptionHandler
+@EnableFebsServerProtect
+@SpringBootApplication
+@MapperScan("cc.mrbird.febs.auth.mapper")
+public class FebsAuthApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(FebsAuthApplication.class, args);
+    }
+}

+ 78 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/configure/FebsAuthorizationServerConfigurer.java

@@ -0,0 +1,78 @@
+package cc.mrbird.febs.auth.configure;
+
+import cc.mrbird.febs.auth.properties.FebsAuthProperties;
+import cc.mrbird.febs.auth.properties.FebsClientsProperties;
+import cc.mrbird.febs.auth.service.FebsUserDetailService;
+import cc.mrbird.febs.auth.translator.FebsWebResponseExceptionTranslator;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
+import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
+import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
+import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
+import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
+import org.springframework.security.oauth2.provider.token.TokenStore;
+import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
+
+/**
+ * 认证服务器配置
+ *
+ * @author MrBird
+ */
+@Configuration
+@EnableAuthorizationServer
+public class FebsAuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
+
+    @Autowired
+    private AuthenticationManager authenticationManager;
+    @Autowired
+    private FebsUserDetailService userDetailService;
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+    @Autowired
+    private FebsWebResponseExceptionTranslator exceptionTranslator;
+    @Autowired
+    private TokenStore jwtTokenStore;
+    @Autowired
+    private JwtAccessTokenConverter jwtAccessTokenConverter;
+    @Autowired
+    private FebsAuthProperties properties;
+
+    @Override
+    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
+        FebsClientsProperties[] clientsArray = properties.getClients();
+        InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
+        if (ArrayUtils.isNotEmpty(clientsArray)) {
+            for (FebsClientsProperties client : clientsArray) {
+                if (StringUtils.isBlank(client.getClient())) {
+                    throw new Exception("client不能为空");
+                }
+                if (StringUtils.isBlank(client.getSecret())) {
+                    throw new Exception("secret不能为空");
+                }
+                String[] grantTypes = StringUtils.splitByWholeSeparatorPreserveAllTokens(client.getGrantType(), ",");
+                builder.withClient(client.getClient())
+                        .secret(passwordEncoder.encode(client.getSecret()))
+                        .authorizedGrantTypes(grantTypes)
+                        .accessTokenValiditySeconds(client.getAccessTokenValiditySeconds())
+                        .refreshTokenValiditySeconds(client.getRefreshTokenValiditySeconds())
+                        .scopes(client.getScope());
+            }
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
+        endpoints.tokenStore(jwtTokenStore)
+                .accessTokenConverter(jwtAccessTokenConverter)
+                .userDetailsService(userDetailService)
+                .authenticationManager(authenticationManager)
+                .exceptionTranslator(exceptionTranslator);
+    }
+
+}

+ 48 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/configure/FebsResourceServerConfigurer.java

@@ -0,0 +1,48 @@
+package cc.mrbird.febs.auth.configure;
+
+import cc.mrbird.febs.auth.properties.FebsAuthProperties;
+import cc.mrbird.febs.common.handler.FebsAccessDeniedHandler;
+import cc.mrbird.febs.common.handler.FebsAuthExceptionEntryPoint;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
+import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
+import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
+
+/**
+ * 资源服务器配置
+ *
+ * @author MrBird
+ */
+@Configuration
+@EnableResourceServer
+public class FebsResourceServerConfigurer extends ResourceServerConfigurerAdapter {
+
+    @Autowired
+    private FebsAccessDeniedHandler accessDeniedHandler;
+    @Autowired
+    private FebsAuthExceptionEntryPoint exceptionEntryPoint;
+    @Autowired
+    private FebsAuthProperties properties;
+
+    @Override
+    public void configure(HttpSecurity http) throws Exception {
+        String[] anonUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(properties.getAnonUrl(), ",");
+
+        http.csrf().disable()
+                .requestMatchers().antMatchers("/**")
+                .and()
+                .authorizeRequests()
+                .antMatchers(anonUrls).permitAll()
+                .antMatchers("/**").authenticated()
+                .and().httpBasic();
+    }
+
+    @Override
+    public void configure(ResourceServerSecurityConfigurer resources) {
+        resources.authenticationEntryPoint(exceptionEntryPoint)
+                .accessDeniedHandler(accessDeniedHandler);
+    }
+}

+ 53 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/configure/FebsSecurityConfigure.java

@@ -0,0 +1,53 @@
+package cc.mrbird.febs.auth.configure;
+
+import cc.mrbird.febs.auth.filter.ValidateCodeFilter;
+import cc.mrbird.febs.auth.service.FebsUserDetailService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+/**
+ * WebSecurity配置
+ *
+ * @author MrBird
+ */
+@Order(2)
+@EnableWebSecurity
+public class FebsSecurityConfigure extends WebSecurityConfigurerAdapter {
+
+    @Autowired
+    private FebsUserDetailService userDetailService;
+    @Autowired
+    private ValidateCodeFilter validateCodeFilter;
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+
+    @Bean
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
+                .requestMatchers()
+                .antMatchers("/oauth/**")
+                .and()
+                .authorizeRequests()
+                .antMatchers("/oauth/**").authenticated()
+                .and()
+                .csrf().disable();
+    }
+
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+        auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder);
+    }
+}

+ 25 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/configure/JWTConfigure.java

@@ -0,0 +1,25 @@
+package cc.mrbird.febs.auth.configure;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.oauth2.provider.token.TokenStore;
+import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
+import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
+
+/**
+ * @author MrBird
+ */
+@Configuration
+public class JWTConfigure {
+    @Bean
+    public TokenStore jwtTokenStore() {
+        return new JwtTokenStore(jwtAccessTokenConverter());
+    }
+
+    @Bean
+    public JwtAccessTokenConverter jwtAccessTokenConverter() {
+        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
+        accessTokenConverter.setSigningKey("febs");
+        return accessTokenConverter;
+    }
+}

+ 45 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/controller/SecurityController.java

@@ -0,0 +1,45 @@
+package cc.mrbird.febs.auth.controller;
+
+import cc.mrbird.febs.auth.manager.UserManager;
+import cc.mrbird.febs.auth.service.ValidateCodeService;
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.entity.system.SystemUser;
+import cc.mrbird.febs.common.exception.ValidateCodeException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.Principal;
+
+/**
+ * @author MrBird
+ */
+@RestController
+public class SecurityController {
+
+    @Autowired
+    private ValidateCodeService validateCodeService;
+    @Autowired
+    private UserManager userManager;
+
+    @GetMapping("user")
+    public Principal currentUser(Principal principal) {
+        return principal;
+    }
+
+    @GetMapping("user/detail")
+    public FebsResponse currentUserDetail(Principal principal) {
+        SystemUser user = userManager.findByName(principal.getName());
+        user.setPassword("secret");
+        return new FebsResponse().data(user);
+    }
+
+    @GetMapping("captcha")
+    public void captcha(HttpServletRequest request, HttpServletResponse response) throws IOException, ValidateCodeException {
+        validateCodeService.create(request, response);
+    }
+
+}

+ 84 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/filter/ValidateCodeFilter.java

@@ -0,0 +1,84 @@
+package cc.mrbird.febs.auth.filter;
+
+import cc.mrbird.febs.auth.service.ValidateCodeService;
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.exception.ValidateCodeException;
+import cc.mrbird.febs.common.utils.FebsUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.annotation.Nonnull;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+/**
+ * 验证码过滤器
+ *
+ * @author MrBird
+ */
+@Slf4j
+@Component
+public class ValidateCodeFilter extends OncePerRequestFilter {
+
+    @Autowired
+    private ValidateCodeService validateCodeService;
+
+    @Override
+    protected void doFilterInternal(@Nonnull HttpServletRequest httpServletRequest, @Nonnull HttpServletResponse httpServletResponse, @Nonnull FilterChain filterChain) throws ServletException, IOException {
+        String header = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
+        String clientId = getClientId(header, httpServletRequest);
+
+        RequestMatcher matcher = new AntPathRequestMatcher("/oauth/token", HttpMethod.POST.toString());
+        if (matcher.matches(httpServletRequest)
+                && StringUtils.equalsIgnoreCase(httpServletRequest.getParameter("grant_type"), "password")
+                && !StringUtils.equalsAnyIgnoreCase(clientId, "swagger")) {
+            try {
+                validateCode(httpServletRequest);
+                filterChain.doFilter(httpServletRequest, httpServletResponse);
+            } catch (ValidateCodeException e) {
+                FebsResponse febsResponse = new FebsResponse();
+                FebsUtil.makeResponse(httpServletResponse, MediaType.APPLICATION_JSON_UTF8_VALUE,
+                        HttpServletResponse.SC_INTERNAL_SERVER_ERROR, febsResponse.message(e.getMessage()));
+                log.error(e.getMessage(), e);
+            }
+        } else {
+            filterChain.doFilter(httpServletRequest, httpServletResponse);
+        }
+    }
+
+    private void validateCode(HttpServletRequest httpServletRequest) throws ValidateCodeException {
+        String code = httpServletRequest.getParameter("code");
+        String key = httpServletRequest.getParameter("key");
+        validateCodeService.check(key, code);
+    }
+
+    private String getClientId(String header, HttpServletRequest request) {
+        String clientId = "";
+        try {
+            byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
+            byte[] decoded;
+            decoded = Base64.getDecoder().decode(base64Token);
+
+            String token = new String(decoded, StandardCharsets.UTF_8);
+            int delim = token.indexOf(":");
+            if (delim != -1) {
+                clientId = new String[]{token.substring(0, delim), token.substring(delim + 1)}[0];
+            }
+        } catch (Exception ignore) {
+        }
+        return clientId;
+    }
+}

+ 14 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/handler/GlobalExceptionHandler.java

@@ -0,0 +1,14 @@
+package cc.mrbird.febs.auth.handler;
+
+import cc.mrbird.febs.common.handler.BaseExceptionHandler;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * @author MrBird
+ */
+@RestControllerAdvice
+@Order(value = Ordered.HIGHEST_PRECEDENCE)
+public class GlobalExceptionHandler extends BaseExceptionHandler {
+}

+ 46 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/manager/UserManager.java

@@ -0,0 +1,46 @@
+package cc.mrbird.febs.auth.manager;
+
+import cc.mrbird.febs.auth.mapper.MenuMapper;
+import cc.mrbird.febs.auth.mapper.UserMapper;
+import cc.mrbird.febs.common.entity.system.Menu;
+import cc.mrbird.febs.common.entity.system.SystemUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 用户业务逻辑
+ *
+ * @author MrBird
+ */
+@Service
+public class UserManager {
+
+    @Autowired
+    private UserMapper userMapper;
+    @Autowired
+    private MenuMapper menuMapper;
+
+    /**
+     * 通过用户名查询用户信息
+     *
+     * @param username 用户名
+     * @return 用户
+     */
+    public SystemUser findByName(String username) {
+        return userMapper.findByName(username);
+    }
+
+    /**
+     * 通过用户名查询用户权限串
+     *
+     * @param username 用户名
+     * @return 权限
+     */
+    public String findUserPermissions(String username) {
+        List<Menu> userPermissions = menuMapper.findUserPermissions(username);
+        return userPermissions.stream().map(Menu::getPerms).collect(Collectors.joining(","));
+    }
+}

+ 14 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/mapper/MenuMapper.java

@@ -0,0 +1,14 @@
+package cc.mrbird.febs.auth.mapper;
+
+import cc.mrbird.febs.common.entity.system.Menu;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+/**
+ * @author MrBird
+ */
+public interface MenuMapper extends BaseMapper<Menu> {
+
+    List<Menu> findUserPermissions(String username);
+}

+ 12 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/mapper/UserMapper.java

@@ -0,0 +1,12 @@
+package cc.mrbird.febs.auth.mapper;
+
+import cc.mrbird.febs.common.entity.system.SystemUser;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @author MrBird
+ */
+public interface UserMapper extends BaseMapper<SystemUser> {
+
+    SystemUser findByName(String username);
+}

+ 29 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/properties/FebsAuthProperties.java

@@ -0,0 +1,29 @@
+package cc.mrbird.febs.auth.properties;
+
+import lombok.Data;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * @author MrBird
+ */
+@Data
+@SpringBootConfiguration
+@PropertySource(value = {"classpath:febs-auth.properties"})
+@ConfigurationProperties(prefix = "febs.auth")
+public class FebsAuthProperties {
+
+    /**
+     * client配置
+     */
+    private FebsClientsProperties[] clients = {};
+    /**
+     * 免认证访问路径
+     */
+    private String anonUrl;
+    /**
+     * 验证码配置
+     */
+    private FebsValidateCodeProperties code = new FebsValidateCodeProperties();
+}

+ 34 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/properties/FebsClientsProperties.java

@@ -0,0 +1,34 @@
+package cc.mrbird.febs.auth.properties;
+
+import lombok.Data;
+
+/**
+ * @author MrBird
+ */
+@Data
+public class FebsClientsProperties {
+    /**
+     * client_id
+     */
+    private String client;
+    /**
+     * client_secret
+     */
+    private String secret;
+    /**
+     * 认证类型
+     */
+    private String grantType = "password,authorization_code,refresh_token";
+    /**
+     * 范围
+     */
+    private String scope = "all";
+    /**
+     * 访问令牌有效时间,单位秒
+     */
+    private int accessTokenValiditySeconds = 60 * 60 * 24;
+    /**
+     * 刷新令牌有效视角,单位秒
+     */
+    private int refreshTokenValiditySeconds = 60 * 60 * 24 * 7;
+}

+ 38 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/properties/FebsValidateCodeProperties.java

@@ -0,0 +1,38 @@
+package cc.mrbird.febs.auth.properties;
+
+import lombok.Data;
+
+/**
+ * @author MrBird
+ */
+@Data
+public class FebsValidateCodeProperties {
+
+    /**
+     * 验证码有效时间,单位秒
+     */
+    private Long time = 120L;
+    /**
+     * 验证码类型,可选值 png和 gif
+     */
+    private String type = "png";
+    /**
+     * 图片宽度,px
+     */
+    private Integer width = 130;
+    /**
+     * 图片高度,px
+     */
+    private Integer height = 48;
+    /**
+     * 验证码位数
+     */
+    private Integer length = 4;
+    /**
+     * 验证码值的类型
+     * 1. 数字加字母
+     * 2. 纯数字
+     * 3. 纯字母
+     */
+    private Integer charType = 2;
+}

+ 34 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/runner/StartedUpRunner.java

@@ -0,0 +1,34 @@
+package cc.mrbird.febs.auth.runner;
+
+import cc.mrbird.febs.common.entity.FebsServerConstant;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+@Slf4j
+@Component
+public class StartedUpRunner implements ApplicationRunner {
+
+    @Autowired
+    private ConfigurableApplicationContext context;
+
+    @Value("${spring.application.name:'" + FebsServerConstant.FEBS_AUTH + "'}")
+    private String applicationName;
+
+    @Override
+    public void run(ApplicationArguments args) {
+        if (context.isActive()) {
+            log.info(" __    ___   _      ___   _     ____ _____  ____ ");
+            log.info("/ /`  / / \\ | |\\/| | |_) | |   | |_   | |  | |_  ");
+            log.info("\\_\\_, \\_\\_/ |_|  | |_|   |_|__ |_|__  |_|  |_|__ ");
+            log.info("                                                      ");
+            log.info("{} 启动完毕,时间:{}", applicationName, LocalDateTime.now());
+        }
+    }
+}

+ 44 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/service/FebsUserDetailService.java

@@ -0,0 +1,44 @@
+package cc.mrbird.febs.auth.service;
+
+import cc.mrbird.febs.auth.manager.UserManager;
+import cc.mrbird.febs.common.entity.FebsAuthUser;
+import cc.mrbird.febs.common.entity.system.SystemUser;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author MrBird
+ */
+@Service
+public class FebsUserDetailService implements UserDetailsService {
+
+    @Autowired
+    private PasswordEncoder passwordEncoder;
+    @Autowired
+    private UserManager userManager;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        SystemUser systemUser = userManager.findByName(username);
+        if (systemUser != null) {
+            String permissions = userManager.findUserPermissions(systemUser.getUsername());
+            boolean notLocked = false;
+            if (StringUtils.equals(SystemUser.STATUS_VALID, systemUser.getStatus()))
+                notLocked = true;
+            FebsAuthUser authUser = new FebsAuthUser(systemUser.getUsername(), systemUser.getPassword(), true, true, true, notLocked,
+                    AuthorityUtils.commaSeparatedStringToAuthorityList(permissions));
+
+            BeanUtils.copyProperties(systemUser,authUser);
+            return authUser;
+        } else {
+            throw new UsernameNotFoundException("");
+        }
+    }
+}

+ 93 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/service/ValidateCodeService.java

@@ -0,0 +1,93 @@
+package cc.mrbird.febs.auth.service;
+
+import cc.mrbird.febs.auth.properties.FebsAuthProperties;
+import cc.mrbird.febs.auth.properties.FebsValidateCodeProperties;
+import cc.mrbird.febs.common.entity.FebsConstant;
+import cc.mrbird.febs.common.exception.ValidateCodeException;
+import cc.mrbird.febs.common.service.RedisService;
+import com.wf.captcha.GifCaptcha;
+import com.wf.captcha.SpecCaptcha;
+import com.wf.captcha.base.Captcha;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 验证码服务
+ *
+ * @author MrBird
+ */
+@Service
+public class ValidateCodeService {
+
+    @Autowired
+    private RedisService redisService;
+    @Autowired
+    private FebsAuthProperties properties;
+
+    /**
+     * 生成验证码
+     *
+     * @param request  HttpServletRequest
+     * @param response HttpServletResponse
+     */
+    public void create(HttpServletRequest request, HttpServletResponse response) throws IOException, ValidateCodeException {
+        String key = request.getParameter("key");
+        if (StringUtils.isBlank(key)) {
+            throw new ValidateCodeException("验证码key不能为空");
+        }
+        FebsValidateCodeProperties code = properties.getCode();
+        setHeader(response, code.getType());
+
+        Captcha captcha = createCaptcha(code);
+        redisService.set(FebsConstant.CODE_PREFIX + key, StringUtils.lowerCase(captcha.text()), code.getTime());
+        captcha.out(response.getOutputStream());
+    }
+
+    /**
+     * 校验验证码
+     *
+     * @param key   前端上送 key
+     * @param value 前端上送待校验值
+     */
+    public void check(String key, String value) throws ValidateCodeException {
+        Object codeInRedis = redisService.get(FebsConstant.CODE_PREFIX + key);
+        if (StringUtils.isBlank(value)) {
+            throw new ValidateCodeException("请输入验证码");
+        }
+        if (codeInRedis == null) {
+            throw new ValidateCodeException("验证码已过期");
+        }
+        if (!StringUtils.equalsIgnoreCase(value, String.valueOf(codeInRedis))) {
+            throw new ValidateCodeException("验证码不正确");
+        }
+    }
+
+    private Captcha createCaptcha(FebsValidateCodeProperties code) {
+        Captcha captcha = null;
+        if (StringUtils.equalsIgnoreCase(code.getType(), FebsConstant.GIF)) {
+            captcha = new GifCaptcha(code.getWidth(), code.getHeight(), code.getLength());
+        } else {
+            captcha = new SpecCaptcha(code.getWidth(), code.getHeight(), code.getLength());
+        }
+        captcha.setCharType(code.getCharType());
+        return captcha;
+    }
+
+    private void setHeader(HttpServletResponse response, String type) {
+        if (StringUtils.equalsIgnoreCase(type, FebsConstant.GIF)) {
+            response.setContentType(MediaType.IMAGE_GIF_VALUE);
+        } else {
+            response.setContentType(MediaType.IMAGE_PNG_VALUE);
+        }
+        response.setHeader(HttpHeaders.PRAGMA, "No-cache");
+        response.setHeader(HttpHeaders.CACHE_CONTROL, "No-cache");
+        response.setDateHeader(HttpHeaders.EXPIRES, 0L);
+    }
+}

+ 46 - 0
febs-auth/src/main/java/cc/mrbird/febs/auth/translator/FebsWebResponseExceptionTranslator.java

@@ -0,0 +1,46 @@
+package cc.mrbird.febs.auth.translator;
+
+import cc.mrbird.febs.common.entity.FebsResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
+import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException;
+import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
+import org.springframework.stereotype.Component;
+
+/**
+ * 异常翻译
+ *
+ * @author MrBird
+ */
+@Slf4j
+@Component
+public class FebsWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
+
+    @Override
+    public ResponseEntity translate(Exception e) {
+        ResponseEntity.BodyBuilder status = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
+        FebsResponse response = new FebsResponse();
+        String message = "认证失败";
+        log.error(message, e);
+        if (e instanceof UnsupportedGrantTypeException) {
+            message = "不支持该认证类型";
+            return status.body(response.message(message));
+        }
+        if (e instanceof InvalidGrantException) {
+            if (StringUtils.containsIgnoreCase(e.getMessage(), "Invalid refresh token")) {
+                message = "refresh token无效";
+                return status.body(response.message(message));
+            }
+            if (StringUtils.containsIgnoreCase(e.getMessage(), "locked")) {
+                message = "用户已被锁定,请联系管理员";
+                return status.body(response.message(message));
+            }
+            message = "用户名或密码错误";
+            return status.body(response.message(message));
+        }
+        return status.body(response.message(message));
+    }
+}

+ 8 - 0
febs-auth/src/main/resources/banner.txt

@@ -0,0 +1,8 @@
+|------------------------------|
+|    ____  ____  ___   __      |
+|   | |_  | |_  | |_) ( (`     |
+|   |_|   |_|__ |_|_) _)_)     |
+|                              |
+|   ${spring.application.name}                  |
+|   Spring-Boot: ${spring-boot.version} |
+|------------------------------|

+ 26 - 0
febs-auth/src/main/resources/bootstrap.yml

@@ -0,0 +1,26 @@
+server:
+  port: 8101
+spring:
+  application:
+    name: FEBS-Auth
+  cloud:
+    config:
+      fail-fast: true
+      name: ${spring.application.name}
+      profile: ${spring.profiles.active}
+      discovery:
+        enabled: true
+        service-id: FEBS-Config
+  profiles:
+    active: dev
+
+eureka:
+  instance:
+    lease-renewal-interval-in-seconds: 20
+  client:
+    register-with-eureka: true
+    fetch-registry: true
+    instance-info-replication-interval-seconds: 30
+    registry-fetch-interval-seconds: 3
+    serviceUrl:
+      defaultZone: http://febs:123456@${febs-register}:8001/register/eureka/

+ 21 - 0
febs-auth/src/main/resources/febs-auth.properties

@@ -0,0 +1,21 @@
+febs.auth.clients[0].client=febs
+febs.auth.clients[0].secret=123456
+febs.auth.clients[0].grantType=password,refresh_token
+febs.auth.clients[0].scope=all
+febs.auth.clients[0].accessTokenValiditySeconds=86400
+febs.auth.clients[0]refreshTokenValiditySeconds=604800
+
+febs.auth.clients[1].client=swagger
+febs.auth.clients[1].secret=123456
+febs.auth.clients[1].grantType=password
+febs.auth.clients[1].scope=test
+febs.auth.clients[1].accessTokenValiditySeconds=3600
+
+febs.auth.anonUrl=/actuator/**,/captcha
+
+febs.auth.code.time=120
+febs.auth.code.type=png
+febs.auth.code.width=115
+febs.auth.code.height=42
+febs.auth.code.length=4
+febs.auth.code.charType=2

+ 61 - 0
febs-auth/src/main/resources/logback-spring.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <contextName>febs</contextName>
+    <property name="log.path" value="log/febs-auth" />
+    <property name="log.maxHistory" value="15" />
+    <property name="log.colorPattern" value="%magenta(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %yellow(%thread) %green(%logger) %msg%n"/>
+    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level %thread %logger %msg%n"/>
+
+    <!--输出到控制台-->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${log.colorPattern}</pattern>
+        </encoder>
+    </appender>
+
+    <!--输出到文件-->
+    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${log.path}/info/info.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <MaxHistory>${log.maxHistory}</MaxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${log.path}/error/error.%d{yyyy-MM-dd}.log</fileNamePattern>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!--输出到 logstash的 appender-->
+<!--    <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">-->
+<!--        <destination>192.168.33.10:4560</destination>-->
+<!--        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"/>-->
+<!--    </appender>-->
+
+    <root level="debug">
+        <appender-ref ref="console" />
+    </root>
+
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+<!--        <appender-ref ref="logstash" />-->
+    </root>
+</configuration>

+ 16 - 0
febs-auth/src/main/resources/mapper/MenuMapper.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cc.mrbird.febs.auth.mapper.MenuMapper">
+
+    <select id="findUserPermissions" resultType="menu">
+        select distinct m.perms
+        from t_role r
+                 left join t_user_role ur on (r.role_id = ur.role_id)
+                 left join t_user u on (u.user_id = ur.user_id)
+                 left join t_role_menu rm on (rm.role_id = r.role_id)
+                 left join t_menu m on (m.menu_id = rm.menu_id)
+        where u.username = #{userName}
+          and m.perms is not null
+          and m.perms &lt;&gt; ''
+    </select>
+</mapper>

+ 31 - 0
febs-auth/src/main/resources/mapper/UserMapper.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cc.mrbird.febs.auth.mapper.UserMapper">
+    <select id="findByName" parameterType="string" resultType="systemUser">
+        SELECT
+        u.user_id userId,
+        u.username,
+        u.email,
+        u.mobile,
+        u.password,
+        u.status,
+        u.create_time createTime,
+        u.ssex sex,
+        u.dept_id deptId,
+        u.last_login_time lastLoginTime,
+        u.modify_time modifyTime,
+        u.description,
+        u.avatar,
+        d.dept_name deptName,
+        GROUP_CONCAT(r.role_id) roleId,
+        GROUP_CONCAT(r.ROLE_NAME) roleName
+        FROM
+        t_user u
+        LEFT JOIN t_dept d ON (u.dept_id = d.dept_id)
+        LEFT JOIN t_user_role ur ON (u.user_id = ur.user_id)
+        LEFT JOIN t_role r ON r.role_id = ur.role_id
+        WHERE  u.username = #{username}
+        group by u.username,u.user_id,u.email,u.mobile,u.password, u.status,u.create_time,u.ssex,u.dept_id
+				,u.last_login_time,u.modify_time,u.description,u.avatar
+    </select>
+</mapper>

+ 36 - 0
febs-cloud/docker compose/elk/docker-compose.yml

@@ -0,0 +1,36 @@
+version: '3'
+services:
+  elasticsearch:
+    image: elasticsearch:6.4.1
+    container_name: elasticsearch
+    environment:
+      - "cluster.name=elasticsearch" #集群名称为 elasticsearch
+      - "discovery.type=single-node" #单节点启动
+      - "ES_JAVA_OPTS=-Xms512m -Xmx512m" #jvm内存分配为 512MB
+    volumes:
+      - /febs/elasticsearch/plugins:/usr/share/elasticsearch/plugins
+      - /febs/elasticsearch/data:/usr/share/elasticsearch/data
+    ports:
+      - 9200:9200
+  kibana:
+    image: kibana:6.4.1
+    container_name: kibana
+    links:
+      - elasticsearch:es #配置elasticsearch域名为 es
+    depends_on:
+      - elasticsearch
+    environment:
+      - "elasticsearch.hosts=http://es:9200" #因为上面配置了域名,所以这里可以简写为 http://es:9200
+    ports:
+      - 5601:5601
+  logstash:
+    image: logstash:6.4.1
+    container_name: logstash
+    volumes:
+      - /febs/logstash/logstash-febs.conf:/usr/share/logstash/pipeline/logstash.conf
+    depends_on:
+      - elasticsearch
+    links:
+      - elasticsearch:es
+    ports:
+      - 4560:4560

+ 119 - 0
febs-cloud/docker compose/febs-cloud/docker-compose.yml

@@ -0,0 +1,119 @@
+version: '3'
+
+services:
+  febs-register:
+    image: febs-register:latest
+    container_name: febs-register
+    volumes:
+      - "/febs/log:/log"
+    command:
+      - "--febs-monitor-admin=127.0.0.1"
+      - "--febs-register=127.0.0.1"
+    ports:
+      - 8001:8001
+    restart: always
+  febs-config:
+    image: febs-config:latest
+    container_name: febs-config
+    depends_on:
+      - febs-register
+    volumes:
+      - "/febs/log:/log"
+    command:
+      - "--febs-monitor-admin=127.0.0.1"
+      - "--febs-register=127.0.0.1"
+    ports:
+      - 8501:8501
+    restart: always
+  febs-monitor-admin:
+    image: febs-monitor-admin:latest
+    container_name: febs-monitor-admin
+    volumes:
+      - "/febs/log:/log"
+    ports:
+      - 8401:8401
+    restart: always
+  febs-gateway:
+    image: febs-gateway:latest
+    container_name: febs-gateway
+    depends_on:
+      - febs-config
+      - febs-register
+    volumes:
+      - "/febs/log:/log"
+    command:
+      - "--febs-monitor-admin=127.0.0.1"
+      - "--febs-register=127.0.0.1"
+    ports:
+      - 8301:8301
+    restart: always
+  febs-auth:
+    image: febs-auth:latest
+    container_name: febs-auth
+    depends_on:
+      - febs-config
+      - febs-register
+    volumes:
+      - "/febs/log:/log"
+    command:
+      - "--febs-monitor-admin=127.0.0.1"
+      - "--febs-register=127.0.0.1"
+      - "--mysql.url=127.0.0.1"
+      - "--redis.url=127.0.0.1"
+      - "--spring.profiles.active=prod"
+    restart: always
+  febs-server-system:
+    image: febs-server-system:latest
+    container_name: febs-server-system
+    depends_on:
+      - febs-register
+      - febs-config
+    volumes:
+      - "/febs/log:/log"
+    command:
+      - "--febs-monitor-admin=127.0.0.1"
+      - "--febs-register=127.0.0.1"
+      - "--febs-gateway=127.0.0.1"
+      - "--mysql.url=127.0.0.1"
+      - "--rabbitmq.url=127.0.0.1"
+      - "--spring.profiles.active=prod"
+    restart: always
+  febs-server-test:
+    image: febs-server-test:latest
+    container_name: febs-server-test
+    depends_on:
+      - febs-config
+      - febs-register
+    volumes:
+      - "/febs/log:/log"
+    command:
+      - "--rabbitmq.url=127.0.0.1"
+      - "--febs-monitor-admin=127.0.0.1"
+      - "--febs-register=127.0.0.1"
+      - "--febs-gateway=127.0.0.1"
+    restart: always
+  zipkin-server:
+    image: zipkin-server
+    container_name: zipkin-server
+    command:
+      - "--server.port=8402"
+      - "--zipkin.storage.type=mysql"
+      - "--zipkin.storage.mysql.db=febs_cloud_base"
+      - "--zipkin.storage.mysql.username=root"
+      - "--zipkin.storage.mysql.password=123456"
+      - "--zipkin.storage.mysql.host=127.0.0.1"
+      - "--zipkin.storage.mysql.port=3306"
+      - "--zipkin.collector.rabbitmq.addresses=127.0.0.1:5672"
+      - "--zipkin.collector.rabbitmq.username=febs"
+      - "--zipkin.collector.rabbitmq.password=123456"
+    ports:
+      - 8402:8402
+    restart: always
+  febs-clou-web:
+    image: febs-cloud-web
+    container_name: febs-cloud-web
+    volumes:
+      - "/febs/log:/log"
+    ports:
+      - 9527:80
+    restart: always

+ 30 - 0
febs-cloud/docker compose/third-part/docker-compose.yml

@@ -0,0 +1,30 @@
+version: '3'
+
+services:
+  mysql:
+    image: mysql:5.7.24
+    container_name: mysql
+    environment:
+      MYSQL_ROOT_PASSWORD: 123456
+    ports:
+      - 3306:3306
+    volumes:
+      - /febs/mysql/data:/var/lib/mysql #挂载 MySQL数据
+  redis:
+    image: redis:4.0.14
+    container_name: redis
+    command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes
+    volumes:
+      - /febs/redis/data:/data #挂载 Redis数据
+      - /febs/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf #挂载 Redis配置
+    ports:
+      - 6379:6379
+  rabbitmq:
+    image: rabbitmq:3.7.15-management
+    container_name: rabbitmq
+    volumes:
+      - /febs/rabbitmq/data:/var/lib/rabbitmq #挂载 RabbitMQ数据
+      - /febs/rabbitmq/log:/var/log/rabbitmq #挂载 RabbitMQ日志
+    ports:
+      - 5672:5672
+      - 15672:15672

+ 49 - 0
febs-cloud/pom.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cc.mrbird</groupId>
+    <artifactId>febs-cloud</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <name>FEBS-Cloud</name>
+    <description>FEBS-Cloud:Spring Cloud,Spring Security OAuth2 微服务权限管理系统</description>
+
+    <modules>
+        <module>../febs-register</module>
+        <module>../febs-auth</module>
+        <module>../febs-common</module>
+        <module>../febs-server</module>
+        <module>../febs-gateway</module>
+        <module>../febs-monitor</module>
+        <module>../febs-config</module>
+    </modules>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.1.6.RELEASE</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring-cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+</project>

+ 85 - 0
febs-common/pom.xml

@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cc.mrbird</groupId>
+        <artifactId>febs-cloud</artifactId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../febs-cloud/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>febs-common</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>FEBS-Common</name>
+    <description>FEBS-Common通用模块</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.51</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>27.0-jre</version>
+        </dependency>
+        <dependency>
+            <groupId>com.wuwenze</groupId>
+            <artifactId>ExcelKit</artifactId>
+            <version>2.0.71</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-oauth2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-config-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>de.codecentric</groupId>
+            <artifactId>spring-boot-admin-starter-client</artifactId>
+            <version>2.1.6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 16 - 0
febs-common/src/main/java/cc/mrbird/febs/common/annotation/EnableFebsAuthExceptionHandler.java

@@ -0,0 +1,16 @@
+package cc.mrbird.febs.common.annotation;
+
+import cc.mrbird.febs.common.configure.FebsAuthExceptionConfigure;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * @author MrBird
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(FebsAuthExceptionConfigure.class)
+public @interface EnableFebsAuthExceptionHandler {
+}

+ 16 - 0
febs-common/src/main/java/cc/mrbird/febs/common/annotation/EnableFebsLettuceRedis.java

@@ -0,0 +1,16 @@
+package cc.mrbird.febs.common.annotation;
+
+import cc.mrbird.febs.common.configure.FebsLettuceRedisConfigure;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * @author MrBird
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(FebsLettuceRedisConfigure.class)
+public @interface EnableFebsLettuceRedis {
+}

+ 16 - 0
febs-common/src/main/java/cc/mrbird/febs/common/annotation/EnableFebsOauth2FeignClient.java

@@ -0,0 +1,16 @@
+package cc.mrbird.febs.common.annotation;
+
+import cc.mrbird.febs.common.configure.FebsOAuth2FeignConfigure;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * @author MrBird
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(FebsOAuth2FeignConfigure.class)
+public @interface EnableFebsOauth2FeignClient {
+}

+ 16 - 0
febs-common/src/main/java/cc/mrbird/febs/common/annotation/EnableFebsServerProtect.java

@@ -0,0 +1,16 @@
+package cc.mrbird.febs.common.annotation;
+
+import cc.mrbird.febs.common.configure.FebsServerProtectConfigure;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * @author MrBird
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(FebsServerProtectConfigure.class)
+public @interface EnableFebsServerProtect {
+}

+ 20 - 0
febs-common/src/main/java/cc/mrbird/febs/common/annotation/Fallback.java

@@ -0,0 +1,20 @@
+package cc.mrbird.febs.common.annotation;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * @author MrBird
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Component
+public @interface Fallback {
+
+    @AliasFor(annotation = Component.class)
+    String value() default "";
+
+}

+ 16 - 0
febs-common/src/main/java/cc/mrbird/febs/common/annotation/FebsCloudApplication.java

@@ -0,0 +1,16 @@
+package cc.mrbird.febs.common.annotation;
+
+import cc.mrbird.febs.common.selector.FebsCloudApplicationSelector;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * @author MrBird
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Import(FebsCloudApplicationSelector.class)
+public @interface FebsCloudApplication {
+}

+ 18 - 0
febs-common/src/main/java/cc/mrbird/febs/common/annotation/Helper.java

@@ -0,0 +1,18 @@
+package cc.mrbird.febs.common.annotation;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * @author MrBird
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Component
+public @interface Helper {
+    @AliasFor(annotation = Component.class)
+    String value() default "";
+}

+ 25 - 0
febs-common/src/main/java/cc/mrbird/febs/common/annotation/IsMobile.java

@@ -0,0 +1,25 @@
+package cc.mrbird.febs.common.annotation;
+
+import cc.mrbird.febs.common.validator.MobileValidator;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author MrBird
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = MobileValidator.class)
+public @interface IsMobile {
+
+    String message();
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

+ 12 - 0
febs-common/src/main/java/cc/mrbird/febs/common/annotation/Log.java

@@ -0,0 +1,12 @@
+package cc.mrbird.febs.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Log {
+    String value() default "";
+}

+ 26 - 0
febs-common/src/main/java/cc/mrbird/febs/common/configure/FebsAuthExceptionConfigure.java

@@ -0,0 +1,26 @@
+package cc.mrbird.febs.common.configure;
+
+import cc.mrbird.febs.common.handler.FebsAccessDeniedHandler;
+import cc.mrbird.febs.common.handler.FebsAuthExceptionEntryPoint;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 异常翻译配置
+ *
+ * @author MrBird
+ */
+public class FebsAuthExceptionConfigure {
+
+    @Bean
+    @ConditionalOnMissingBean(name = "accessDeniedHandler")
+    public FebsAccessDeniedHandler accessDeniedHandler() {
+        return new FebsAccessDeniedHandler();
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(name = "authenticationEntryPoint")
+    public FebsAuthExceptionEntryPoint authenticationEntryPoint() {
+        return new FebsAuthExceptionEntryPoint();
+    }
+}

+ 55 - 0
febs-common/src/main/java/cc/mrbird/febs/common/configure/FebsLettuceRedisConfigure.java

@@ -0,0 +1,55 @@
+package cc.mrbird.febs.common.configure;
+
+import cc.mrbird.febs.common.service.RedisService;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * Lettuce Redis配置
+ *
+ * @author MrBird
+ */
+public class FebsLettuceRedisConfigure {
+
+    @Bean
+    @ConditionalOnClass(RedisOperations.class)
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(factory);
+
+        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+        jackson2JsonRedisSerializer.setObjectMapper(mapper);
+
+        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
+        // key采用 String的序列化方式
+        template.setKeySerializer(stringRedisSerializer);
+        // hash的 key也采用 String的序列化方式
+        template.setHashKeySerializer(stringRedisSerializer);
+        // value序列化方式采用 jackson
+        template.setValueSerializer(jackson2JsonRedisSerializer);
+        // hash的 value序列化方式采用 jackson
+        template.setHashValueSerializer(jackson2JsonRedisSerializer);
+        template.afterPropertiesSet();
+
+        return template;
+    }
+
+    @Bean
+    @ConditionalOnBean(name = "redisTemplate")
+    public RedisService redisService() {
+        return new RedisService();
+    }
+
+}

+ 32 - 0
febs-common/src/main/java/cc/mrbird/febs/common/configure/FebsOAuth2FeignConfigure.java

@@ -0,0 +1,32 @@
+package cc.mrbird.febs.common.configure;
+
+import cc.mrbird.febs.common.entity.FebsConstant;
+import com.google.common.net.HttpHeaders;
+import feign.RequestInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
+import org.springframework.util.Base64Utils;
+
+/**
+ * OAuth2 Feign配置
+ *
+ * @author MrBird
+ */
+public class FebsOAuth2FeignConfigure {
+
+    @Bean
+    public RequestInterceptor oauth2FeignRequestInterceptor() {
+        return requestTemplate -> {
+            // 请求头中添加 Zuul Token
+            String zuulToken = new String(Base64Utils.encode(FebsConstant.ZUUL_TOKEN_VALUE.getBytes()));
+            requestTemplate.header(FebsConstant.ZUUL_TOKEN_HEADER, zuulToken);
+            // 请求头中添加原请求头中的 Token
+            Object details = SecurityContextHolder.getContext().getAuthentication().getDetails();
+            if (details instanceof OAuth2AuthenticationDetails) {
+                String authorizationToken = ((OAuth2AuthenticationDetails) details).getTokenValue();
+                requestTemplate.header(HttpHeaders.AUTHORIZATION, "bearer " + authorizationToken);
+            }
+        };
+    }
+}

+ 34 - 0
febs-common/src/main/java/cc/mrbird/febs/common/configure/FebsServerProtectConfigure.java

@@ -0,0 +1,34 @@
+package cc.mrbird.febs.common.configure;
+
+import cc.mrbird.febs.common.interceptor.FebsServerProtectInterceptor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 微服务防护配置
+ *
+ * @author MrBird
+ */
+public class FebsServerProtectConfigure implements WebMvcConfigurer {
+
+    @Bean
+    @ConditionalOnMissingBean(value = PasswordEncoder.class)
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Bean
+    public HandlerInterceptor febsServerProtectInterceptor() {
+        return new FebsServerProtectInterceptor();
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(febsServerProtectInterceptor());
+    }
+}

+ 31 - 0
febs-common/src/main/java/cc/mrbird/febs/common/converter/TimeConverter.java

@@ -0,0 +1,31 @@
+package cc.mrbird.febs.common.converter;
+
+import cc.mrbird.febs.common.utils.DateUtil;
+import com.wuwenze.poi.convert.WriteConverter;
+import com.wuwenze.poi.exception.ExcelKitWriteConverterException;
+import lombok.extern.slf4j.Slf4j;
+
+import java.text.ParseException;
+
+/**
+ * Execl导出时间类型字段格式化
+ *
+ * @author MrBird
+ */
+@Slf4j
+public class TimeConverter implements WriteConverter {
+    @Override
+    public String convert(Object value) {
+        if (value == null)
+            return "";
+        else {
+            try {
+                return DateUtil.formatCSTTime(value.toString(), DateUtil.FULL_TIME_SPLIT_PATTERN);
+            } catch (ParseException e) {
+                String message = "时间转换异常";
+                log.error(message, e);
+                throw new ExcelKitWriteConverterException(message);
+            }
+        }
+    }
+}

+ 15 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/DeptTree.java

@@ -0,0 +1,15 @@
+package cc.mrbird.febs.common.entity;
+
+import cc.mrbird.febs.common.entity.system.Dept;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @author MrBird
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DeptTree extends Tree<Dept>{
+
+    private Integer orderNum;
+}

+ 51 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/FebsAuthUser.java

@@ -0,0 +1,51 @@
+package cc.mrbird.febs.common.entity;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * @author MrBird
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FebsAuthUser extends User {
+
+    private static final long serialVersionUID = -6411066541689297219L;
+
+    private Long userId;
+
+    private String avatar;
+
+    private String email;
+
+    private String mobile;
+
+    private String sex;
+
+    private Long deptId;
+
+    private String deptName;
+
+    private String roleId;
+
+    private String roleName;
+
+    private Date lastLoginTime;
+
+    private String description;
+
+    private String status;
+
+    public FebsAuthUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
+        super(username, password, authorities);
+    }
+
+    public FebsAuthUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
+        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
+    }
+}

+ 45 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/FebsConstant.java

@@ -0,0 +1,45 @@
+package cc.mrbird.febs.common.entity;
+
+/**
+ * @author MrBird
+ */
+public class FebsConstant {
+
+    /**
+     * 排序规则:降序
+     */
+    public static final String ORDER_DESC = "descending";
+    /**
+     * 排序规则:升序
+     */
+    public static final String ORDER_ASC = "ascending";
+
+    /**
+     * Zuul请求头TOKEN名称(不要有空格)
+     */
+    public static final String ZUUL_TOKEN_HEADER = "ZuulToken";
+    /**
+     * Zuul请求头TOKEN值
+     */
+    public static final String ZUUL_TOKEN_VALUE = "febs:zuul:123456";
+
+    /**
+     * 允许下载的文件类型,根据需求自己添加(小写)
+     */
+    public static final String[] VALID_FILE_TYPE = {"xlsx", "zip"};
+
+    /**
+     * gif类型
+     */
+    public static final String GIF = "gif";
+    /**
+     * png类型
+     */
+    public static final String PNG = "png";
+
+    /**
+     * 验证码 key前缀
+     */
+    public static final String CODE_PREFIX = "febs.captcha.";
+
+}

+ 35 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/FebsResponse.java

@@ -0,0 +1,35 @@
+package cc.mrbird.febs.common.entity;
+
+import java.util.HashMap;
+
+/**
+ * @author MrBird
+ */
+public class FebsResponse extends HashMap<String, Object> {
+
+    private static final long serialVersionUID = -8713837118340960775L;
+
+    public FebsResponse message(String message) {
+        this.put("message", message);
+        return this;
+    }
+
+    public FebsResponse data(Object data) {
+        this.put("data", data);
+        return this;
+    }
+
+    @Override
+    public FebsResponse put(String key, Object value) {
+        super.put(key, value);
+        return this;
+    }
+
+    public String getMessage() {
+        return String.valueOf(get("message"));
+    }
+
+    public Object getData() {
+        return get("data");
+    }
+}

+ 18 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/FebsServerConstant.java

@@ -0,0 +1,18 @@
+package cc.mrbird.febs.common.entity;
+
+/**
+ * @author MrBird
+ */
+public class FebsServerConstant {
+
+    public static final String FEBS_AUTH = "FEBS-Auth";
+    public static final String FEBS_CLOUD = "FEBS-Cloud";
+    public static final String FEBS_COMMON = "FEBS-Common";
+    public static final String FEBS_GATEWAY = "FEBS-Gateway";
+    public static final String FEBS_MONITOR = "FEBS-Monitor";
+    public static final String FEBS_MONITOR_ADMIN = "FEBS-Monitor_Admin";
+    public static final String FEBS_REGISTER = "FEBS-Register";
+    public static final String FEBS_SERVER = "FEBS-Server";
+    public static final String FEBS_SERVER_SYSTEM= "FEBS-Server-System";
+    public static final String FEBS_SERVER_TEST= "FEBS-Server-Test";
+}

+ 20 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/MenuTree.java

@@ -0,0 +1,20 @@
+package cc.mrbird.febs.common.entity;
+
+import cc.mrbird.febs.common.entity.system.Menu;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * @author MrBird
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MenuTree extends Tree<Menu>{
+
+    private String path;
+    private String component;
+    private String perms;
+    private String icon;
+    private String type;
+    private Integer orderNum;
+}

+ 32 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/QueryRequest.java

@@ -0,0 +1,32 @@
+package cc.mrbird.febs.common.entity;
+
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+
+/**
+ * @author MrBird
+ */
+@Data
+@ToString
+public class QueryRequest implements Serializable {
+
+    private static final long serialVersionUID = -4869594085374385813L;
+    /**
+     * 当前页面数据量
+     */
+    private int pageSize = 10;
+    /**
+     * 当前页码
+     */
+    private int pageNum = 1;
+    /**
+     * 排序字段
+     */
+    private String field;
+    /**
+     * 排序规则,asc升序,desc降序
+     */
+    private String order;
+}

+ 13 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/RegexpConstant.java

@@ -0,0 +1,13 @@
+package cc.mrbird.febs.common.entity;
+
+/**
+ * 正则常量
+ *
+ * @author MrBird
+ */
+public class RegexpConstant {
+
+    // 简单手机号正则(这里只是简单校验是否为 11位,实际规则更复杂)
+    public static final String MOBILE_REG = "[1]\\d{10}";
+
+}

+ 32 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/Tree.java

@@ -0,0 +1,32 @@
+package cc.mrbird.febs.common.entity;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author MrBird
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Tree<T> {
+
+    private String id;
+
+    private String label;
+
+    private List<Tree<T>> children;
+
+    private String parentId;
+
+    private boolean hasParent = false;
+
+    private boolean hasChildren = false;
+
+    public void initChildren(){
+        this.children = new ArrayList<>();
+    }
+
+}

+ 23 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/router/RouterMeta.java

@@ -0,0 +1,23 @@
+package cc.mrbird.febs.common.entity.router;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * Vue路由 Meta
+ */
+@Data
+@AllArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class RouterMeta implements Serializable {
+
+    private static final long serialVersionUID = 5499925008927195914L;
+
+    private String title;
+    private String icon;
+    private Boolean breadcrumb = true;
+
+}

+ 44 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/router/VueRouter.java

@@ -0,0 +1,44 @@
+package cc.mrbird.febs.common.entity.router;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 构建 Vue路由
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class VueRouter<T> implements Serializable {
+
+    private static final long serialVersionUID = -3327478146308500708L;
+
+    @JsonIgnore
+    private String id;
+    @JsonIgnore
+    private String parentId;
+
+    private String path;
+    private String name;
+    private String component;
+    private String redirect;
+    private RouterMeta meta;
+    private Boolean hidden = false;
+    private Boolean alwaysShow = false;
+    private List<VueRouter<T>> children;
+
+    @JsonIgnore
+    private Boolean hasParent = false;
+
+    @JsonIgnore
+    private Boolean hasChildren = false;
+
+    public void initChildren(){
+        this.children = new ArrayList<>();
+    }
+
+}

+ 30 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Column.java

@@ -0,0 +1,30 @@
+package cc.mrbird.febs.common.entity.system;
+
+import lombok.Data;
+
+/**
+ * @author MrBird
+ */
+@Data
+public class Column {
+    /**
+     * 名称
+     */
+    private String name;
+    /**
+     * 是否为主键
+     */
+    private Boolean isKey;
+    /**
+     * 类型
+     */
+    private String type;
+    /**
+     * 注释
+     */
+    private String remark;
+    /**
+     * 属性名称
+     */
+    private String field;
+}

+ 50 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Dept.java

@@ -0,0 +1,50 @@
+package cc.mrbird.febs.common.entity.system;
+
+import cc.mrbird.febs.common.converter.TimeConverter;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.wuwenze.poi.annotation.Excel;
+import com.wuwenze.poi.annotation.ExcelField;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("t_dept")
+@Excel("部门信息表")
+public class Dept implements Serializable {
+
+    private static final long serialVersionUID = -7790334862410409053L;
+
+    @TableId(value = "DEPT_ID", type = IdType.AUTO)
+    private Long deptId;
+
+    @TableField(value = "PARENT_ID")
+    private Long parentId;
+
+    @NotBlank(message = "{required}")
+    @Size(max = 20, message = "{noMoreThan}")
+    @ExcelField(value = "部门名称")
+    private String deptName;
+
+    @TableField(value = "ORDER_NUM")
+    private Integer orderNum;
+
+    @TableField(value = "CREATE_TIME")
+    @ExcelField(value = "创建时间", writeConverter = TimeConverter.class)
+    private Date createTime;
+
+    @TableField(value = "MODIFY_TIME")
+    @ExcelField(value = "修改时间", writeConverter = TimeConverter.class)
+    private Date modifyTime;
+
+    private transient String createTimeFrom;
+
+    private transient String createTimeTo;
+
+}

+ 47 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Eximport.java

@@ -0,0 +1,47 @@
+package cc.mrbird.febs.common.entity.system;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.wuwenze.poi.annotation.Excel;
+import com.wuwenze.poi.annotation.ExcelField;
+import com.wuwenze.poi.validator.EmailValidator;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 导入导出测试,Eximport = export + import
+ *
+ * @author MrBird
+ */
+@Data
+@TableName("t_eximport")
+@Excel("测试导入导出数据")
+public class Eximport {
+
+    /**
+     * 字段1
+     */
+    @ExcelField(value = "字段1", required = true, maxLength = 20,
+            comment = "提示:必填,长度不能超过20个字符")
+    private String field1;
+
+    /**
+     * 字段2
+     */
+    @ExcelField(value = "字段2", required = true, maxLength = 11, regularExp = "[0-9]+",
+            regularExpMessage = "必须是数字", comment = "提示: 必填,只能填写数字,并且长度不能超过11位")
+    private Integer field2;
+
+    /**
+     * 字段3
+     */
+    @ExcelField(value = "字段3", required = true, maxLength = 50,
+            comment = "提示:必填,只能填写邮箱,长度不能超过50个字符", validator = EmailValidator.class)
+    private String field3;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+}

+ 123 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/GeneratorConfig.java

@@ -0,0 +1,123 @@
+package cc.mrbird.febs.common.entity.system;
+
+
+import cc.mrbird.febs.common.utils.DateUtil;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import javax.validation.constraints.Size;
+import java.time.LocalDateTime;
+
+/**
+ * @author MrBird
+ */
+@Data
+@TableName("t_generator_config")
+public class GeneratorConfig {
+
+    public static final String TRIM_YES = "1";
+    public static final String TRIM_NO = "0";
+
+    /**
+     * 主键
+     */
+    @TableId(value = "ID", type = IdType.AUTO)
+    private String id;
+
+    /**
+     * 作者
+     */
+    @TableField("author")
+    @Size(max = 20, message = "{noMoreThan}")
+    private String author;
+
+    /**
+     * 基础包名
+     */
+    @TableField("base_package")
+    @Size(max = 50, message = "{noMoreThan}")
+    private String basePackage;
+
+    /**
+     * entity文件存放路径
+     */
+    @TableField("entity_package")
+    @Size(max = 20, message = "{noMoreThan}")
+    private String entityPackage;
+
+    /**
+     * mapper文件存放路径
+     */
+    @TableField("mapper_package")
+    @Size(max = 20, message = "{noMoreThan}")
+    private String mapperPackage;
+
+    /**
+     * mapper xml文件存放路径
+     */
+    @TableField("mapper_xml_package")
+    @Size(max = 20, message = "{noMoreThan}")
+    private String mapperXmlPackage;
+
+    /**
+     * servcie文件存放路径
+     */
+    @TableField("service_package")
+    private String servicePackage;
+
+    /**
+     * serviceImpl文件存放路径
+     */
+    @TableField("service_impl_package")
+    @Size(max = 20, message = "{noMoreThan}")
+    private String serviceImplPackage;
+
+    /**
+     * controller文件存放路径
+     */
+    @TableField("controller_package")
+    @Size(max = 20, message = "{noMoreThan}")
+    private String controllerPackage;
+
+    /**
+     * 是否去除前缀
+     */
+    @TableField("is_trim")
+    private String isTrim;
+
+    /**
+     * 前缀内容
+     */
+    @TableField("trim_value")
+    private String trimValue;
+
+    /**
+     * java文件路径,固定值
+     */
+    private transient String javaPath = "/src/main/java/";
+    /**
+     * 配置文件存放路径,固定值
+     */
+    private transient String resourcesPath = "src/main/resources";
+    /**
+     * 文件生成日期
+     */
+    private transient String date = DateUtil.formatFullTime(LocalDateTime.now(), DateUtil.FULL_TIME_SPLIT_PATTERN);
+
+    /**
+     * 表名
+     */
+    private transient String tableName;
+    /**
+     * 表注释
+     */
+    private transient String tableComment;
+    /**
+     * 数据表对应的类名
+     */
+    private transient String className;
+
+}

+ 72 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/GeneratorConstant.java

@@ -0,0 +1,72 @@
+package cc.mrbird.febs.common.entity.system;
+
+/**
+ * 代码生成常量
+ *
+ * @author MrBird
+ */
+public class GeneratorConstant {
+
+    /**
+     * 数据库类型
+     */
+    public static final String DATABASE_TYPE = "mysql";
+    /**
+     * 数据库名称
+     */
+    public static final String DATABASE_NAME = "febs_cloud_base";
+
+    /**
+     * 生成代码的临时目录
+     */
+    public static final String TEMP_PATH = "febs_gen_temp/";
+
+    /**
+     * java类型文件后缀
+     */
+    public static final String JAVA_FILE_SUFFIX = ".java";
+    /**
+     * mapper文件类型后缀
+     */
+    public static final String MAPPER_FILE_SUFFIX = "Mapper.java";
+    /**
+     * service文件类型后缀
+     */
+    public static final String SERVICE_FILE_SUFFIX = "Service.java";
+    /**
+     * service impl文件类型后缀
+     */
+    public static final String SERVICEIMPL_FILE_SUFFIX = "ServiceImpl.java";
+    /**
+     * controller文件类型后缀
+     */
+    public static final String CONTROLLER_FILE_SUFFIX = "Controller.java";
+    /**
+     * mapper xml文件类型后缀
+     */
+    public static final String MAPPERXML_FILE_SUFFIX = "Mapper.xml";
+    /**
+     * entity模板
+     */
+    public static final String ENTITY_TEMPLATE = "entity.ftl";
+    /**
+     * mapper模板
+     */
+    public static final String MAPPER_TEMPLATE = "mapper.ftl";
+    /**
+     * service接口模板
+     */
+    public static final String SERVICE_TEMPLATE = "service.ftl";
+    /**
+     * service impl接口模板
+     */
+    public static final String SERVICEIMPL_TEMPLATE = "serviceImpl.ftl";
+    /**
+     * controller接口模板
+     */
+    public static final String CONTROLLER_TEMPLATE = "controller.ftl";
+    /**
+     * mapper xml接口模板
+     */
+    public static final String MAPPERXML_TEMPLATE = "mapperXml.ftl";
+}

+ 89 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Log.java

@@ -0,0 +1,89 @@
+package cc.mrbird.febs.common.entity.system;
+
+import cc.mrbird.febs.common.converter.TimeConverter;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.wuwenze.poi.annotation.Excel;
+import com.wuwenze.poi.annotation.ExcelField;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author MrBird
+ */
+@Data
+@TableName("t_log")
+@Excel("系统日志表")
+public class Log implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 日志ID
+     */
+    @TableId(value = "ID", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 操作用户
+     */
+    @TableField("USERNAME")
+    @ExcelField(value = "操作用户")
+    private String username;
+
+    /**
+     * 操作内容
+     */
+    @TableField("OPERATION")
+    @ExcelField(value = "操作内容")
+    private String operation;
+
+    /**
+     * 耗时
+     */
+    @TableField("TIME")
+    @ExcelField(value = "耗时(毫秒)")
+    private Long time;
+
+    /**
+     * 操作方法
+     */
+    @TableField("METHOD")
+    @ExcelField(value = "操作方法")
+    private String method;
+
+    /**
+     * 方法参数
+     */
+    @TableField("PARAMS")
+    @ExcelField(value = "方法参数")
+    private String params;
+
+    /**
+     * 操作者IP
+     */
+    @TableField("IP")
+    @ExcelField(value = "操作者IP")
+    private String ip;
+
+    /**
+     * 创建时间
+     */
+    @TableField("CREATE_TIME")
+    @ExcelField(value = "操作时间", writeConverter = TimeConverter.class)
+    private Date createTime;
+
+    /**
+     * 操作地点
+     */
+    @TableField("LOCATION")
+    @ExcelField(value = "操作地点")
+    private String location;
+
+    private transient String createTimeFrom;
+    private transient String createTimeTo;
+}

+ 163 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/LoginLog.java

@@ -0,0 +1,163 @@
+package cc.mrbird.febs.common.entity.system;
+
+import cc.mrbird.febs.common.converter.TimeConverter;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.wuwenze.poi.annotation.Excel;
+import com.wuwenze.poi.annotation.ExcelField;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author MrBird
+ */
+@Slf4j
+@Data
+@TableName("t_login_log")
+@Excel("登录日志")
+public class LoginLog implements Serializable {
+
+    private static final long serialVersionUID = 921991157363932095L;
+    /**
+     * id
+     */
+    @TableId(value = "ID", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 登录用户
+     */
+    @TableField("USERNAME")
+    @ExcelField("登录用户")
+    private String username;
+
+    /**
+     * 登录时间
+     */
+    @TableField("LOGIN_TIME")
+    @ExcelField(value = "登录时间", writeConverter = TimeConverter.class)
+    private Date loginTime;
+
+    /**
+     * 登录地点
+     */
+    @TableField("LOCATION")
+    @ExcelField(value = "登录地点")
+    private String location;
+    /**
+     * 登录 IP
+     */
+    @TableField("IP")
+    @ExcelField("登录IP")
+    private String ip;
+    /**
+     * 操作系统
+     */
+    @TableField("`SYSTEM`")
+    @ExcelField("操作系统")
+    private String system;
+    /**
+     * 登录浏览器
+     */
+    @TableField("BROWSER")
+    @ExcelField("登录浏览器")
+    private String browser;
+
+    private transient String loginTimeFrom;
+    private transient String loginTimeTo;
+
+    public void setSystemBrowserInfo(String ua) {
+        try {
+            StringBuilder userAgent = new StringBuilder("[");
+            userAgent.append(ua);
+            userAgent.append("]");
+            int indexOfMac = userAgent.indexOf("Mac OS X");
+            int indexOfWindows = userAgent.indexOf("Windows NT");
+            int indexOfIE = userAgent.indexOf("MSIE");
+            int indexOfIE11 = userAgent.indexOf("rv:");
+            int indexOfFF = userAgent.indexOf("Firefox");
+            int indexOfSogou = userAgent.indexOf("MetaSr");
+            int indexOfChrome = userAgent.indexOf("Chrome");
+            int indexOfSafari = userAgent.indexOf("Safari");
+            boolean isMac = indexOfMac > 0;
+            boolean isWindows = indexOfWindows > 0;
+            boolean isLinux = userAgent.indexOf("Linux") > 0;
+            boolean containIE = indexOfIE > 0 || (isWindows && (indexOfIE11 > 0));
+            boolean containFF = indexOfFF > 0;
+            boolean containSogou = indexOfSogou > 0;
+            boolean containChrome = indexOfChrome > 0;
+            boolean containSafari = indexOfSafari > 0;
+            String browser = "";
+            if (containSogou) {
+                if (containIE) {
+                    browser = "搜狗" + userAgent.substring(indexOfIE, indexOfIE + "IE x.x".length());
+                } else if (containChrome) {
+                    browser = "搜狗" + userAgent.substring(indexOfChrome, indexOfChrome + "Chrome/xx".length());
+                }
+            } else if (containChrome) {
+                browser = userAgent.substring(indexOfChrome, indexOfChrome + "Chrome/xx".length());
+            } else if (containSafari) {
+                int indexOfSafariVersion = userAgent.indexOf("Version");
+                browser = "Safari "
+                        + userAgent.substring(indexOfSafariVersion, indexOfSafariVersion + "Version/x.x.x.x".length());
+            } else if (containFF) {
+                browser = userAgent.substring(indexOfFF, indexOfFF + "Firefox/xx".length());
+            } else if (containIE) {
+                if (indexOfIE11 > 0) {
+                    browser = "IE 11";
+                } else {
+                    browser = userAgent.substring(indexOfIE, indexOfIE + "IE x.x".length());
+                }
+            }
+            String os = "";
+            if (isMac) {
+                os = userAgent.substring(indexOfMac, indexOfMac + "MacOS X xxxxxxxx".length());
+            } else if (isLinux) {
+                os = "Linux";
+            } else if (isWindows) {
+                os = "Windows ";
+                String version = userAgent.substring(indexOfWindows + "Windows NT".length(), indexOfWindows
+                        + "Windows NTx.x".length());
+                switch (version.trim()) {
+                    case "5.0":
+                        os += "2000";
+                        break;
+                    case "5.1":
+                        os += "XP";
+                        break;
+                    case "5.2":
+                        os += "2003";
+                        break;
+                    case "6.0":
+                        os += "Vista";
+                        break;
+                    case "6.1":
+                        os += "7";
+                        break;
+                    case "6.2":
+                        os += "8";
+                        break;
+                    case "6.3":
+                        os += "8.1";
+                        break;
+                    case "10":
+                        os += "10";
+                        break;
+                }
+            }
+            this.system = os;
+            this.browser = StringUtils.replace(browser, "/", " ");
+        } catch (Exception e) {
+            log.error("获取登录信息失败:{}", e.getMessage());
+            this.system = "";
+            this.browser = "";
+        }
+
+    }
+}

+ 115 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Menu.java

@@ -0,0 +1,115 @@
+package cc.mrbird.febs.common.entity.system;
+
+import cc.mrbird.febs.common.converter.TimeConverter;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.wuwenze.poi.annotation.Excel;
+import com.wuwenze.poi.annotation.ExcelField;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author MrBird
+ */
+@Data
+@TableName("t_menu")
+@Excel("菜单信息表")
+public class Menu implements Serializable {
+
+    private static final long serialVersionUID = 7187628714679791771L;
+
+    // 菜单
+    public static final String TYPE_MENU = "0";
+    // 按钮
+    public static final String TYPE_BUTTON = "1";
+
+    /**
+     * 菜单/按钮ID
+     */
+    @TableId(value = "MENU_ID", type = IdType.AUTO)
+    private Long menuId;
+
+    /**
+     * 上级菜单ID
+     */
+    @TableField("PARENT_ID")
+    private Long parentId;
+
+    /**
+     * 菜单/按钮名称
+     */
+    @TableField("MENU_NAME")
+    @NotBlank(message = "{required}")
+    @Size(max = 10, message = "{noMoreThan}")
+    @ExcelField(value = "名称")
+    private String menuName;
+
+    /**
+     * 菜单URL
+     */
+    @TableField("PATH")
+    @Size(max = 50, message = "{noMoreThan}")
+    @ExcelField(value = "URL")
+    private String path;
+
+    /**
+     * 对应 Vue组件
+     */
+    @TableField("COMPONENT")
+    @Size(max = 100, message = "{noMoreThan}")
+    @ExcelField(value = "对应Vue组件")
+    private String component;
+
+    /**
+     * 权限标识
+     */
+    @TableField("PERMS")
+    @Size(max = 50, message = "{noMoreThan}")
+    @ExcelField(value = "权限")
+    private String perms;
+
+    /**
+     * 图标
+     */
+    @TableField("ICON")
+    @ExcelField(value = "图标")
+    private String icon;
+
+    /**
+     * 类型 0菜单 1按钮
+     */
+    @TableField("TYPE")
+    @NotBlank(message = "{required}")
+    @ExcelField(value = "类型", writeConverterExp = "0=按钮,1=菜单")
+    private String type;
+
+    /**
+     * 排序
+     */
+    @TableField("ORDER_NUM")
+    private Integer orderNum;
+
+    /**
+     * 创建时间
+     */
+    @TableField("CREATE_TIME")
+    @ExcelField(value = "创建时间", writeConverter = TimeConverter.class)
+    private Date createTime;
+
+    /**
+     * 修改时间
+     */
+    @TableField("MODIFY_TIME")
+    @ExcelField(value = "修改时间", writeConverter = TimeConverter.class)
+    private Date modifyTime;
+
+    private transient String createTimeFrom;
+    private transient String createTimeTo;
+
+}

+ 51 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Role.java

@@ -0,0 +1,51 @@
+package cc.mrbird.febs.common.entity.system;
+
+import cc.mrbird.febs.common.converter.TimeConverter;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.wuwenze.poi.annotation.Excel;
+import com.wuwenze.poi.annotation.ExcelField;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author MrBird
+ */
+@Data
+@TableName("t_role")
+@Excel("角色信息表")
+public class Role implements Serializable {
+
+    private static final long serialVersionUID = -1714476694755654924L;
+
+    @TableId(value = "ROLE_ID", type = IdType.AUTO)
+    private Long roleId;
+
+    @TableField(value = "ROLE_NAME")
+    @NotBlank(message = "{required}")
+    @Size(max = 10, message = "{noMoreThan}")
+    @ExcelField(value = "角色名称")
+    private String roleName;
+
+    @TableField(value = "REMARK")
+    @Size(max = 50, message = "{noMoreThan}")
+    @ExcelField(value = "角色描述")
+    private String remark;
+
+    @TableField(value = "CREATE_TIME")
+    @ExcelField(value = "创建时间", writeConverter = TimeConverter.class)
+    private Date createTime;
+
+    @TableField(value = "MODIFY_TIME")
+    @ExcelField(value = "修改时间", writeConverter = TimeConverter.class)
+    private Date modifyTime;
+
+    private transient String menuIds;
+
+}

+ 22 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/RoleMenu.java

@@ -0,0 +1,22 @@
+package cc.mrbird.febs.common.entity.system;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author MrBird
+ */
+@TableName("t_role_menu")
+@Data
+public class RoleMenu implements Serializable {
+	
+	private static final long serialVersionUID = -7573904024872252113L;
+
+	@TableField(value = "ROLE_ID")
+    private Long roleId;
+    @TableField(value = "MENU_ID")
+    private Long menuId;
+}

+ 158 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/SystemUser.java

@@ -0,0 +1,158 @@
+package cc.mrbird.febs.common.entity.system;
+
+import cc.mrbird.febs.common.annotation.IsMobile;
+import cc.mrbird.febs.common.converter.TimeConverter;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.wuwenze.poi.annotation.Excel;
+import com.wuwenze.poi.annotation.ExcelField;
+import lombok.Data;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author MrBird
+ */
+@Data
+@TableName("t_user")
+@Excel("用户信息表")
+public class SystemUser implements Serializable {
+
+    private static final long serialVersionUID = -4352868070794165001L;
+
+    // 用户状态:有效
+    public static final String STATUS_VALID = "1";
+    // 用户状态:锁定
+    public static final String STATUS_LOCK = "0";
+    // 默认头像
+    public static final String DEFAULT_AVATAR = "default.jpg";
+    // 默认密码
+    public static final String DEFAULT_PASSWORD = "1234qwer";
+    // 性别男
+    public static final String SEX_MALE = "0";
+    // 性别女
+    public static final String SEX_FEMALE = "1";
+    // 性别保密
+    public static final String SEX_UNKNOW = "2";
+
+    /**
+     * 用户 ID
+     */
+    @TableId(value = "USER_ID", type = IdType.AUTO)
+    private Long userId;
+
+    /**
+     * 用户名
+     */
+    @TableField("USERNAME")
+    @Size(min = 4, max = 10, message = "{range}")
+    @ExcelField(value = "用户名")
+    private String username;
+
+    /**
+     * 密码
+     */
+    @TableField("PASSWORD")
+    private String password;
+
+    /**
+     * 部门 ID
+     */
+    @TableField("DEPT_ID")
+    private Long deptId;
+
+    /**
+     * 邮箱
+     */
+    @TableField("EMAIL")
+    @Size(max = 50, message = "{noMoreThan}")
+    @Email(message = "{email}")
+    @ExcelField(value = "邮箱")
+    private String email;
+
+    /**
+     * 联系电话
+     */
+    @TableField("MOBILE")
+    @IsMobile(message = "{mobile}")
+    @ExcelField(value = "联系电话")
+    private String mobile;
+
+    /**
+     * 状态 0锁定 1有效
+     */
+    @TableField("STATUS")
+    @NotBlank(message = "{required}")
+    @ExcelField(value = "状态", writeConverterExp = "0=锁定,1=有效")
+    private String status;
+
+    /**
+     * 创建时间
+     */
+    @TableField("CREATE_TIME")
+    @ExcelField(value = "创建时间", writeConverter = TimeConverter.class)
+    private Date createTime;
+
+    /**
+     * 修改时间
+     */
+    @TableField("MODIFY_TIME")
+    @ExcelField(value = "修改时间", writeConverter = TimeConverter.class)
+    private Date modifyTime;
+
+    /**
+     * 最近访问时间
+     */
+    @TableField("LAST_LOGIN_TIME")
+    @ExcelField(value = "最近访问时间", writeConverter = TimeConverter.class)
+    private Date lastLoginTime;
+
+    /**
+     * 性别 0男 1女 2 保密
+     */
+    @TableField("SSEX")
+    @NotBlank(message = "{required}")
+    @ExcelField(value = "性别", writeConverterExp = "0=男,1=女,2=保密")
+    private String sex;
+
+    /**
+     * 头像
+     */
+    @TableField("AVATAR")
+    private String avatar;
+
+    /**
+     * 描述
+     */
+    @TableField("DESCRIPTION")
+    @Size(max = 100, message = "{noMoreThan}")
+    @ExcelField(value = "个人描述")
+    private String description;
+
+    /**
+     * 部门名称
+     */
+    @TableField(exist = false)
+    private String deptName;
+
+    @TableField(exist = false)
+    private String createTimeFrom;
+    @TableField(exist = false)
+    private String createTimeTo;
+    /**
+     * 角色 ID
+     */
+    @NotBlank(message = "{required}")
+    @TableField(exist = false)
+    private String roleId;
+
+    @TableField(exist = false)
+    private String roleName;
+
+}

+ 30 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/Table.java

@@ -0,0 +1,30 @@
+package cc.mrbird.febs.common.entity.system;
+
+import lombok.Data;
+
+/**
+ * @author MrBird
+ */
+@Data
+public class Table {
+    /**
+     * 名称
+     */
+    private String name;
+    /**
+     * 备注
+     */
+    private String remark;
+    /**
+     * 数据量(行)
+     */
+    private Long dataRows;
+    /**
+     * 创建时间
+     */
+    private String createTime;
+    /**
+     * 修改时间
+     */
+    private String updateTime;
+}

+ 24 - 0
febs-common/src/main/java/cc/mrbird/febs/common/entity/system/UserRole.java

@@ -0,0 +1,24 @@
+package cc.mrbird.febs.common.entity.system;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author MrBird
+ */
+@Data
+@TableName("t_user_role")
+public class UserRole implements Serializable {
+
+    private static final long serialVersionUID = -3166012934498268403L;
+
+    @TableField(value = "USER_ID")
+    private Long userId;
+
+    @TableField(value = "ROLE_ID")
+    private Long roleId;
+
+}

+ 15 - 0
febs-common/src/main/java/cc/mrbird/febs/common/exception/FebsException.java

@@ -0,0 +1,15 @@
+package cc.mrbird.febs.common.exception;
+
+/**
+ * FEBS系统异常
+ *
+ * @author MrBird
+ */
+public class FebsException extends Exception {
+
+    private static final long serialVersionUID = -6916154462432027437L;
+
+    public FebsException(String message) {
+        super(message);
+    }
+}

+ 14 - 0
febs-common/src/main/java/cc/mrbird/febs/common/exception/FileDownloadException.java

@@ -0,0 +1,14 @@
+package cc.mrbird.febs.common.exception;
+
+/**
+ * 文件下载异常
+ *
+ * @author MrBird
+ */
+public class FileDownloadException extends Exception {
+    private static final long serialVersionUID = -4353976687870027960L;
+
+    public FileDownloadException(String message) {
+        super(message);
+    }
+}

+ 15 - 0
febs-common/src/main/java/cc/mrbird/febs/common/exception/ValidateCodeException.java

@@ -0,0 +1,15 @@
+package cc.mrbird.febs.common.exception;
+
+/**
+ * 验证码类型异常
+ *
+ * @author MrBird
+ */
+public class ValidateCodeException extends Exception {
+
+    private static final long serialVersionUID = 7514854456967620043L;
+
+    public ValidateCodeException(String message) {
+        super(message);
+    }
+}

+ 91 - 0
febs-common/src/main/java/cc/mrbird/febs/common/handler/BaseExceptionHandler.java

@@ -0,0 +1,91 @@
+package cc.mrbird.febs.common.handler;
+
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.exception.FebsException;
+import cc.mrbird.febs.common.exception.FileDownloadException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Path;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author MrBird
+ */
+@Slf4j
+public class BaseExceptionHandler {
+
+    @ExceptionHandler(value = Exception.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    public FebsResponse handleException(Exception e) {
+        log.error("系统内部异常,异常信息", e);
+        return new FebsResponse().message("系统内部异常");
+    }
+
+    @ExceptionHandler(value = FebsException.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    public FebsResponse handleFebsException(FebsException e) {
+        log.error("系统错误", e);
+        return new FebsResponse().message(e.getMessage());
+    }
+
+    /**
+     * 统一处理请求参数校验(实体对象传参)
+     *
+     * @param e BindException
+     * @return FebsResponse
+     */
+    @ExceptionHandler(BindException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public FebsResponse handleBindException(BindException e) {
+        StringBuilder message = new StringBuilder();
+        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
+        for (FieldError error : fieldErrors) {
+            message.append(error.getField()).append(error.getDefaultMessage()).append(",");
+        }
+        message = new StringBuilder(message.substring(0, message.length() - 1));
+        return new FebsResponse().message(message.toString());
+    }
+
+    /**
+     * 统一处理请求参数校验(普通传参)
+     *
+     * @param e ConstraintViolationException
+     * @return FebsResponse
+     */
+    @ExceptionHandler(value = ConstraintViolationException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public FebsResponse handleConstraintViolationException(ConstraintViolationException e) {
+        StringBuilder message = new StringBuilder();
+        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
+        for (ConstraintViolation<?> violation : violations) {
+            Path path = violation.getPropertyPath();
+            String[] pathArr = StringUtils.splitByWholeSeparatorPreserveAllTokens(path.toString(), ".");
+            message.append(pathArr[1]).append(violation.getMessage()).append(",");
+        }
+        message = new StringBuilder(message.substring(0, message.length() - 1));
+        return new FebsResponse().message(message.toString());
+    }
+
+    @ExceptionHandler(value = FileDownloadException.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    public void handleFileDownloadException(FileDownloadException e) {
+        log.error("FileDownloadException", e);
+    }
+
+    @ExceptionHandler(value = AccessDeniedException.class)
+    @ResponseStatus(HttpStatus.FORBIDDEN)
+    public FebsResponse handleAccessDeniedException(){
+        return new FebsResponse().message("没有权限访问该资源");
+    }
+
+}

+ 25 - 0
febs-common/src/main/java/cc/mrbird/febs/common/handler/FebsAccessDeniedHandler.java

@@ -0,0 +1,25 @@
+package cc.mrbird.febs.common.handler;
+
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.utils.FebsUtil;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author MrBird
+ */
+public class FebsAccessDeniedHandler implements AccessDeniedHandler {
+
+    @Override
+    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
+        FebsResponse febsResponse = new FebsResponse();
+        FebsUtil.makeResponse(
+                response, MediaType.APPLICATION_JSON_UTF8_VALUE,
+                HttpServletResponse.SC_FORBIDDEN, febsResponse.message("没有权限访问该资源"));
+    }
+}

+ 27 - 0
febs-common/src/main/java/cc/mrbird/febs/common/handler/FebsAuthExceptionEntryPoint.java

@@ -0,0 +1,27 @@
+package cc.mrbird.febs.common.handler;
+
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.utils.FebsUtil;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author MrBird
+ */
+public class FebsAuthExceptionEntryPoint implements AuthenticationEntryPoint {
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response,
+                         AuthenticationException authException) throws IOException {
+        FebsResponse febsResponse = new FebsResponse();
+        FebsUtil.makeResponse(
+                response, MediaType.APPLICATION_JSON_UTF8_VALUE,
+                HttpServletResponse.SC_UNAUTHORIZED, febsResponse.message("token无效")
+        );
+    }
+}

+ 35 - 0
febs-common/src/main/java/cc/mrbird/febs/common/interceptor/FebsServerProtectInterceptor.java

@@ -0,0 +1,35 @@
+package cc.mrbird.febs.common.interceptor;
+
+import cc.mrbird.febs.common.entity.FebsConstant;
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.utils.FebsUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.MediaType;
+import org.springframework.util.Base64Utils;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author MrBird
+ */
+public class FebsServerProtectInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
+        // 从请求头中获取 Zuul Token
+        String token = request.getHeader(FebsConstant.ZUUL_TOKEN_HEADER);
+        String zuulToken = new String(Base64Utils.encode(FebsConstant.ZUUL_TOKEN_VALUE.getBytes()));
+        // 校验 Zuul Token的正确性
+        if (StringUtils.equals(zuulToken, token)) {
+            return true;
+        } else {
+            FebsResponse febsResponse = new FebsResponse();
+            FebsUtil.makeResponse(response,MediaType.APPLICATION_JSON_UTF8_VALUE,
+                    HttpServletResponse.SC_FORBIDDEN, febsResponse.message("请通过网关获取资源"));
+            return false;
+        }
+    }
+}

+ 24 - 0
febs-common/src/main/java/cc/mrbird/febs/common/selector/FebsCloudApplicationSelector.java

@@ -0,0 +1,24 @@
+package cc.mrbird.febs.common.selector;
+
+import cc.mrbird.febs.common.configure.FebsAuthExceptionConfigure;
+import cc.mrbird.febs.common.configure.FebsOAuth2FeignConfigure;
+import cc.mrbird.febs.common.configure.FebsServerProtectConfigure;
+import org.springframework.context.annotation.ImportSelector;
+import org.springframework.core.type.AnnotationMetadata;
+
+import javax.annotation.Nonnull;
+
+/**
+ * @author MrBird
+ */
+public class FebsCloudApplicationSelector implements ImportSelector {
+
+    @Override
+    public String[] selectImports(@Nonnull AnnotationMetadata annotationMetadata) {
+        return new String[]{
+                FebsAuthExceptionConfigure.class.getName(),
+                FebsOAuth2FeignConfigure.class.getName(),
+                FebsServerProtectConfigure.class.getName()
+        };
+    }
+}

+ 557 - 0
febs-common/src/main/java/cc/mrbird/febs/common/service/RedisService.java

@@ -0,0 +1,557 @@
+package cc.mrbird.febs.common.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 定义常用的 Redis操作
+ *
+ * @author MrBird
+ */
+public class RedisService {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /**
+     * 指定缓存失效时间
+     *
+     * @param key  键
+     * @param time 时间(秒)
+     * @return Boolean
+     */
+    public Boolean expire(String key, Long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据key获取过期时间
+     *
+     * @param key 键 不能为 null
+     * @return 时间(秒) 返回 0代表为永久有效
+     */
+    public Long getExpire(String key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 判断 key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public Boolean hasKey(String key) {
+        try {
+            return redisTemplate.hasKey(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除缓存
+     *
+     * @param key 可以传一个值 或多个
+     */
+    public void del(String... key) {
+        if (key != null && key.length > 0) {
+            if (key.length == 1) {
+                redisTemplate.delete(key[0]);
+            } else {
+                redisTemplate.delete(Arrays.asList(key));
+            }
+        }
+    }
+
+    /**
+     * 普通缓存获取
+     *
+     * @param key 键
+     * @return 值
+     */
+    public Object get(String key) {
+        return key == null ? null : redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 普通缓存放入
+     *
+     * @param key   键
+     * @param value 值
+     * @return true成功 false失败
+     */
+    public Boolean set(String key, Object value) {
+        try {
+            redisTemplate.opsForValue().set(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
+     * @return true成功 false 失败
+     */
+    public Boolean set(String key, Object value, Long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 递增
+     *
+     * @param key   键
+     * @param delta 要增加几(大于0)
+     * @return Long
+     */
+    public Long incr(String key, Long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递增因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    /**
+     * 递减
+     *
+     * @param key   键
+     * @param delta 要减少几(小于0)
+     * @return Long
+     */
+    public Long decr(String key, Long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递减因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, -delta);
+    }
+
+    /**
+     * HashGet
+     *
+     * @param key  键 不能为 null
+     * @param item 项 不能为 null
+     * @return 值
+     */
+    public Object hget(String key, String item) {
+        return redisTemplate.opsForHash().get(key, item);
+    }
+
+    /**
+     * 获取 hashKey对应的所有键值
+     *
+     * @param key 键
+     * @return 对应的多个键值
+     */
+    public Map<Object, Object> hmget(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * HashSet
+     *
+     * @param key 键
+     * @param map 对应多个键值
+     * @return true 成功 false 失败
+     */
+    public Boolean hmset(String key, Map<String, Object> map) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * HashSet 并设置时间
+     *
+     * @param key  键
+     * @param map  对应多个键值
+     * @param time 时间(秒)
+     * @return true成功 false失败
+     */
+    public Boolean hmset(String key, Map<String, Object> map, Long time) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @return true 成功 false失败
+     */
+    public Boolean hset(String key, String item, Object value) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+     * @return true 成功 false失败
+     */
+    public Boolean hset(String key, String item, Object value, Long time) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除hash表中的值
+     *
+     * @param key  键 不能为 null
+     * @param item 项 可以使多个不能为 null
+     */
+    public void hdel(String key, Object... item) {
+        redisTemplate.opsForHash().delete(key, item);
+    }
+
+    /**
+     * 判断hash表中是否有该项的值
+     *
+     * @param key  键 不能为 null
+     * @param item 项 不能为 null
+     * @return true 存在 false不存在
+     */
+    public Boolean hHasKey(String key, String item) {
+        return redisTemplate.opsForHash().hasKey(key, item);
+    }
+
+    /**
+     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要增加几(大于0)
+     * @return Double
+     */
+    public Double hincr(String key, String item, Double by) {
+        return redisTemplate.opsForHash().increment(key, item, by);
+    }
+
+    /**
+     * hash递减
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要减少记(小于0)
+     * @return Double
+     */
+    public Double hdecr(String key, String item, Double by) {
+        return redisTemplate.opsForHash().increment(key, item, -by);
+    }
+
+    /**
+     * 根据 key获取 Set中的所有值
+     *
+     * @param key 键
+     * @return Set
+     */
+    public Set<Object> sGet(String key) {
+        try {
+            return redisTemplate.opsForSet().members(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 根据value从一个set中查询,是否存在
+     *
+     * @param key   键
+     * @param value 值
+     * @return true 存在 false不存在
+     */
+    public Boolean sHasKey(String key, Object value) {
+        try {
+            return redisTemplate.opsForSet().isMember(key, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将数据放入set缓存
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public Long sSet(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0L;
+        }
+    }
+
+    /**
+     * 将set数据放入缓存
+     *
+     * @param key    键
+     * @param time   时间(秒)
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public Long sSetAndTime(String key, Long time, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().add(key, values);
+            if (time > 0)
+                expire(key, time);
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0L;
+        }
+    }
+
+    /**
+     * 获取set缓存的长度
+     *
+     * @param key 键
+     * @return Long
+     */
+    public Long sGetSetSize(String key) {
+        try {
+            return redisTemplate.opsForSet().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0L;
+        }
+    }
+
+    /**
+     * 移除值为value的
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 移除的个数
+     */
+    public Long setRemove(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().remove(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0L;
+        }
+    }
+
+    /**
+     * 获取list缓存的内容
+     *
+     * @param key   键
+     * @param start 开始
+     * @param end   结束 0 到 -1代表所有值
+     * @return List
+     */
+    public List<Object> lGet(String key, Long start, Long end) {
+        try {
+            return redisTemplate.opsForList().range(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 获取list缓存的长度
+     *
+     * @param key 键
+     * @return Long
+     */
+    public Long lGetListSize(String key) {
+        try {
+            return redisTemplate.opsForList().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0L;
+        }
+    }
+
+    /**
+     * 通过索引 获取list中的值
+     *
+     * @param key   键
+     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;
+     *              index<0时,-1,表尾,-2倒数第二个元素,依次类推
+     * @return Object
+     */
+    public Object lGetIndex(String key, Long index) {
+        try {
+            return redisTemplate.opsForList().index(key, index);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @return Boolean
+     */
+    public Boolean lSet(String key, Object value) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return Boolean
+     */
+    public Boolean lSet(String key, Object value, Long time) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            if (time > 0)
+                expire(key, time);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @return Boolean
+     */
+    public Boolean lSet(String key, List<Object> value) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return Boolean
+     */
+    public Boolean lSet(String key, List<Object> value, Long time) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            if (time > 0)
+                expire(key, time);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据索引修改list中的某条数据
+     *
+     * @param key   键
+     * @param index 索引
+     * @param value 值
+     * @return Boolean
+     */
+    public Boolean lUpdateIndex(String key, Long index, Object value) {
+        try {
+            redisTemplate.opsForList().set(key, index, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 移除N个值为value
+     *
+     * @param key   键
+     * @param count 移除多少个
+     * @param value 值
+     * @return 移除的个数
+     */
+    public Long lRemove(String key, Long count, Object value) {
+        try {
+            return redisTemplate.opsForList().remove(key, count, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0L;
+        }
+    }
+}

+ 84 - 0
febs-common/src/main/java/cc/mrbird/febs/common/utils/DateUtil.java

@@ -0,0 +1,84 @@
+package cc.mrbird.febs.common.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * 时间工具类
+ *
+ * @author MrBird
+ */
+public class DateUtil {
+
+    public static final String FULL_TIME_PATTERN = "yyyyMMddHHmmss";
+
+    public static final String FULL_TIME_SPLIT_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    public static final String CST_TIME_PATTERN = "EEE MMM dd HH:mm:ss zzz yyyy";
+
+    /**
+     * 格式化时间,格式为 yyyyMMddHHmmss
+     *
+     * @param localDateTime LocalDateTime
+     * @return 格式化后的字符串
+     */
+    public static String formatFullTime(LocalDateTime localDateTime) {
+        return formatFullTime(localDateTime, FULL_TIME_PATTERN);
+    }
+
+    /**
+     * 根据传入的格式,格式化时间
+     *
+     * @param localDateTime LocalDateTime
+     * @param format        格式
+     * @return 格式化后的字符串
+     */
+    public static String formatFullTime(LocalDateTime localDateTime, String format) {
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
+        return localDateTime.format(dateTimeFormatter);
+    }
+
+    /**
+     * 根据传入的格式,格式化时间
+     *
+     * @param date   Date
+     * @param format 格式
+     * @return 格式化后的字符串
+     */
+    public static String getDateFormat(Date date, String format) {
+        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format, Locale.CHINA);
+        return simpleDateFormat.format(date);
+    }
+
+    /**
+     * 格式化 CST类型的时间字符串
+     *
+     * @param date   CST类型的时间字符串
+     * @param format 格式
+     * @return 格式化后的字符串
+     * @throws ParseException 异常
+     */
+    public static String formatCSTTime(String date, String format) throws ParseException {
+        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(CST_TIME_PATTERN, Locale.US);
+        Date usDate = simpleDateFormat.parse(date);
+        return DateUtil.getDateFormat(usDate, format);
+    }
+
+    /**
+     * 格式化 Instant
+     *
+     * @param instant Instant
+     * @param format  格式
+     * @return 格式化后的字符串
+     */
+    public static String formatInstant(Instant instant, String format) {
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+        return localDateTime.format(DateTimeFormatter.ofPattern(format));
+    }
+}

+ 114 - 0
febs-common/src/main/java/cc/mrbird/febs/common/utils/FebsUtil.java

@@ -0,0 +1,114 @@
+package cc.mrbird.febs.common.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.IntStream;
+
+/**
+ * FEBS工具类
+ *
+ * @author MrBird
+ */
+@Slf4j
+public class FebsUtil {
+
+    /**
+     * 驼峰转下划线
+     *
+     * @param value 待转换值
+     * @return 结果
+     */
+    public static String camelToUnderscore(String value) {
+        if (StringUtils.isBlank(value))
+            return value;
+        String[] arr = StringUtils.splitByCharacterTypeCamelCase(value);
+        if (arr.length == 0)
+            return value;
+        StringBuilder result = new StringBuilder();
+        IntStream.range(0, arr.length).forEach(i -> {
+            if (i != arr.length - 1)
+                result.append(arr[i]).append("_");
+            else
+                result.append(arr[i]);
+        });
+        return StringUtils.lowerCase(result.toString());
+    }
+
+    /**
+     * 下划线转驼峰
+     *
+     * @param value 待转换值
+     * @return 结果
+     */
+    public static String underscoreToCamel(String value) {
+        StringBuilder result = new StringBuilder();
+        String[] arr = value.split("_");
+        for (String s : arr) {
+            result.append((String.valueOf(s.charAt(0))).toUpperCase()).append(s.substring(1));
+        }
+        return result.toString();
+    }
+
+    /**
+     * 判断是否为 ajax请求
+     *
+     * @param request HttpServletRequest
+     * @return boolean
+     */
+    public static boolean isAjaxRequest(HttpServletRequest request) {
+        return (request.getHeader("X-Requested-With") != null
+                && "XMLHttpRequest".equals(request.getHeader("X-Requested-With")));
+    }
+
+    /**
+     * 正则校验
+     *
+     * @param regex 正则表达式字符串
+     * @param value 要匹配的字符串
+     * @return 正则校验结果
+     */
+    public static boolean match(String regex, String value) {
+        Pattern pattern = Pattern.compile(regex);
+        Matcher matcher = pattern.matcher(value);
+        return matcher.matches();
+    }
+
+    /**
+     * 设置响应
+     *
+     * @param response    HttpServletResponse
+     * @param contentType content-type
+     * @param status      http状态码
+     * @param value       响应内容
+     * @throws IOException IOException
+     */
+    public static void makeResponse(HttpServletResponse response, String contentType,
+                                    int status, Object value) throws IOException {
+        response.setContentType(contentType);
+        response.setStatus(status);
+        response.getOutputStream().write(JSONObject.toJSONString(value).getBytes());
+    }
+
+    /**
+     * 封装前端分页表格所需数据
+     *
+     * @param pageInfo pageInfo
+     * @return Map<String, Object>
+     */
+    public static Map<String, Object> getDataTable(IPage<?> pageInfo) {
+        Map<String, Object> data = new HashMap<>();
+        data.put("rows", pageInfo.getRecords());
+        data.put("total", pageInfo.getTotal());
+        return data;
+    }
+}

+ 157 - 0
febs-common/src/main/java/cc/mrbird/febs/common/utils/FileUtil.java

@@ -0,0 +1,157 @@
+package cc.mrbird.febs.common.utils;
+
+import cc.mrbird.febs.common.entity.FebsConstant;
+import com.google.common.base.Preconditions;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.util.Arrays;
+import java.util.zip.CRC32;
+import java.util.zip.CheckedOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author MrBird
+ */
+@Slf4j
+public class FileUtil {
+
+    private static final int BUFFER = 1024 * 8;
+
+    /**
+     * 压缩文件或目录
+     *
+     * @param fromPath 待压缩文件或路径
+     * @param toPath   压缩文件,如 xx.zip
+     */
+    public static void compress(String fromPath, String toPath) throws IOException {
+        File fromFile = new File(fromPath);
+        File toFile = new File(toPath);
+        if (!fromFile.exists()) {
+            throw new FileNotFoundException(fromPath + "不存在!");
+        }
+        try (
+                FileOutputStream outputStream = new FileOutputStream(toFile);
+                CheckedOutputStream checkedOutputStream = new CheckedOutputStream(outputStream, new CRC32());
+                ZipOutputStream zipOutputStream = new ZipOutputStream(checkedOutputStream)
+        ) {
+            String baseDir = "";
+            compress(fromFile, zipOutputStream, baseDir);
+        }
+    }
+
+    /**
+     * 文件下载
+     *
+     * @param filePath 待下载文件路径
+     * @param fileName 下载文件名称
+     * @param delete   下载后是否删除源文件
+     * @param response HttpServletResponse
+     * @throws Exception Exception
+     */
+    public static void download(String filePath, String fileName, Boolean delete, HttpServletResponse response) throws Exception {
+        File file = new File(filePath);
+        if (!file.exists())
+            throw new Exception("文件未找到");
+
+        String fileType = getFileType(file);
+        if (!fileTypeIsValid(fileType)) {
+            throw new Exception("暂不支持该类型文件下载");
+        }
+        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;fileName=" + java.net.URLEncoder.encode(fileName, "utf-8"));
+        response.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE);
+        response.setCharacterEncoding("utf-8");
+        try (InputStream inputStream = new FileInputStream(file); OutputStream os = response.getOutputStream()) {
+            byte[] b = new byte[2048];
+            int length;
+            while ((length = inputStream.read(b)) > 0) {
+                os.write(b, 0, length);
+            }
+        } finally {
+            if (delete)
+                delete(filePath);
+        }
+    }
+
+    /**
+     * 递归删除文件或目录
+     *
+     * @param filePath 文件或目录
+     */
+    public static void delete(String filePath) {
+        File file = new File(filePath);
+        if (file.isDirectory()) {
+            File[] files = file.listFiles();
+            if (files != null) Arrays.stream(files).forEach(f -> delete(f.getPath()));
+        }
+        file.delete();
+    }
+
+    /**
+     * 获取文件类型
+     *
+     * @param file 文件
+     * @return 文件类型
+     * @throws Exception Exception
+     */
+    private static String getFileType(File file) throws Exception {
+        Preconditions.checkNotNull(file);
+        if (file.isDirectory()) {
+            throw new Exception("file不是文件");
+        }
+        String fileName = file.getName();
+        return fileName.substring(fileName.lastIndexOf(".") + 1);
+    }
+
+
+    /**
+     * 校验文件类型是否是允许下载的类型
+     * (出于安全考虑:https://github.com/wuyouzhuguli/FEBS-Shiro/issues/40)
+     *
+     * @param fileType fileType
+     * @return Boolean
+     */
+    private static Boolean fileTypeIsValid(String fileType) {
+        Preconditions.checkNotNull(fileType);
+        fileType = StringUtils.lowerCase(fileType);
+        return ArrayUtils.contains(FebsConstant.VALID_FILE_TYPE, fileType);
+    }
+
+    private static void compress(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
+        if (file.isDirectory()) {
+            compressDirectory(file, zipOut, baseDir);
+        } else {
+            compressFile(file, zipOut, baseDir);
+        }
+    }
+
+    private static void compressDirectory(File dir, ZipOutputStream zipOut, String baseDir) throws IOException {
+        File[] files = dir.listFiles();
+        if (files != null && ArrayUtils.isNotEmpty(files)) {
+            for (File file : files) {
+                compress(file, zipOut, baseDir + dir.getName() + File.separator);
+            }
+        }
+    }
+
+    private static void compressFile(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
+        if (!file.exists()) {
+            return;
+        }
+        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
+            ZipEntry entry = new ZipEntry(baseDir + file.getName());
+            zipOut.putNextEntry(entry);
+            int count;
+            byte[] data = new byte[BUFFER];
+            while ((count = bis.read(data, 0, BUFFER)) != -1) {
+                zipOut.write(data, 0, count);
+            }
+        }
+    }
+}

+ 19 - 0
febs-common/src/main/java/cc/mrbird/febs/common/utils/HttpContextUtil.java

@@ -0,0 +1,19 @@
+package cc.mrbird.febs.common.utils;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Objects;
+
+/**
+ * 用于获取 HTTP请求上下文
+ *
+ * @author MrBird
+ */
+public class HttpContextUtil {
+
+    public static HttpServletRequest getHttpServletRequest() {
+        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
+    }
+}

+ 34 - 0
febs-common/src/main/java/cc/mrbird/febs/common/utils/IPUtil.java

@@ -0,0 +1,34 @@
+package cc.mrbird.febs.common.utils;
+
+import org.springframework.http.HttpHeaders;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author MrBird
+ */
+public class IPUtil {
+
+	private static final String UNKNOWN = "unknown";
+
+	/**
+	 * 获取 IP地址
+	 * 使用 Nginx等反向代理软件, 则不能通过 request.getRemoteAddr()获取 IP地址
+	 * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,
+	 * X-Forwarded-For中第一个非 unknown的有效IP字符串,则为真实IP地址
+	 */
+	public static String getIpAddr(HttpServletRequest request) {
+		String ip = request.getHeader("x-forwarded-for");
+		if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+			ip = request.getHeader("Proxy-Client-IP");
+		}
+		if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+			ip = request.getHeader("WL-Proxy-Client-IP");
+		}
+		if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+			ip = request.getRemoteAddr();
+		}
+		return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
+	}
+
+}

+ 125 - 0
febs-common/src/main/java/cc/mrbird/febs/common/utils/SortUtil.java

@@ -0,0 +1,125 @@
+package cc.mrbird.febs.common.utils;
+
+import cc.mrbird.febs.common.entity.FebsConstant;
+import cc.mrbird.febs.common.entity.QueryRequest;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 处理排序工具类
+ * 
+ * @author MrBird
+ */
+@SuppressWarnings("unchecked")
+public class SortUtil {
+    /**
+     * 处理排序(分页情况下) for mybatis-plus
+     *
+     * @param request           QueryRequest
+     * @param page              Page
+     * @param defaultSort       默认排序的字段
+     * @param defaultOrder      默认排序规则
+     * @param camelToUnderscore 是否开启驼峰转下划线
+     */
+    public static void handlePageSort(QueryRequest request, Page page, String defaultSort, String defaultOrder, boolean camelToUnderscore) {
+        page.setCurrent(request.getPageNum());
+        page.setSize(request.getPageSize());
+        String sortField = request.getField();
+        if (camelToUnderscore) {
+            sortField = FebsUtil.camelToUnderscore(sortField);
+            defaultSort = FebsUtil.camelToUnderscore(defaultSort);
+        }
+        if (StringUtils.isNotBlank(request.getField())
+                && StringUtils.isNotBlank(request.getOrder())
+                && !StringUtils.equalsIgnoreCase(request.getField(), "null")
+                && !StringUtils.equalsIgnoreCase(request.getOrder(), "null")) {
+            if (StringUtils.equals(request.getOrder(), FebsConstant.ORDER_DESC))
+                page.setDesc(sortField);
+            else
+                page.setAsc(sortField);
+        } else {
+            if (StringUtils.isNotBlank(defaultSort)) {
+                if (StringUtils.equals(defaultOrder, FebsConstant.ORDER_DESC))
+                    page.setDesc(defaultSort);
+                else
+                    page.setAsc(defaultSort);
+            }
+        }
+    }
+
+    /**
+     * 处理排序 for mybatis-plus
+     *
+     * @param request QueryRequest
+     * @param page    Page
+     */
+    public static void handlePageSort(QueryRequest request, Page page) {
+        handlePageSort(request, page, null, null, false);
+    }
+
+    /**
+     * 处理排序 for mybatis-plus
+     *
+     * @param request           QueryRequest
+     * @param page              Page
+     * @param camelToUnderscore 是否开启驼峰转下划线
+     */
+    public static void handlePageSort(QueryRequest request, Page page, boolean camelToUnderscore) {
+        handlePageSort(request, page, null, null, camelToUnderscore);
+    }
+
+    /**
+     * 处理排序 for mybatis-plus
+     *
+     * @param request           QueryRequest
+     * @param wrapper           wrapper
+     * @param defaultSort       默认排序的字段
+     * @param defaultOrder      默认排序规则
+     * @param camelToUnderscore 是否开启驼峰转下划线
+     */
+    public static void handleWrapperSort(QueryRequest request, QueryWrapper wrapper, String defaultSort, String defaultOrder, boolean camelToUnderscore) {
+        String sortField = request.getField();
+        if (camelToUnderscore) {
+            sortField = FebsUtil.camelToUnderscore(sortField);
+            defaultSort = FebsUtil.camelToUnderscore(defaultSort);
+        }
+        if (StringUtils.isNotBlank(request.getField())
+                && StringUtils.isNotBlank(request.getOrder())
+                && !StringUtils.equalsIgnoreCase(request.getField(), "null")
+                && !StringUtils.equalsIgnoreCase(request.getOrder(), "null")) {
+            if (StringUtils.equals(request.getOrder(), FebsConstant.ORDER_DESC))
+                wrapper.orderByDesc(sortField);
+            else
+                wrapper.orderByAsc(sortField);
+        } else {
+            if (StringUtils.isNotBlank(defaultSort)) {
+                if (StringUtils.equals(defaultOrder, FebsConstant.ORDER_DESC))
+                    wrapper.orderByDesc(defaultSort);
+                else
+                    wrapper.orderByAsc(defaultSort);
+            }
+        }
+    }
+
+    /**
+     * 处理排序 for mybatis-plus
+     *
+     * @param request QueryRequest
+     * @param wrapper wrapper
+     */
+    public static void handleWrapperSort(QueryRequest request, QueryWrapper wrapper) {
+        handleWrapperSort(request, wrapper, null, null, false);
+    }
+
+    /**
+     * 处理排序 for mybatis-plus
+     *
+     * @param request           QueryRequest
+     * @param wrapper           wrapper
+     * @param camelToUnderscore 是否开启驼峰转下划线
+     */
+    public static void handleWrapperSort(QueryRequest request, QueryWrapper wrapper, boolean camelToUnderscore) {
+        handleWrapperSort(request, wrapper, null, null, camelToUnderscore);
+    }
+}

+ 46 - 0
febs-common/src/main/java/cc/mrbird/febs/common/utils/SpringContextUtil.java

@@ -0,0 +1,46 @@
+package cc.mrbird.febs.common.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * 用于从 IOC容器中获取 Bean
+ * 
+ * @author MrBird
+ *
+ */
+@Component
+public class SpringContextUtil implements ApplicationContextAware {
+	private static ApplicationContext applicationContext;
+
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		SpringContextUtil.applicationContext = applicationContext;
+	}
+
+	public static Object getBean(String name) {
+		return applicationContext.getBean(name);
+	}
+	public static <T> T getBean(Class<T> clazz){
+		return applicationContext.getBean(clazz);
+	}
+
+	public static <T> T getBean(String name, Class<T> requiredType) {
+		return applicationContext.getBean(name, requiredType);
+	}
+
+	public static boolean containsBean(String name) {
+		return applicationContext.containsBean(name);
+	}
+
+	public static boolean isSingleton(String name) {
+		return applicationContext.isSingleton(name);
+	}
+
+	public static Class<?> getType(String name) {
+		return applicationContext.getType(name);
+	}
+
+}

+ 93 - 0
febs-common/src/main/java/cc/mrbird/febs/common/utils/TreeUtil.java

@@ -0,0 +1,93 @@
+package cc.mrbird.febs.common.utils;
+
+import cc.mrbird.febs.common.entity.Tree;
+import cc.mrbird.febs.common.entity.router.VueRouter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author MrBird
+ */
+public class TreeUtil {
+
+    private final static String TOP_NODE_ID = "0";
+
+    /**
+     * 用于构建菜单或部门树
+     *
+     * @param nodes nodes
+     * @return <T> List<? extends Tree>
+     */
+    public static <T> List<? extends Tree> build(List<? extends Tree<T>> nodes) {
+        if (nodes == null) {
+            return null;
+        }
+        List<Tree<T>> topNodes = new ArrayList<>();
+        nodes.forEach(node -> {
+            String pid = node.getParentId();
+            if (pid == null || TOP_NODE_ID.equals(pid)) {
+                topNodes.add(node);
+                return;
+            }
+            for (Tree<T> n : nodes) {
+                String id = n.getId();
+                if (id != null && id.equals(pid)) {
+                    if (n.getChildren() == null)
+                        n.initChildren();
+                    n.getChildren().add(node);
+                    node.setHasParent(true);
+                    n.setHasChildren(true);
+                    n.setHasParent(true);
+                    return;
+                }
+            }
+            if (topNodes.isEmpty())
+                topNodes.add(node);
+        });
+        return topNodes;
+    }
+
+
+    /**
+     * 构造前端路由
+     *
+     * @param routes routes
+     * @param <T>    T
+     * @return ArrayList<VueRouter < T>>
+     */
+    public static <T> List<VueRouter<T>> buildVueRouter(List<VueRouter<T>> routes) {
+        if (routes == null) {
+            return null;
+        }
+        List<VueRouter<T>> topRoutes = new ArrayList<>();
+        VueRouter<T> router = new VueRouter<>();
+        routes.forEach(route -> {
+            String parentId = route.getParentId();
+            if (parentId == null || TOP_NODE_ID.equals(parentId)) {
+                topRoutes.add(route);
+                return;
+            }
+            for (VueRouter<T> parent : routes) {
+                String id = parent.getId();
+                if (id != null && id.equals(parentId)) {
+                    if (parent.getChildren() == null)
+                        parent.initChildren();
+                    parent.getChildren().add(route);
+                    parent.setAlwaysShow(true);
+                    parent.setHasChildren(true);
+                    route.setHasParent(true);
+                    parent.setHasParent(true);
+                    return;
+                }
+            }
+        });
+        VueRouter<T> router404 = new VueRouter<>();
+        router404.setName("404");
+        router404.setComponent("error-page/404");
+        router404.setPath("*");
+
+        topRoutes.add(router404);
+        return topRoutes;
+    }
+}

+ 35 - 0
febs-common/src/main/java/cc/mrbird/febs/common/validator/MobileValidator.java

@@ -0,0 +1,35 @@
+package cc.mrbird.febs.common.validator;
+
+import cc.mrbird.febs.common.annotation.IsMobile;
+import cc.mrbird.febs.common.entity.RegexpConstant;
+import cc.mrbird.febs.common.utils.FebsUtil;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * 校验是否为合法的手机号码
+ *
+ * @author MrBird
+ */
+public class MobileValidator implements ConstraintValidator<IsMobile, String> {
+
+    @Override
+    public void initialize(IsMobile isMobile) {
+    }
+
+    @Override
+    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
+        try {
+            if (StringUtils.isBlank(s)) {
+                return true;
+            } else {
+                String regex = RegexpConstant.MOBILE_REG;
+                return FebsUtil.match(regex, s);
+            }
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}

+ 5 - 0
febs-config/Dockerfile

@@ -0,0 +1,5 @@
+FROM openjdk:8u212-jre
+MAINTAINER MrBird 852252810@qq.com
+
+COPY ./target/febs-config-1.0-SNAPSHOT.jar /febs/febs-config-1.0-SNAPSHOT.jar
+ENTRYPOINT ["java", "-Xmx256m", "-jar", "/febs/febs-config-1.0-SNAPSHOT.jar"]

+ 46 - 0
febs-config/pom.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cc.mrbird</groupId>
+        <artifactId>febs-cloud</artifactId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../febs-cloud/pom.xml</relativePath>
+    </parent>
+    <groupId>cc.mrbird.febs</groupId>
+    <artifactId>febs-config</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>FEBS-Config</name>
+    <description>FEBS-Config微服务配置中心</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-config-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.codecentric</groupId>
+            <artifactId>spring-boot-admin-starter-client</artifactId>
+            <version>2.1.6</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 60 - 0
febs-config/repository/config/FEBS-Auth-dev.yml

@@ -0,0 +1,60 @@
+spring:
+  datasource:
+    dynamic:
+      hikari:
+        connection-timeout: 30000
+        max-lifetime: 1800000
+        max-pool-size: 15
+        min-idle: 5
+        connection-test-query: select 1
+        pool-name: FebsHikariCP
+      primary: base
+      datasource:
+        base:
+          username: root
+          password: 123456
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://${mysql.url}:3306/febs_cloud_base?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
+
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
+  boot:
+    admin:
+      client:
+        url: http://${febs-monitor-admin}:8401
+        username: febs
+        password: 123456
+
+  redis:
+    database: 0
+    host: ${redis.url}
+    port: 6379
+    lettuce:
+      pool:
+        min-idle: 8
+        max-idle: 500
+        max-active: 2000
+        max-wait: 10000
+    timeout: 5000
+mybatis-plus:
+  type-aliases-package: cc.mrbird.febs.common.entity.system
+  mapper-locations: classpath:mapper/*.xml
+  configuration:
+    jdbc-type-for-null: null
+  global-config:
+    banner: false
+info:
+  app:
+    name: ${spring.application.name}
+    description: "@project.description@"
+    version: "@project.version@"
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS

+ 61 - 0
febs-config/repository/config/FEBS-Auth-prod.yml

@@ -0,0 +1,61 @@
+spring:
+  datasource:
+    dynamic:
+      hikari:
+        connection-timeout: 30000
+        max-lifetime: 1800000
+        max-pool-size: 15
+        min-idle: 5
+        connection-test-query: select 1
+        pool-name: FebsHikariCP
+      primary: base
+      datasource:
+        base:
+          username: root
+          password: 123456
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://${mysql.url}:3306/febs_cloud_base?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
+
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
+  boot:
+    admin:
+      client:
+        url: http://${febs-monitor-admin}:8401
+        username: febs
+        password: 123456
+
+  redis:
+    database: 0
+    host: ${redis.url}
+    port: 6379
+    password: 123456
+    lettuce:
+      pool:
+        min-idle: 8
+        max-idle: 500
+        max-active: 2000
+        max-wait: 10000
+    timeout: 5000
+mybatis-plus:
+  type-aliases-package: cc.mrbird.febs.common.entity.system
+  mapper-locations: classpath:mapper/*.xml
+  configuration:
+    jdbc-type-for-null: null
+  global-config:
+    banner: false
+info:
+  app:
+    name: ${spring.application.name}
+    description: "@project.description@"
+    version: "@project.version@"
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS

+ 60 - 0
febs-config/repository/config/FEBS-Auth-test.yml

@@ -0,0 +1,60 @@
+spring:
+  datasource:
+    dynamic:
+      hikari:
+        connection-timeout: 30000
+        max-lifetime: 1800000
+        max-pool-size: 15
+        min-idle: 5
+        connection-test-query: select 1
+        pool-name: FebsHikariCP
+      primary: base
+      datasource:
+        base:
+          username: root
+          password: 123456
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://${mysql.url}:3306/febs_cloud_base?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
+
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
+  boot:
+    admin:
+      client:
+        url: http://${febs-monitor-admin}:8401
+        username: febs
+        password: 123456
+
+  redis:
+    database: 0
+    host: ${redis.url}
+    port: 6379
+    lettuce:
+      pool:
+        min-idle: 8
+        max-idle: 500
+        max-active: 2000
+        max-wait: 10000
+    timeout: 5000
+mybatis-plus:
+  type-aliases-package: cc.mrbird.febs.common.entity.system
+  mapper-locations: classpath:mapper/*.xml
+  configuration:
+    jdbc-type-for-null: null
+  global-config:
+    banner: false
+info:
+  app:
+    name: ${spring.application.name}
+    description: "@project.description@"
+    version: "@project.version@"
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS

+ 57 - 0
febs-config/repository/config/FEBS-Gateway.yml

@@ -0,0 +1,57 @@
+spring:
+  boot:
+    admin:
+      client:
+        url: http://${febs-monitor-admin}:8401
+        username: febs
+        password: 123456
+  mvc:
+    throw-exception-if-no-handler-found: true
+  autoconfigure:
+    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
+zuul:
+  routes:
+    auth:
+      path: /auth/**
+      serviceId: FEBS-Auth
+      sensitiveHeaders: "*"
+    system:
+      path: /system/**
+      serviceId: FEBS-Server-System
+      sensitiveHeaders: "*"
+    test:
+      path: /test/**
+      serviceId: FEBS-Server-Test
+      sensitiveHeaders: "*"
+  retryable: false
+  ignored-services: "*"
+  ribbon:
+    eager-load:
+      enabled: true
+  host:
+    connect-timeout-millis: 5000
+    socket-timeout-millis: 5000
+  add-proxy-headers: true
+
+  SendErrorFilter:
+    error:
+      disable: true
+
+ribbon:
+  ConnectTimeout: 3000
+  ReadTimeout: 5000
+
+info:
+  app:
+    name: ${spring.application.name}
+    description: "@project.description@"
+    version: "@project.version@"
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS

+ 74 - 0
febs-config/repository/config/FEBS-Server-System-dev.yml

@@ -0,0 +1,74 @@
+spring:
+  aop:
+    proxy-target-class: true
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
+  boot:
+    admin:
+      client:
+        url: http://${febs-monitor-admin}:8401
+        username: febs
+        password: 123456
+  freemarker:
+    check-template-location: false
+
+  datasource:
+    dynamic:
+      p6spy: true
+      hikari:
+        connection-timeout: 30000
+        max-lifetime: 1800000
+        max-pool-size: 15
+        min-idle: 5
+        connection-test-query: select 1
+        pool-name: FebsHikariCP
+      primary: base
+      datasource:
+        base:
+          username: root
+          password: 123456
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://${mysql.url}:3306/febs_cloud_base?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
+  zipkin:
+    sender:
+      type: rabbit
+  sleuth:
+    sampler:
+      probability: 1
+  rabbitmq:
+    host: ${rabbitmq.url}
+    port: 5672
+    username: febs
+    password: 123456
+
+  autoconfigure:
+    exclude: org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
+
+mybatis-plus:
+  type-aliases-package: cc.mrbird.febs.common.entity.system
+  mapper-locations: classpath:mapper/*/*.xml
+  configuration:
+    jdbc-type-for-null: null
+  global-config:
+    banner: false
+security:
+  oauth2:
+    resource:
+      id: ${spring.application.name}
+      user-info-uri: http://${febs-gateway}:8301/auth/user
+
+info:
+  app:
+    name: ${spring.application.name}
+    description: "@project.description@"
+    version: "@project.version@"
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS

+ 74 - 0
febs-config/repository/config/FEBS-Server-System-prod.yml

@@ -0,0 +1,74 @@
+spring:
+  aop:
+    proxy-target-class: true
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
+  boot:
+    admin:
+      client:
+        url: http://${febs-monitor-admin}:8401
+        username: febs
+        password: 123456
+  freemarker:
+    check-template-location: false
+
+  datasource:
+    dynamic:
+      p6spy: true
+      hikari:
+        connection-timeout: 30000
+        max-lifetime: 1800000
+        max-pool-size: 15
+        min-idle: 5
+        connection-test-query: select 1
+        pool-name: FebsHikariCP
+      primary: base
+      datasource:
+        base:
+          username: root
+          password: 123456
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://${mysql.url}:3306/febs_cloud_base?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
+  zipkin:
+    sender:
+      type: rabbit
+  sleuth:
+    sampler:
+      probability: 1
+  rabbitmq:
+    host: ${rabbitmq.url}
+    port: 5672
+    username: febs
+    password: 123456
+
+  autoconfigure:
+    exclude: org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
+
+mybatis-plus:
+  type-aliases-package: cc.mrbird.febs.common.entity.system
+  mapper-locations: classpath:mapper/*/*.xml
+  configuration:
+    jdbc-type-for-null: null
+  global-config:
+    banner: false
+security:
+  oauth2:
+    resource:
+      id: ${spring.application.name}
+      user-info-uri: http://${febs-gateway}:8301/auth/user
+
+info:
+  app:
+    name: ${spring.application.name}
+    description: "@project.description@"
+    version: "@project.version@"
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů