Selaa lähdekoodia

主分支由改为main

15858193327 3 vuotta sitten
commit
ddf1f68615
100 muutettua tiedostoa jossa 4180 lisäystä ja 0 poistoa
  1. 16 0
      .gitee/ISSUE_TEMPLATE.zh-CN.md
  2. 15 0
      .gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
  3. 29 0
      .gitignore
  4. 201 0
      LICENSE
  5. 195 0
      README.md
  6. 109 0
      banner-font.txt
  7. 6 0
      doc/README.MD
  8. 9 0
      doc/appConfig/boot-admin.yml
  9. 100 0
      doc/appConfig/common.yml
  10. 103 0
      doc/appConfig/db-mysql.yml
  11. 89 0
      doc/appConfig/db.yml
  12. 13 0
      doc/appConfig/freemark.yml
  13. 19 0
      doc/appConfig/mq-rabbit.yml
  14. 54 0
      doc/appConfig/redis.yml
  15. 88 0
      doc/appConfig/shoulder-gateway.yml
  16. 9 0
      doc/appConfig/shoulder-sms-center.yml
  17. 48 0
      doc/appConfig/shoulder-storage-center.yml
  18. 21 0
      doc/appConfig/third-account.yml
  19. 11 0
      doc/nacos.md
  20. 7 0
      doc/sentinel.yml
  21. 1 0
      dynamicConfig/README.MD
  22. 7 0
      dynamicConfig/config-dev.properties
  23. 4 0
      dynamicConfig/config-prod.properties
  24. BIN
      img/architecture.png
  25. BIN
      img/docker.png
  26. BIN
      img/elk-nginx.png
  27. BIN
      img/elk-nginx2.png
  28. BIN
      img/host.png
  29. BIN
      img/mysql1.png
  30. BIN
      img/mysql2.png
  31. BIN
      img/mysql3.png
  32. BIN
      img/nacos1.png
  33. BIN
      img/nacos2.png
  34. BIN
      img/redis.png
  35. 25 0
      pom.xml
  36. 30 0
      shoulder-auth-center/README.md
  37. 24 0
      shoulder-auth-center/pom.xml
  38. 16 0
      shoulder-auth-center/shoulder-auth-api/pom.xml
  39. 72 0
      shoulder-backstage/README.md
  40. 15 0
      shoulder-backstage/pom.xml
  41. 29 0
      shoulder-gateway/README.md
  42. 31 0
      shoulder-gateway/pom.xml
  43. 10 0
      shoulder-gateway/shoulder-api-gateway/README.md
  44. 76 0
      shoulder-gateway/shoulder-api-gateway/pom.xml
  45. 27 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/ShoulderApiGateway.java
  46. 30 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/ServiceTokenClient.java
  47. 39 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/dto/param/AccessToken2ServiceTokenParam.java
  48. 27 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/dto/param/DeleteServiceTokenParam.java
  49. 83 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/impl/ServiceTokenClientImpl.java
  50. 45 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/config/CorsConfig.java
  51. 147 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/config/JsonExceptionHandler.java
  52. 31 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/ex/ShoulderGatewayException.java
  53. 104 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/filter/AuthenticationGlobalFilter.java
  54. 35 0
      shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/util/RequestUtil.java
  55. 11 0
      shoulder-gateway/shoulder-api-gateway/src/main/resources/banner.txt
  56. 50 0
      shoulder-gateway/shoulder-api-gateway/src/main/resources/bootstrap.yml
  57. 5 0
      shoulder-gateway/shoulder-storage-gateway/README.md
  58. 15 0
      shoulder-gateway/shoulder-storage-gateway/pom.xml
  59. 109 0
      shoulder-generator/pom.xml
  60. 18 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/GeneratorApp.java
  61. 76 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/controller/GeneratorController.java
  62. 24 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/dao/SysGeneratorDao.java
  63. 31 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/dao/SysGeneratorDao.xml
  64. 34 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/model/ColumnEntity.java
  65. 27 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/model/TableEntity.java
  66. 26 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/service/SysGeneratorService.java
  67. 80 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/service/impl/SysGeneratorServiceImpl.java
  68. 35 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/utils/DateUtils.java
  69. 220 0
      shoulder-generator/src/main/java/cn/itlym/shoulder/generator/utils/GenUtils.java
  70. 15 0
      shoulder-generator/src/main/resources/bootstrap.yml
  71. 31 0
      shoulder-generator/src/main/resources/generator.properties
  72. 76 0
      shoulder-generator/src/main/resources/template/Controller.java.vm
  73. 27 0
      shoulder-generator/src/main/resources/template/Dao.java.vm
  74. 62 0
      shoulder-generator/src/main/resources/template/Dao.xml.vm
  75. 31 0
      shoulder-generator/src/main/resources/template/Entity.java.vm
  76. 43 0
      shoulder-generator/src/main/resources/template/Service.java.vm
  77. 66 0
      shoulder-generator/src/main/resources/template/ServiceImpl.java.vm
  78. 169 0
      shoulder-generator/src/main/resources/template/index.html.vm
  79. 28 0
      shoulder-notify-center/README.md
  80. BIN
      shoulder-notify-center/arch.png
  81. 56 0
      shoulder-notify-center/email.md
  82. 22 0
      shoulder-notify-center/pom.xml
  83. 8 0
      shoulder-notify-center/shoulder-sms/README.md
  84. 48 0
      shoulder-notify-center/shoulder-sms/pom.xml
  85. 33 0
      shoulder-notify-center/shoulder-sms/shoulder-sms-client/pom.xml
  86. 11 0
      shoulder-notify-center/shoulder-sms/shoulder-sms-client/src/main/java/cn/itlym/shoulder/platform/notify/sms/sdk/client/SmsClient.java
  87. 7 0
      shoulder-notify-center/shoulder-sms/shoulder-sms-client/src/main/java/cn/itlym/shoulder/platform/notify/sms/sdk/package-info.java
  88. 16 0
      shoulder-notify-center/shoulder-sms/sms-api/pom.xml
  89. 89 0
      shoulder-notify-center/shoulder-sms/sms-center/pom.xml
  90. 31 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/SmsCenterStarter.java
  91. 19 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/config/JmsConfig.java
  92. 41 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/constant/EmailTemplateTypeEnum.java
  93. 46 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/EmailController.java
  94. 48 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/SmsController.java
  95. 32 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/TestController.java
  96. 74 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/dto/EmailDTO.java
  97. 54 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/dto/UiResult.java
  98. 33 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/entity/EmailContentTemplate.java
  99. 116 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/entity/EmailEntity.java
  100. 37 0
      shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/EnumConverter.java

+ 16 - 0
.gitee/ISSUE_TEMPLATE.zh-CN.md

@@ -0,0 +1,16 @@
+### 该问题是怎么引起的?
+
+
+
+### 重现步骤
+
+
+
+### 报错信息
+
+
+### 尝试过的解决手段
+
+
+
+

+ 15 - 0
.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md

@@ -0,0 +1,15 @@
+### 相关的Issue
+
+
+### 原因(目的、解决的问题等)
+
+
+### 描述(做了什么,变更了什么)
+
+
+### 测试用例(新增、改动、可能影响的功能)
+
+
+
+
+

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# IDEA
+.idea
+*.iml
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+target
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*

+ 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 [yyyy] [name of copyright owner]
+
+   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.

+ 195 - 0
README.md

@@ -0,0 +1,195 @@
+# shoulder-platform
+
+[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/ChinaLym/Shoulder-Platform)
+[![](https://img.shields.io/badge/Author-lym-blue.svg)](https://github.com/ChinaLym)
+[![](https://img.shields.io/badge/version-1.0-brightgreen.svg)](https://github.com/ChinaLym/Shoulder-Platform)
+[![GitHub stars](https://img.shields.io/github/stars/ChinaLym/Shoulder-Framework.svg?style=social&label=Stars)](https://github.com/ChinaLym/Shoulder-Platform/stargazers)
+[![GitHub forks](https://img.shields.io/github/forks/ChinaLym/Shoulder-Framework.svg?style=social&label=Fork)](https://github.com/ChinaLym/Shoulder-Framework/network/members)
+
+
+## 简介:
+
+`shoulder-platform` 是一个 `SaaS` 平台(仅实现基础能力,不包含具体业务),代码简洁,架构清晰,非常适合学习使用。
+
+## 架构图
+
+![架构图.png](img/architecture.png)
+
+在线工具:[https://app.diagrams.net/](https://app.diagrams.net/) [https://processon.com/](https://processon.com/)
+
+## 在线预览
+
+- [开发规范地址](http://spec.itlym.cn)
+- [Grafana + Prometheus 监控系统](http://grafana.itlym.cn)(访客账号密码:`shoulder` / `shoulder123`,仅包含仪表盘查看权限)
+- [EFK 日志系统](http://kibana.itlym.cn)(访客账号密码:`shoulder` / `shoulder123`,仅包含日志检索查看权限)
+- 平台地址( 开发中 )
+- ~~[zipkin 链路追踪系统](http://zipkin.itlym.cn)(暂时下线)~~
+
+# 能力介绍
+
+- 能力概览
+	- 单点登录
+    - 用户管理
+	- 资源权限管理
+    - 通知推送中心(短信、邮件)
+    - 错误码中心(查询错误码,大概产生原因,解决措施)
+    - 知识库(记录常见问题排查方式等)
+    - 在线 api 文档中心
+    
+- 核心框架
+    - `SpringBoot` 
+    - `SpringCloud`
+    - `Shoulder Framework`
+    - 服务认证: Spring Security(Oauth、JWT)
+
+- 微服务治理方案选型
+    - 服务注册、服务发现: nacos
+    - 服务调用: feign + 负载均衡: Ribbon / Dubbo
+    - 限流 & 断路器: Sentinel
+    - 配置中心:nacos
+    - 消息通知
+        - rabbitMQ、KafKa
+    - 文件存储
+        - ceph、OSS..
+    - 分布式任务调度
+        - Power Job
+    - 分布式事务
+        - Seata
+    - 数据同步
+        - canal
+    - 监控
+        - 集群监控:spring-boot-actuator + spring-boot-admin
+        - 服务监控:sentinel
+        - 链路追踪:zipkin/Skywalking(根据部署机器性能选择)
+        - 指标监控:metrics + exporter + prometheus + grafana
+        - 主机监控、容器监控:cAdvisor
+        - 告警:alertManager
+        - 日志监控 EFK(Elastic Search + Fluentd + Kibana)
+    - 持续集成、持续部署(不限制)
+        - 版本控制:Git
+        - 接口文档:openApi3
+        - 代码审查:Sonar
+        - 自动测试:AutoTest
+        - 持续集成:Maven、Jenkins、Drone
+        - 部署:Docker、K8s
+        - 发布方式:金丝雀发布、蓝绿发布、灰度发布(Ribbon)
+    - 数据智能
+        - ETL:
+        - 数据处理:Flink、Google Data Flow、Beam
+   
+
+- 认证中心
+    - 单点登录
+    - 会话管理
+    - 授权管理
+    
+- 用户中心
+    - 用户、组织、人事管理
+    - 租户管理
+    
+- 权限中心
+    - 菜单权限
+    - 角色权限
+    - 岗位管理
+    - 资源管理
+    - 应用管理
+    
+- 消息推送
+    - 短信
+    - 邮件
+    - 钉钉
+    - 企业微信
+    - App(第三方)
+
+- 存储中心
+    - 本地文件
+        - 结合数据库、本地文件路径
+    - 自建存储系统
+        - **minio**、FastDFS、Hadoop、GDFS等
+    - 第三方OOS存储
+        - **七牛云、阿里云、亚马逊云**、腾讯云、华为云
+    
+- 平台
+    - 用户平台
+    - 监控门户
+    - 运维平台
+    - 运营平台(后台管理)
+    
+- 网关
+    - Web 浏览器
+    - H5 小程序
+    - App
+    - OpenApi
+    - 静态资源
+    
+## 启动与使用
+
+- IDEA
+- jar
+- docker
+    
+## 如果觉得对您有帮助,请点右上角 "Star" 支持一下吧,谢谢!
+
+## 文档
+
+
+## 展示
+
+#### 监控
+
+[监控系统预览地址](http://grafana.itlym.cn)(访客账号密码:`shoulder` / `shoulder123`,演示账号仅包含仪表盘查看权限,不能编辑)
+
+![主机监控](img/host.png)
+
+![prometheus + grafana 监控 docker](img/docker.png)
+
+![监控redis](img/redis.png)
+
+![nacos1](img/nacos1.png)
+
+![nacos2](img/nacos2.png)
+
+![mysql1](img/mysql1.png)
+
+![mysql2](img/mysql2.png)
+
+![mysql3](img/mysql3.png)
+
+
+#### 日志收集
+
+ELK展示nginx日志演示
+
+查看所有访问 grafana.itlym.cn 的访问日志
+
+![ELK展示nginx日志,演示过滤访问 grafana.itlym.cn 的记录](img/elk-nginx.png)
+
+查看所有请求时间大于 200ms 的访问日志
+
+![ELK展示nginx日志,演示过滤访问 grafana.itlym.cn 的记录](img/elk-nginx.png)
+
+
+## 项目代码地址
+
+| 项目 | 开源地址 | 说明 |
+|---|---|---|
+| Shoulder Framework | [github](https://github.com/ChinaLym/Shoulder-Framework)、[gitee](https://gitee.com/ChinaLym/shoulder-framework) | 开发框架,在 Spring Boot 基础之上,结合[软件优雅设计与开发最佳实践](http://spec.itlym.cn),增加常用的功能,任何基于`Spring Boot`/`Spring Cloud`的项目都可以使用。 |
+| shoulder-framework-demo | [github](https://github.com/ChinaLym/shoulder-framework-demo)、[gitee](https://gitee.com/ChinaLym/shoulder-framework) | 以简单的例子介绍 `Shoulder Framework` 的使用 |
+| shoulder-plugins | [github](https://github.com/ChinaLym/shoulder-plugins)、[gitee](https://gitee.com/ChinaLym/shoulder-plugins) | shoulder 提供的的减少开发工作量的`maven`插件(非必须,如遵循[软件优雅设计与开发最佳实践-国际化开发](http://doc.itlym.cn/specs/base/i18n.html)时推荐希望使用自动生成多语言翻译资源文件的插件减少开发工作量) |
+| shoulder-lombok | [github](https://github.com/ChinaLym/shoulder-lombok)、[gitee](https://gitee.com/ChinaLym/shoulder-lombok) | 在`lombok`之上,增加 `@SLog` 注解,用于简化[软件优雅设计与开发最佳实践-错误码与日志](http://spec.itlym.cn/specs/base/errorCode.html) -shoulder 实现的日志框架的使用(非必须) |
+| shoulder-lombok-idea-plugin | [github](https://github.com/ChinaLym/lombok-intellij-plugin)、[gitee](https://gitee.com/ChinaLym/lombok-intellij-plugin) | 在 `lombok-idea-plugin`之上,在 IDEA 中增加`@SLog`的编码提示,以更好的使用 `shoulder-lombok`(非必须,使用 shoulder-lombok 时推荐) |
+| **Shoulder Platform** | [github](https://github.com/ChinaLym/Shoulder-Platform)、[gitee](https://gitee.com/ChinaLym/shoulder-Platform) | SaaS 开发平台,提供了基础通用能力,与具体业务无关 |
+| Shoulder iPaaS | [github](https://github.com/ChinaLym/shoulder-iPaaS)、[gitee](https://gitee.com/ChinaLym/shoulder-iPaaS) | iPaaS 平台,介绍了常见中间件、监控系统、私有基础平台如何部署 |
+
+## 层次设计
+
+
+| 层次 | 定位 | 方案 | Shoulder 支持 |
+|---|---|---|---|
+| 业务应用服务 `SaaS` | 面向用户设计,更应该考虑如何方便用户 | 使用者根据实际业务把握 | `shoulder-framework` 提供了一些常用的能力,以及规约的对接;`shoulder-platform-common` 提供了快速开发一个与 `shoulder-platform` 设计、技术、风格统一的应用服务 |
+| 平台对接开发包 `SDK` | 降低使用者调用 `shoulder` 的开发成本和难度 | 以 Spring Boot 自动装配形式提供,包含使用文档和Demo | 提供对接 shoulder-platform的默认实现,使用者也可根据平台api接口文档自行实现 |
+| 共性业务层 `aPaaS` | 通用基础功能如认证、注册、授权、通知推送、知识库、错误码查询等 | api网关、web管理平台、用户中心、通知中心 |  |
+| 开发脚手架 `工具` | 统一维护共性代码,提供常用能力如异常拦截、错误码、安全加密等,统一管理技术和依赖版本 | `spring boot`、`spring cloud`、`shoulder-framework`、`shoulder-platform-common` 等 | 提供一些常用的功能封装,**可直接用于任何项目** |
+| 软件开发设计理论指导 `理论` | 软件开发设计理论指导,主要为了系统的易维护、易扩展、易观测、安全性 | 总结业界开发设计实践经验如 `阿里巴巴Java开发规范` 结合而成,详见[优雅软件设计规范](http://spec.itlym.cn) | shoulder给予了一定的理论指导,但这是**可选的**,不强制使用者必须遵循 |
+| 软件平台基础层 `iPaaS` | 无业务含义的基础中间件,数据库、消息队列、监控中间件、告警中间件等 | MySql、RabbitMQ、Nacos、Zipkin、ElasticSearch、Docker、K8s 等,以 `Docker` 镜像方式提供 | 提供大部分场景的最佳技术方案选型,安装、部署、参数调优方案,**可直接用于任何项目** |
+| 硬件基础层 `IaaS` | 硬件支撑,如CPU、内存、网络、存储等 | 依赖云主机厂商,如阿里云、腾讯云、亚马逊云等 | 无,shoulder不干涉该层 |

+ 109 - 0
banner-font.txt

@@ -0,0 +1,109 @@
+- http://patorjk.com/software/taag/#p=display&f=Ivrit&t=shoulder
+- http://www.network-science.de/ascii/
+
+---
+
+Ivrit
+
+  ____  _                 _     _              ____       _
+ / ___|| |__   ___  _   _| | __| | ___ _ __   / ___| __ _| |_ _____      ____ _ _   _
+ \___ \| '_ \ / _ \| | | | |/ _` |/ _ \ '__| | |  _ / _` | __/ _ \ \ /\ / / _` | | | |
+  ___) | | | | (_) | |_| | | (_| |  __/ |    | |_| | (_| | ||  __/\ V  V / (_| | |_| |
+ |____/|_| |_|\___/ \__,_|_|\__,_|\___|_|     \____|\__,_|\__\___| \_/\_/ \__,_|\__, |
+                                                                                |___/
+
+Rounded
+  ______ _                 _     _                 ______
+ / _____) |               | |   | |              / ______)        _
+( (____ | |__   ___  _   _| | __| |_____  ____   | |  ___ _____ _| |_ _____ _ _ _ _____ _   _
+ \____ \|  _ \ / _ \| | | | |/ _  | ___ |/ ___)  | | (_  (____ (_   _) ___ | | | (____ | | | |
+ _____) ) | | | |_| | |_| | ( (_| | ____| |      | |___) / ___ | | |_| ____| | | / ___ | |_| |
+(______/|_| |_|\___/|____/ \_)____|_____)_|       \_____/\_____|  \__)_____)\___/\_____|\__  |
+                                                                                       (____/
+                                                                               |___/
+
+Slant
+   _____ __                __    __             ______      __
+  / ___// /_  ____  __  __/ /___/ /__  _____   / ____/___ _/ /____ _      ______ ___  __
+  \__ \/ __ \/ __ \/ / / / / __  / _ \/ ___/  / / __/ __ `/ __/ _ \ | /| / / __ `/ / / /
+ ___/ / / / / /_/ / /_/ / / /_/ /  __/ /     / /_/ / /_/ / /_/  __/ |/ |/ / /_/ / /_/ /
+/____/_/ /_/\____/\__,_/_/\__,_/\___/_/      \____/\__,_/\__/\___/|__/|__/\__,_/\__, /
+                                                                               /____/
+
+Doom
+ _____ _                 _     _             _____       _
+/  ___| |               | |   | |           |  __ \     | |
+\ `--.| |__   ___  _   _| | __| | ___ _ __  | |  \/ __ _| |_ _____      ____ _ _   _
+ `--. \ '_ \ / _ \| | | | |/ _` |/ _ \ '__| | | __ / _` | __/ _ \ \ /\ / / _` | | | |
+/\__/ / | | | (_) | |_| | | (_| |  __/ |    | |_\ \ (_| | ||  __/\ V  V / (_| | |_| |
+\____/|_| |_|\___/ \__,_|_|\__,_|\___|_|     \____/\__,_|\__\___| \_/\_/ \__,_|\__, |
+                                                                                __/ |
+
+Big Money-ne
+  /$$$$$$  /$$                           /$$       /$$                            /$$$$$$              /$$
+ /$$__  $$| $$                          | $$      | $$                           /$$__  $$            | $$
+| $$  \__/| $$$$$$$   /$$$$$$  /$$   /$$| $$  /$$$$$$$  /$$$$$$   /$$$$$$       | $$  \__/  /$$$$$$  /$$$$$$    /$$$$$$  /$$  /$$  /$$  /$$$$$$  /$$   /$$
+|  $$$$$$ | $$__  $$ /$$__  $$| $$  | $$| $$ /$$__  $$ /$$__  $$ /$$__  $$      | $$ /$$$$ |____  $$|_  $$_/   /$$__  $$| $$ | $$ | $$ |____  $$| $$  | $$
+ \____  $$| $$  \ $$| $$  \ $$| $$  | $$| $$| $$  | $$| $$$$$$$$| $$  \__/      | $$|_  $$  /$$$$$$$  | $$    | $$$$$$$$| $$ | $$ | $$  /$$$$$$$| $$  | $$
+ /$$  \ $$| $$  | $$| $$  | $$| $$  | $$| $$| $$  | $$| $$_____/| $$            | $$  \ $$ /$$__  $$  | $$ /$$| $$_____/| $$ | $$ | $$ /$$__  $$| $$  | $$
+|  $$$$$$/| $$  | $$|  $$$$$$/|  $$$$$$/| $$|  $$$$$$$|  $$$$$$$| $$            |  $$$$$$/|  $$$$$$$  |  $$$$/|  $$$$$$$|  $$$$$/$$$$/|  $$$$$$$|  $$$$$$$
+ \______/ |__/  |__/ \______/  \______/ |__/ \_______/ \_______/|__/             \______/  \_______/   \___/   \_______/ \_____/\___/  \_______/ \____  $$
+                                                                                                                                                 /$$  | $$
+                                                                                                                                                |  $$$$$$/
+                                                                                                                                                 \______/
+
+
+Sub-Zero
+ ______     __  __     ______     __  __     __         _____     ______     ______        ______     ______     ______   ______     __     __     ______     __  __
+/\  ___\   /\ \_\ \   /\  __ \   /\ \/\ \   /\ \       /\  __-.  /\  ___\   /\  == \      /\  ___\   /\  __ \   /\__  _\ /\  ___\   /\ \  _ \ \   /\  __ \   /\ \_\ \
+\ \___  \  \ \  __ \  \ \ \/\ \  \ \ \_\ \  \ \ \____  \ \ \/\ \ \ \  __\   \ \  __<      \ \ \__ \  \ \  __ \  \/_/\ \/ \ \  __\   \ \ \/ ".\ \  \ \  __ \  \ \____ \
+ \/\_____\  \ \_\ \_\  \ \_____\  \ \_____\  \ \_____\  \ \____-  \ \_____\  \ \_\ \_\     \ \_____\  \ \_\ \_\    \ \_\  \ \_____\  \ \__/".~\_\  \ \_\ \_\  \/\_____\
+  \/_____/   \/_/\/_/   \/_____/   \/_____/   \/_____/   \/____/   \/_____/   \/_/ /_/      \/_____/   \/_/\/_/     \/_/   \/_____/   \/_/   \/_/   \/_/\/_/   \/_____/
+
+
+
+3D-ASCII
+ ________  ___  ___  ________  ___  ___  ___       ________  _______   ________          ________  ________  _________  _______   ___       __   ________      ___    ___
+|\   ____\|\  \|\  \|\   __  \|\  \|\  \|\  \     |\   ___ \|\  ___ \ |\   __  \        |\   ____\|\   __  \|\___   ___\\  ___ \ |\  \     |\  \|\   __  \    |\  \  /  /|
+\ \  \___|\ \  \\\  \ \  \|\  \ \  \\\  \ \  \    \ \  \_|\ \ \   __/|\ \  \|\  \       \ \  \___|\ \  \|\  \|___ \  \_\ \   __/|\ \  \    \ \  \ \  \|\  \   \ \  \/  / /
+ \ \_____  \ \   __  \ \  \\\  \ \  \\\  \ \  \    \ \  \ \\ \ \  \_|/_\ \   _  _\       \ \  \  __\ \   __  \   \ \  \ \ \  \_|/_\ \  \  __\ \  \ \   __  \   \ \    / /
+  \|____|\  \ \  \ \  \ \  \\\  \ \  \\\  \ \  \____\ \  \_\\ \ \  \_|\ \ \  \\  \|       \ \  \|\  \ \  \ \  \   \ \  \ \ \  \_|\ \ \  \|\__\_\  \ \  \ \  \   \/  /  /
+    ____\_\  \ \__\ \__\ \_______\ \_______\ \_______\ \_______\ \_______\ \__\\ _\        \ \_______\ \__\ \__\   \ \__\ \ \_______\ \____________\ \__\ \__\__/  / /
+   |\_________\|__|\|__|\|_______|\|_______|\|_______|\|_______|\|_______|\|__|\|__|        \|_______|\|__|\|__|    \|__|  \|_______|\|____________|\|__|\|__|\___/ /
+   \|_________|                                                                                                                                              \|___|/
+
+Larry 3D
+ ____    __                       ___       __                     ____              __
+/\  _`\ /\ \                     /\_ \     /\ \                   /\  _`\           /\ \__
+\ \,\L\_\ \ \___     ___   __  __\//\ \    \_\ \     __   _ __    \ \ \L\_\     __  \ \ ,_\    __   __  __  __     __     __  __
+ \/_\__ \\ \  _ `\  / __`\/\ \/\ \ \ \ \   /'_` \  /'__`\/\`'__\   \ \ \L_L   /'__`\ \ \ \/  /'__`\/\ \/\ \/\ \  /'__`\  /\ \/\ \
+   /\ \L\ \ \ \ \ \/\ \L\ \ \ \_\ \ \_\ \_/\ \L\ \/\  __/\ \ \/     \ \ \/, \/\ \L\.\_\ \ \_/\  __/\ \ \_/ \_/ \/\ \L\.\_\ \ \_\ \
+   \ `\____\ \_\ \_\ \____/\ \____/ /\____\ \___,_\ \____\\ \_\      \ \____/\ \__/.\_\\ \__\ \____\\ \___x___/'\ \__/.\_\\/`____ \
+    \/_____/\/_/\/_/\/___/  \/___/  \/____/\/__,_ /\/____/ \/_/       \/___/  \/__/\/_/ \/__/\/____/ \/__//__/   \/__/\/_/ `/___/> \
+                                                                                                                              /\___/
+
+
+Alpha
+
+          _____                    _____                   _______                   _____                    _____            _____                    _____                    _____                            _____                    _____                _____                    _____                    _____                    _____                _____
+         /\    \                  /\    \                 /::\    \                 /\    \                  /\    \          /\    \                  /\    \                  /\    \                          /\    \                  /\    \              /\    \                  /\    \                  /\    \                  /\    \              |\    \
+        /::\    \                /::\____\               /::::\    \               /::\____\                /::\____\        /::\    \                /::\    \                /::\    \                        /::\    \                /::\    \            /::\    \                /::\    \                /::\____\                /::\    \             |:\____\
+       /::::\    \              /:::/    /              /::::::\    \             /:::/    /               /:::/    /       /::::\    \              /::::\    \              /::::\    \                      /::::\    \              /::::\    \           \:::\    \              /::::\    \              /:::/    /               /::::\    \            |::|   |
+      /::::::\    \            /:::/    /              /::::::::\    \           /:::/    /               /:::/    /       /::::::\    \            /::::::\    \            /::::::\    \                    /::::::\    \            /::::::\    \           \:::\    \            /::::::\    \            /:::/   _/___            /::::::\    \           |::|   |
+     /:::/\:::\    \          /:::/    /              /:::/~~\:::\    \         /:::/    /               /:::/    /       /:::/\:::\    \          /:::/\:::\    \          /:::/\:::\    \                  /:::/\:::\    \          /:::/\:::\    \           \:::\    \          /:::/\:::\    \          /:::/   /\    \          /:::/\:::\    \          |::|   |
+    /:::/__\:::\    \        /:::/____/              /:::/    \:::\    \       /:::/    /               /:::/    /       /:::/  \:::\    \        /:::/__\:::\    \        /:::/__\:::\    \                /:::/  \:::\    \        /:::/__\:::\    \           \:::\    \        /:::/__\:::\    \        /:::/   /::\____\        /:::/__\:::\    \         |::|   |
+    \:::\   \:::\    \      /::::\    \             /:::/    / \:::\    \     /:::/    /               /:::/    /       /:::/    \:::\    \      /::::\   \:::\    \      /::::\   \:::\    \              /:::/    \:::\    \      /::::\   \:::\    \          /::::\    \      /::::\   \:::\    \      /:::/   /:::/    /       /::::\   \:::\    \        |::|   |
+  ___\:::\   \:::\    \    /::::::\    \   _____   /:::/____/   \:::\____\   /:::/    /      _____    /:::/    /       /:::/    / \:::\    \    /::::::\   \:::\    \    /::::::\   \:::\    \            /:::/    / \:::\    \    /::::::\   \:::\    \        /::::::\    \    /::::::\   \:::\    \    /:::/   /:::/   _/___    /::::::\   \:::\    \       |::|___|______
+ /\   \:::\   \:::\    \  /:::/\:::\    \ /\    \ |:::|    |     |:::|    | /:::/____/      /\    \  /:::/    /       /:::/    /   \:::\ ___\  /:::/\:::\   \:::\    \  /:::/\:::\   \:::\____\          /:::/    /   \:::\ ___\  /:::/\:::\   \:::\    \      /:::/\:::\    \  /:::/\:::\   \:::\    \  /:::/___/:::/   /\    \  /:::/\:::\   \:::\    \      /::::::::\    \
+/::\   \:::\   \:::\____\/:::/  \:::\    /::\____\|:::|____|     |:::|    ||:::|    /      /::\____\/:::/____/       /:::/____/     \:::|    |/:::/__\:::\   \:::\____\/:::/  \:::\   \:::|    |        /:::/____/  ___\:::|    |/:::/  \:::\   \:::\____\    /:::/  \:::\____\/:::/__\:::\   \:::\____\|:::|   /:::/   /::\____\/:::/  \:::\   \:::\____\    /::::::::::\____\
+\:::\   \:::\   \::/    /\::/    \:::\  /:::/    / \:::\    \   /:::/    / |:::|____\     /:::/    /\:::\    \       \:::\    \     /:::|____|\:::\   \:::\   \::/    /\::/   |::::\  /:::|____|        \:::\    \ /\  /:::|____|\::/    \:::\  /:::/    /   /:::/    \::/    /\:::\   \:::\   \::/    /|:::|__/:::/   /:::/    /\::/    \:::\  /:::/    /   /:::/~~~~/~~
+ \:::\   \:::\   \/____/  \/____/ \:::\/:::/    /   \:::\    \ /:::/    /   \:::\    \   /:::/    /  \:::\    \       \:::\    \   /:::/    /  \:::\   \:::\   \/____/  \/____|:::::\/:::/    /          \:::\    /::\ \::/    /  \/____/ \:::\/:::/    /   /:::/    / \/____/  \:::\   \:::\   \/____/  \:::\/:::/   /:::/    /  \/____/ \:::\/:::/    /   /:::/    /
+  \:::\   \:::\    \               \::::::/    /     \:::\    /:::/    /     \:::\    \ /:::/    /    \:::\    \       \:::\    \ /:::/    /    \:::\   \:::\    \            |:::::::::/    /            \:::\   \:::\ \/____/            \::::::/    /   /:::/    /            \:::\   \:::\    \       \::::::/   /:::/    /            \::::::/    /   /:::/    /
+   \:::\   \:::\____\               \::::/    /       \:::\__/:::/    /       \:::\    /:::/    /      \:::\    \       \:::\    /:::/    /      \:::\   \:::\____\           |::|\::::/    /              \:::\   \:::\____\               \::::/    /   /:::/    /              \:::\   \:::\____\       \::::/___/:::/    /              \::::/    /   /:::/    /
+    \:::\  /:::/    /               /:::/    /         \::::::::/    /         \:::\__/:::/    /        \:::\    \       \:::\  /:::/    /        \:::\   \::/    /           |::| \::/____/                \:::\  /:::/    /               /:::/    /    \::/    /                \:::\   \::/    /        \:::\__/:::/    /               /:::/    /    \::/    /
+     \:::\/:::/    /               /:::/    /           \::::::/    /           \::::::::/    /          \:::\    \       \:::\/:::/    /          \:::\   \/____/            |::|  ~|                       \:::\/:::/    /               /:::/    /      \/____/                  \:::\   \/____/          \::::::::/    /               /:::/    /      \/____/
+      \::::::/    /               /:::/    /             \::::/    /             \::::::/    /            \:::\    \       \::::::/    /            \:::\    \                |::|   |                        \::::::/    /               /:::/    /                                 \:::\    \               \::::::/    /               /:::/    /
+       \::::/    /               /:::/    /               \::/____/               \::::/    /              \:::\____\       \::::/    /              \:::\____\               \::|   |                         \::::/    /               /:::/    /                                   \:::\____\               \::::/    /               /:::/    /
+        \::/    /                \::/    /                 ~~                      \::/____/                \::/    /        \::/____/                \::/    /                \:|   |                          \::/____/                \::/    /                                     \::/    /                \::/____/                \::/    /
+         \/____/                  \/____/                                           ~~                       \/____/          ~~                       \/____/                  \|___|                                                    \/____/                                       \/____/                  ~~                       \/____/
+

+ 6 - 0
doc/README.MD

@@ -0,0 +1,6 @@
+需要放到 NACOS 里的应用配置信息
+
+appConfig 中的配置文件信息是在启动时读取的信息,
+实际中这些信息应放到 nacos 中维护并在 mysql 持久化备份,
+这里为了将其一并开源,特地放在这里
+注意,配置文件中,ip、端口号、账号密码需要修改为自己的

+ 9 - 0
doc/appConfig/boot-admin.yml

@@ -0,0 +1,9 @@
+boot:
+  admin:
+    client:
+      url: localhost:12365
+      #username:
+      #password:
+      instance:
+        prefer-ip: true
+        service-url: localhost:8080

+ 100 - 0
doc/appConfig/common.yml

@@ -0,0 +1,100 @@
+# 所有服务和环境下都不变的配置
+# 个性化配置:复制本配置到 {服务}-${profiles.active}.yml 文件中进行修改
+
+shoulder:
+  application:
+    id: ${spring.application.name}
+    # errorCodePrefix:  # 每个应用唯一
+    # version: # 从 pom.xml 获取
+    # dateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSS Z" # 默认 yyyy-MM-dd'T'HH:mm:ss.SSS Z
+    defaultLocale: zh_CN
+    charset: UTF-8
+    cluster: false
+
+# 服务器配置
+server:
+  undertow:
+    io-threads: 8         # 线程数, 主要执行非阻塞的任务。推荐与 CPU 核数相同
+    worker-threads: 120   # 阻塞任务线程池, 执行类似servlet请求阻塞操作。推荐为 CPU 核数 * 8
+    buffer-size: 1024     # 用于服务器连接的IO操作,类似netty的池化内存管理。推荐略大于绝大多数请求的大小(根据自己的实际场景决定)
+    direct-buffers: true  # 是否分配的直接内存(堆外内存,避免 GC、复制)。推荐开启
+
+spring:
+  # servlet 配置
+  servlet:
+    multipart:
+      max-file-size: 128MB      # 上传文件最大大小,默认1M
+      max-request-size: 128MB   # 请求最大大小,默认10M
+
+  # http 配置
+  http:
+    encoding:
+      charset: ${shoulder.application.charset}  # 使用统一编码
+      force: true
+      enabled: true
+
+  zipkin:
+    sender:
+      type: RABBIT
+    enabled: ${shoulder.zipkin.enabled}
+    discoveryClientEnabled: true
+    baseUrl: http://localhost:9411/  #http://shoulder-zipkin:8772/
+    compression: # 压缩
+      enabled: true
+    locator: # 通过 nacos 动态获取地址
+      discovery:
+        enabled: true
+    rabbitmq: # 使用指定的队列
+      queue: shoulder_zipkin
+
+  # 采集率,默认 0.1 (记录 10% 的请求,过高会影响性能)
+  sleuth:
+    enabled: ${shoulder.zipkin.enabled}
+    sampler:
+      probability: 1.0
+
+# 健康检查
+management:
+  endpoints:
+    web:
+      base-path: /actuator
+      exposure:
+        include: '*'
+  endpoint:
+    health:
+      show-details: ALWAYS
+      enabled: true
+
+# Feign 配置
+feign:
+  httpclient:
+    enabled: false
+  okhttp:
+    enabled: true
+  hystrix:
+    enabled: true   # 开启熔断机制
+  compression:  # 压缩请求
+    request:
+      enabled: true
+      mime-types: text/xml,application/xml,application/json
+      min-request-size: 2048
+    response:  # 响应压缩
+      enabled: true
+
+# ribbon 配置
+ribbon:
+  httpclient:
+    enabled: false
+  okhttp:
+    enabled: true
+  ReadTimeout: 30000            # 响应流读取超时时间,
+  ConnectTimeout: 30000         # 注意:要小于熔断超时时间,否则将被熔断
+  MaxAutoRetries: 0             # 最大自动重试次数(不切换服务地址)
+  MaxAutoRetriesNextServer: 2   # 最大自动服务地址切换重试次数
+  OkToRetryOnAllOperations: false  #无论是请求超时或者socket read timeout都进行重试,
+
+# 统一日志记录位置
+logging:
+  file:
+    path: /logs
+    name: ${logging.file.path}/${spring.application.name}/${spring.application.name}.log

+ 103 - 0
doc/appConfig/db-mysql.yml

@@ -0,0 +1,103 @@
+# 数据库 配置模板
+
+# 优先从环境变量里取值
+shoulder:
+  database:      # 数据库配置请看DatabaseProperties类上的注释
+    driverClassName: com.mysql.cj.jdbc.Driver
+    conn-schema: jdbc:mysql
+    ip: ${MYSQL_IP:127.0.0.1}
+    port: ${MYSQL_PORT:3306}
+    username: ${MYSQL_USERNAME:root}
+    password: ${MYSQL_PWD:root}
+    addr: ${MYSQL_ADDR:'${shoulder.database.ip}:${shoulder.database.port}'}
+    database: shoulder_database
+    # utf8字符集、+8 时区、使用 unicode、关闭 ssl、自动重连、忽略错误的时间(使用null代替)、单次发送多条语句(分号分隔)
+    conn-param: characterEncoding=utf8&serverTimezone=CTT&useUnicode=true&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
+    url: ${shoulder.database.conn-schema}://${shoulder.database.ip}:${shoulder.database.port}/${shoulder.database.database}?${shoulder.database.conn-param}
+    bizDatabase: shoulder_base
+    multiTenantType: SCHEMA
+    isNotWrite: false
+    isBlockAttack: false  # 是否启用 攻击 SQL 阻断解析器
+    worker-id: 0
+    data-center-id: 0
+
+spring:
+  jpa:
+    database: MYSQL
+    hibernate:
+    #ddl-auto: update
+    properties:
+      hibernate:
+        dialect: org.hibernate.dialect.MySQL8Dialect
+    show-sql: true
+
+  # ============================ 数据库无关的配置 ============================
+  datasource:
+    # 多数据源配置
+    #nameList: beecp
+    # beecp
+    type: cn.beecp.BeeDataSource
+    driverClassName: cn.beecp.BeeDataSource
+    url: ${shoulder.database.url}
+    username: ${shoulder.database.username}
+    password: ${shoulder.database.password}
+
+    druid:
+      username: ${shoulder.database.username}
+      password: ${shoulder.database.password}
+      driver-class-name: ${shoulder.database.driverClassName}
+      url: ${shoulder.database.url}
+      db-type: mysql
+      initialSize: 10
+      minIdle: 10
+      maxActive: 500
+      max-wait: 60000
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      validation-query: SELECT '1'
+      test-on-borrow: false
+      test-on-return: false
+      test-while-idle: true
+      time-between-eviction-runs-millis: 60000  #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+      min-evictable-idle-time-millis: 300000    #配置一个连接在池中最小生存的时间,单位是毫秒
+      filters: stat,wall
+      filter:
+        wall:
+          enabled: true
+          config:
+            commentAllow: true
+            multiStatementAllow: true
+            noneBaseStatementAllow: true
+      web-stat-filter:  # WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
+        enabled: true
+        url-pattern: /*
+        exclusions: "*.js , *.gif ,*.jpg ,*.png ,*.css ,*.ico , /druid/*"
+        session-stat-max-count: 1000
+        profile-enable: true
+        session-stat-enable: false
+      stat-view-servlet:  #展示Druid的统计信息,StatViewServlet的用途包括:1.提供监控信息展示的html页面2.提供监控信息的JSON API
+        enabled: true
+        url-pattern: /druid/*   #根据配置中的url-pattern来访问内置监控页面,如果是上面的配置,内置监控页面的首页是/druid/index.html例如:http://127.0.0.1:9000/druid/index.html
+        reset-enable: true    #允许清空统计数据
+        login-username: shoulder
+        login-password: shoulder
+
+
+mybatis-plus:
+  mapper-locations:
+    - classpath*:mapper_**/**/*Mapper.xml
+  #实体扫描,多个package用逗号或者分号分隔 todo 修改这里
+  typeAliasesPackage: com.github.shoulder.*.entity;com.github.shoulder.database.mybatis.typehandler
+  typeEnumsPackage: com.github.shoulder.*.enumeration
+  global-config:
+    db-config:
+      id-type: INPUT
+      insert-strategy: NOT_NULL
+      update-strategy: NOT_NULL
+      select-strategy: NOT_EMPTY
+  configuration:
+    #配置返回数据库(column下划线命名&&返回java实体是驼峰命名),自动匹配无需as(没开启这个,SQL需要写as: select user_id as userId)
+    map-underscore-to-camel-case: true
+    cache-enabled: false
+    #配置JdbcTypeForNull, oracle数据库必须配置
+    jdbc-type-for-null: 'null'

+ 89 - 0
doc/appConfig/db.yml

@@ -0,0 +1,89 @@
+# mysql 配置模板
+
+# 优先从环境变量里取值
+shoulder:
+  mysql:
+    ip: 127.0.0.1
+    port: 3306
+    driverClassName: com.mysql.cj.jdbc.Driver
+    database: db_shoulder_platform
+    username: root
+    password: root
+  database:      # 数据库配置请看 DatabaseProperties 类上的注释
+    bizDatabase: shoulder_base
+    multiTenantType: SCHEMA
+    isNotWrite: false
+    isBlockAttack: false  # 是否启用 攻击 SQL 阻断解析器
+    worker-id: 0
+    data-center-id: 0
+
+# mysql 通用配置
+spring:
+  datasource:
+    druid:
+      username: ${shoulder.mysql.username}
+      password: ${shoulder.mysql.password}
+      driver-class-name: ${shoulder.mysql.driverClassName}
+      url: jdbc:mysql://${shoulder.mysql.ip}:${shoulder.mysql.port}/${shoulder.mysql.database}?serverTimezone=CTT&characterEncoding=utf8&useUnicode=true&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
+      db-type: mysql
+      initialSize: 10
+      minIdle: 10
+      maxActive: 500
+      max-wait: 60000
+      pool-prepared-statements: true
+      max-pool-prepared-statement-per-connection-size: 20
+      validation-query: SELECT 'x'
+      test-on-borrow: false
+      test-on-return: false
+      test-while-idle: true
+      time-between-eviction-runs-millis: 60000  #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+      min-evictable-idle-time-millis: 300000    #配置一个连接在池中最小生存的时间,单位是毫秒
+      filters: stat,wall
+      filter:
+        wall:
+          enabled: true
+          config:
+            commentAllow: true
+            multiStatementAllow: true
+            noneBaseStatementAllow: true
+      web-stat-filter:  # WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
+        enabled: true
+        url-pattern: /*
+        exclusions: "*.js , *.gif ,*.jpg ,*.png ,*.css ,*.ico , /druid/*"
+        session-stat-max-count: 1000
+        profile-enable: true
+        session-stat-enable: false
+      stat-view-servlet:  #展示Druid的统计信息,StatViewServlet的用途包括:1.提供监控信息展示的html页面2.提供监控信息的JSON API
+        enabled: true
+        url-pattern: /druid/*   #根据配置中的url-pattern来访问内置监控页面,如果是上面的配置,内置监控页面的首页是/druid/index.html例如:http://127.0.0.1:9000/druid/index.html
+        reset-enable: true    #允许清空统计数据
+        login-username: shoulder
+        login-password: shoulder
+
+  jpa:
+    database: MYSQL
+    hibernate:
+    #ddl-auto: update
+    properties:
+      hibernate:
+        dialect: org.hibernate.dialect.MySQL8Dialect
+    show-sql: true
+
+mybatis-plus:
+  mapper-locations:
+    - classpath*:mapper_**/**/*Mapper.xml
+  #实体扫描,多个package用逗号或者分号分隔 todo 修改这里
+  #typeAliasesPackage:
+  #typeEnumsPackage:
+  global-config:
+    db-config:
+      id-type: INPUT
+      insert-strategy: NOT_NULL
+      update-strategy: NOT_NULL
+      select-strategy: NOT_EMPTY
+  configuration:
+    # 下划线(列名)自动转驼峰(java对象属性名)
+    map-underscore-to-camel-case: true
+    cache-enabled: false
+    #配置JdbcTypeForNull, oracle数据库必须配置
+    jdbc-type-for-null: 'null'

+ 13 - 0
doc/appConfig/freemark.yml

@@ -0,0 +1,13 @@
+spring:
+  freemarker:
+    allow-request-override: false
+    cache: false
+    charset: UTF-8
+    check-template-location: true
+    content-type: text/html
+    enabled: true
+    expose-request-attributes: false
+    expose-session-attributes: false
+    expose-spring-macro-helpers: false
+    suffix: .flt
+    template-loader-path: classpath:/static/template/

+ 19 - 0
doc/appConfig/mq-rabbit.yml

@@ -0,0 +1,19 @@
+# rabbitmq 配置模板
+
+# 优先从环境变量里取值
+shoulder:
+  rabbitmq:
+    ip: ${RABBITMQ_IP:127.0.0.1}
+    port: ${RABBITMQ_PORT:5672}
+    username: ${RABBITMQ_USERNAME:shoulder}
+    password: ${RABBITMQ_PASSWORD:shoulder}
+
+spring:
+  rabbitmq:
+    enable: true
+    host: ${shoulder.rabbitmq.ip}
+    port: ${shoulder.rabbitmq.port}
+    username: ${shoulder.rabbitmq.username}
+    password: ${shoulder.rabbitmq.password}
+    listener:
+      type: direct # simple direct

+ 54 - 0
doc/appConfig/redis.yml

@@ -0,0 +1,54 @@
+# redis 配置模板
+
+# 优先从环境变量里取值
+shoulder:
+  redis:
+    ip: ${REDIS_IP:127.0.0.1}
+    port: ${REDIS_PORT:5672}
+    username: ${REDIS_USERNAME:shoulder} # redis6 支持用户名
+    password: ${REDIS_PASSWORD:shoulder}
+    database: 0
+
+spring:
+  cache:
+    type: GENERIC
+  redis:
+    host: ${shoulder.redis.ip}
+    password: ${shoulder.redis.password}
+    port: ${shoulder.redis.port}
+    database: ${shoulder.redis.database}
+
+j2cache:
+  #  config-location: /j2cache.properties
+  open-spring-cache: true
+  cache-clean-mode: passive
+  allow-null-values: true
+  redis-client: lettuce
+  l2-cache-open: true
+  # l2-cache-open: false     # 关闭二级缓存
+  broadcast: net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
+  #  broadcast: jgroups       # 关闭二级缓存
+  L1:
+    provider_class: caffeine
+  L2:
+    provider_class: net.oschina.j2cache.cache.support.redis.SpringRedisProvider
+    config_section: lettuce
+  sync_ttl_to_redis: true
+  default_cache_null_object: false
+  serialization: fst
+caffeine:
+  properties: /j2cache/caffeine.properties   # 这个配置文件需要放在项目中
+lettuce:
+  mode: single
+  namespace: ''
+  storage: generic
+  channel: j2cache
+  scheme: redis
+  hosts: ${shoulder.redis.ip}:${shoulder.redis.port}
+  password: ${shoulder.redis.password}
+  database: ${shoulder.redis.database}
+  sentinelMasterId: ''
+  maxTotal: 100
+  maxIdle: 10
+  minIdle: 10
+  timeout: 10000

+ 88 - 0
doc/appConfig/shoulder-gateway.yml

@@ -0,0 +1,88 @@
+shoulder:
+  log:
+    enabled: false
+
+spring:
+  cloud:
+    gateway:
+      discovery:
+        locator:
+          enabled: true  #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
+          lowerCaseServiceId: true   #是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了),比如以/service-hi/*的请求路径被路由转发到服务名为service-hi的服务上。
+          filters:
+            - StripPrefix=1 # 去掉前缀
+      x-forwarded:
+        prefixEnabled: false
+
+      routes:
+        - id: storage
+          uri: lb://shoulder-storage-center
+          predicates:
+            - Path=/file/**
+          filters:
+            - StripPrefix=1
+            - name: Hystrix
+              args:
+                name: default
+                fallbackUri: 'forward:/fallback'
+
+        - id: authority
+          uri: lb://shoulder-authority-server
+          predicates:
+            - Path=/authority/**
+          filters:
+            - StripPrefix=1
+            - name: Hystrix
+              args:
+                name: default
+                fallbackUri: 'forward:/fallback'
+
+        - id: msgs
+          uri: lb://shoulder-msgs-server
+          predicates:
+            - Path=/msgs/**
+          filters:
+            - StripPrefix=1
+            - name: Hystrix
+              args:
+                name: default
+                fallbackUri: 'forward:/fallback'
+
+        - id: demo
+          uri: lb://shoulder-demo-server
+          predicates:
+            - Path=/demo/**
+          filters:
+            - StripPrefix=1
+            - name: Hystrix
+              args:
+                name: default
+                fallbackUri: 'forward:/fallback'
+
+        - id: order
+          uri: lb://shoulder-order-server
+          predicates:
+            - Path=/order/**
+          filters:
+            - StripPrefix=1
+            - name: Hystrix
+              args:
+                name: default
+                fallbackUri: 'forward:/fallback'
+
+filters:
+  - name: Hystrix
+    args:
+      name: default
+      fallbackUri: 'forward:/fallback'
+
+
+server:
+  port: 8760
+  servlet:
+    context-path: /api  # = server.servlet.context-path
+
+authentication:
+  user:
+    header-name: token
+    pub-key: client/pub.key    # 解密

+ 9 - 0
doc/appConfig/shoulder-sms-center.yml

@@ -0,0 +1,9 @@
+server:
+  port: 8768
+
+test:
+  sms:
+    phoneNumber: 15858193327
+    template-code: SMS_184220232
+  email:
+    receiver-address: 1730398492@qq.com

+ 48 - 0
doc/appConfig/shoulder-storage-center.yml

@@ -0,0 +1,48 @@
+shoulder:
+  nginx:
+    ip: ${spring.cloud.client.ip-address}   # 正式环境需要将该ip设置成nginx对应的 公网ip
+    port: 10000                             # 正式环境需要将该ip设置成nginx对应的 公网端口
+  swagger:
+    enabled: true
+    docket:
+      file:
+        title: 存储服务
+        base-package: cn.itlym.shoulder.platform.storage.controller
+      general:
+        title: 通用模块
+        base-package: cn.itlym.shoulder.common.controller
+  file:
+    type: LOCAL # FAST_DFS LOCAL
+    storage-path: /data/projects/uploadfile/file/     # 文件存储路径  ( 某些版本的 window 需要改成  D:\\data\\projects\\uploadfile\\file\\  )
+    uriPrefix: http://${shoulder.nginx.ip}:${shoulder.nginx.port}/file/   # 文件访问 需要通过这个uri前缀进行访问
+    inner-uri-prefix: null  #  内网的url前缀
+    down-by-id: http://${shoulder.nginx.ip}:${shoulder.nginx.port}/api/file/attachment/download?ids[]=%s
+    down-by-biz-id: http://${shoulder.nginx.ip}:${shoulder.nginx.port}/api/file/attachment/download/biz?bizIds[]=%s
+    down-by-url: http://${shoulder.nginx.ip}:${shoulder.nginx.port}/api/file/attachment/download/url?url=%s&filename=%s
+    ali:
+      # 请填写自己的阿里云存储配置
+      uriPrefix: http://test.oss-cn-shenzhen.aliyuncs.com/
+      bucket-name: test
+      endpoint: http://oss-cn-shenzhen.aliyuncs.com
+      access-key-id: test
+      access-key-secret: test
+
+#FAST_DFS配置
+fdfs:
+  soTimeout: 1500
+  connectTimeout: 600
+  thumb-image:
+    width: 150
+    height: 150
+  tracker-list:
+    - 127.0.0.1:12345
+  pool:
+    #从池中借出的对象的最大数目
+    max-total: 128
+    max-wait-millis: 100
+    jmx-name-base: 1
+    jmx-name-prefix: 1
+
+
+server:
+  port: 10000

+ 21 - 0
doc/appConfig/third-account.yml

@@ -0,0 +1,21 @@
+# 第三方开发者账号信息,如对接阿里云、腾讯云、微信登陆等信息
+
+ali-cloud:
+  access-key-id: changeme
+  access-secret: changeme
+
+spring:
+  mail:
+    username: changeme
+    host: smtp.qq.com
+    password: changeme
+    properties:
+      mail:
+        smtp:
+          auth: true
+          connectiontimeout: 5000
+          timeout: 3000
+          writetimeout: 5000
+          starttls:
+            enable: true
+            required: true

+ 11 - 0
doc/nacos.md

@@ -0,0 +1,11 @@
+# NACOS 使用说明
+
+### NACOS namespace 和 groupId 使用
+
+`namespace` 默认值为 `Public`、`groupId` 默认值为 `DEFAULT_GROUP`
+
+环境隔离方案:
+- [NACOS 官方给的方案](https://nacos.io/zh-cn/blog/namespace-endpoint-best-practices.html)
+- [其他方案](https://www.cnblogs.com/larscheng/p/11411423.html)
+
+

+ 7 - 0
doc/sentinel.yml

@@ -0,0 +1,7 @@
+spring:
+  cloud:
+    sentinel:
+      filter:
+        enabled: false
+      transport:
+        dashboard: localhost:8080

+ 1 - 0
dynamicConfig/README.MD

@@ -0,0 +1 @@
+maven 多环境打包 配置信息自动切换

+ 7 - 0
dynamicConfig/config-dev.properties

@@ -0,0 +1,7 @@
+# 注意:value的结尾部分不要有空格,maven-resources-plugin 不会自动去除
+nacos.ip=nacos.itlym.cn
+nacos.port=8848
+# 多租户标识,默认就是 public。主要用于隔离不同环境,如 dev、test。注意:nacos1.4前不要设置任何值(如 public),见 github 3460
+nacos.namespace=
+# nacos.group 默认为 DEFAULT_GROUP,主要用于隔离不同环境,如 pay、notPay。NACOS 目前仅留了该扩展点,实际还不支持
+seata.namespace=

+ 4 - 0
dynamicConfig/config-prod.properties

@@ -0,0 +1,4 @@
+nacos.ip=127.0.0.1
+nacos.port=8848
+nacos.namespace=
+seata.namespace=

BIN
img/architecture.png


BIN
img/docker.png


BIN
img/elk-nginx.png


BIN
img/elk-nginx2.png


BIN
img/host.png


BIN
img/mysql1.png


BIN
img/mysql2.png


BIN
img/mysql3.png


BIN
img/nacos1.png


BIN
img/nacos2.png


BIN
img/redis.png


+ 25 - 0
pom.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.itlym.platform</groupId>
+    <artifactId>shoulder-platform</artifactId>
+    <packaging>pom</packaging>
+    <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+
+    <modules>
+        <module>shoulder-platform-common</module>
+        <module>shoulder-gateway</module>
+        <module>shoulder-auth-center</module>
+        <module>shoulder-notify-center</module>
+        <module>shoulder-pay-center</module>
+        <module>shoulder-storage-center</module>
+        <module>shoulder-system-center</module>
+        <module>shoulder-backstage</module>
+        <module>shoulder-generator</module>
+    </modules>
+
+
+</project>

+ 30 - 0
shoulder-auth-center/README.md

@@ -0,0 +1,30 @@
+# shoulder-platform
+
+USER-CENTER
+
+用户中心,提供 注册、登录、注销、RBAC 权限管理、认证、授权
+
+支持单点登录,Oauth2 给第三方授权,通过第三方 OIDC认证,通过第三方 Oauth2 登录
+
+提供管理租户、appKey 等 API
+
+分为以下模块
+
+- Account
+    - 提供账户能力与管理,包含 注册、登录、注销、RBAC 权限管理、认证、授权
+- Authentication
+    - 认证
+- Authority
+    - 授权
+- Audit
+    - 日志审计
+
+
+
+权限相关:
+- [RBAC](https://zhuanlan.zhihu.com/p/98559681)
+- [ACL, DAC, MAC, RBAC, ABAC模型的不同应用场景](https://zhuanlan.zhihu.com/p/70548562)
+- [服务认证与鉴权](https://zhuanlan.zhihu.com/p/101595143)(适合权限数量较少,如OpenAPI)
+- 服务认证:accessToken + refreshToken。前端每10分钟检查一下 refreshToken 过期时间,如果发现即将过期,提前申请 refreshToken
+
+[gitee:六个高Star开源项目,让你更懂OAuth和单点登录](https://zhuanlan.zhihu.com/p/187131269)

+ 24 - 0
shoulder-auth-center/pom.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>cn.itlym.platform</groupId>
+        <artifactId>shoulder-platform-parent</artifactId>
+        <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+        <relativePath>../shoulder-platform-common/shoulder-platform-parent/pom.xml</relativePath>
+    </parent>
+
+    <groupId>cn.itlym.platform</groupId>
+    <artifactId>shoulder-auth-center</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>shoulder-auth-api</module>
+    </modules>
+
+
+</project>

+ 16 - 0
shoulder-auth-center/shoulder-auth-api/pom.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.itlym.platform</groupId>
+        <artifactId>shoulder-platform-parent</artifactId>
+        <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+        <relativePath>../../shoulder-platform-common/shoulder-platform-parent/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>shoulder-auth-api</artifactId>
+
+
+</project>

+ 72 - 0
shoulder-backstage/README.md

@@ -0,0 +1,72 @@
+# shoulder-backstage
+
+后台
+
+- 用户管理
+    - 后台用户管理
+    - 用户组
+    - 锁定、冻结、解锁
+    - 登录、退出
+- ~~组织管理~~
+    - 部门
+    - 地区
+- 权限管理
+    - 权限(接口、界面、按钮、菜单、字段)
+    - 角色
+    - 用户-角色
+- 菜单管理
+    - 跳到哪个url
+    - 提升管理系统的扩展性:跳转到应用个性后台
+- 消息管理
+    - 消息推送记录
+    - 
+- 内容管理
+    - 留言管理
+    - 友情链接
+    - 调查问卷管理
+- 运维体验提升
+    - 错误码统一管理(错误码、含义、建议措施、关联 FAQ 等)
+    - FAQ 文档管理(文档、标签、评论)
+- 应用数据统计
+    - DNU 日新增用户
+    - DAU(Daily Active User)日活跃用户数量
+    - WAU 周活跃用户数量
+    - MAU(monthly active users)月活跃用户人数(重点)
+    - 用户留存
+    - 终端信息统计:手机型号、分辨率
+    - 用户群体与画像:年龄、位置、喜好等
+- 租户管理
+    - 租户
+    - 租户权限
+    - 租户调用记录
+- 运营管理
+    - 虚拟用户/内容系统(避免初期因数据少,留不住用户)
+    - 批量操作系统(一键批量随机点赞、
+- ~~日志管理~~ 【放置于 ELK】
+    - 操作日志,记录对管理系统的操作
+    - 系统日志(linux命令执行记录)
+    - 运行日志(应用运行时产生的日志)
+    - 登录日志。记录管理系统的登录记录 
+- 开放能力管理
+    - 第三方管理:接入、登记
+    - 第三方使用记录
+- 下载中心(可分类增加各种文件)
+    - 对接文档等
+- 广告管理
+    - 广告商
+    - 展示位置、大小、优先级、目标用户
+    - 定期展示
+- 系统设置
+    - 后台系统参数设置
+    - 应用系统参数设置:图标,节日图标
+    - 企业信息
+    - 新闻
+    - 语言
+- 应用系统状态监控
+    - 各个服务状态
+    - 业务指标监控
+    - 告警、推送
+- 安全项
+    - 数据备份
+    - 拦截设置
+    - 反爬强度

+ 15 - 0
shoulder-backstage/pom.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>shoulder-platform</artifactId>
+        <groupId>cn.itlym.platform</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>shoulder-backstage</artifactId>
+
+
+</project>

+ 29 - 0
shoulder-gateway/README.md

@@ -0,0 +1,29 @@
+# shoulder-platform-gateway
+
+- 对外网关
+    - api-gateway 开放 api 网关
+    - web-gateway web网关
+    - app-gateway app网关
+    - 注:这些网关用于隔离内部细节,虽然是系统对外服务的门面,但一般也不会直接暴露于外部网络,而是位于 nginx 等代理服务器之后,系统服务之前。
+
+- 对内网关
+    - biz-gateway 业务网关
+    - storage-gateway 对象存储,统一存储
+    - third-api-gateway 调用第三方接口过该网关,按照业务重要程度选择性单独部署
+
+
+           
+                              i.        
+                            .`   i.      
+                          .`       `i      
+                     ,.·`            i      
+                .··`                  i     
+           .··``           Spring     1     
+         ·`                 ./        i     
+        :                .//          i     
+       (              ,.//           i      
+       :            .##`           ,:      
+        `·       .##i`           .:       
+           `:.###:,           ,·`        
+     .#.  :###::`   `--....-`         
+     `·`  ```    

+ 31 - 0
shoulder-gateway/pom.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>pom</packaging>
+
+    <parent>
+        <groupId>cn.itlym.platform</groupId>
+        <artifactId>shoulder-platform-parent</artifactId>
+        <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+        <relativePath>../shoulder-platform-common/shoulder-platform-parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>shoulder-gateway</artifactId>
+    <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+
+    <modules>
+        <module>shoulder-api-gateway</module>
+        <module>shoulder-storage-gateway</module>
+    </modules>
+
+
+    <properties>
+        <shoulder.version>0.4-SNAPSHOT</shoulder.version><!-- shoulder-version -->
+        <spring-boot.version>2.2.8.RELEASE</spring-boot.version>
+        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
+        <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
+    </properties>
+
+</project>
+

+ 10 - 0
shoulder-gateway/shoulder-api-gateway/README.md

@@ -0,0 +1,10 @@
+# shoulder-api-gateway
+
+
+
+接入层网关:安全、限流、日志、监控、缓存(业务无关)
+
+API 服务网关:超时、缓存、熔断、重试、查询聚合、数据校验(时间、方法、版本、AppKey、签名)
+【贴近业务】
+
+实际应用中,也可以根据自己的设计,将代理网关的能力赋予 API gateway 中。

+ 76 - 0
shoulder-gateway/shoulder-api-gateway/pom.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cn.itlym.platform</groupId>
+        <artifactId>shoulder-platform-parent</artifactId>
+        <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+        <relativePath>../../shoulder-platform-common/shoulder-platform-parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>shoulder-api-gateway</artifactId>
+    <description>Shoulder 网关,支持跨域认证,追踪,限流,接口发布与撤销</description>
+    <name>${project.artifactId}</name>
+
+    <properties>
+        <shoulder.version>0.4-SNAPSHOT</shoulder.version><!-- shoulder-version -->
+        <spring-boot.version>2.2.8.RELEASE</spring-boot.version>
+        <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
+        <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.itlym</groupId>
+            <artifactId>shoulder-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-gateway</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <!-- 服务注册与发现 -->
+        <dependency>
+            <groupId>cn.itlym.platform</groupId>
+            <artifactId>shoulder-platform-starter-discovery-client</artifactId>
+            <version>${shoulder-platform.version}</version>
+        </dependency>
+
+        <!-- 配置动态刷新 -->
+        <dependency>
+            <groupId>cn.itlym.platform</groupId>
+            <artifactId>shoulder-platform-starter-config-client</artifactId>
+            <version>${shoulder-platform.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <!-- maven 默认使用 artifactId+version拼接 -->
+        <finalName>${project.artifactId}</finalName>
+
+        <!-- 动态打包环境配置源文件 -->
+        <filters>
+            <filter>../../dynamicConfig/config-${profile.active}.properties</filter>
+        </filters>
+
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+
+    </build>
+
+</project>

+ 27 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/ShoulderApiGateway.java

@@ -0,0 +1,27 @@
+package cn.itlym.shoulder.platform.gateway;
+
+import org.shoulder.core.dto.response.RestResult;
+import org.shoulder.core.exception.CommonErrorCodeEnum;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.web.bind.annotation.RequestMapping;
+import reactor.core.publisher.Mono;
+
+/**
+ * 网关启动类
+ *
+ * @author lym
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+public class ShoulderApiGateway {
+    public static void main(String[] args) {
+        SpringApplication.run(ShoulderApiGateway.class, args);
+    }
+
+    @RequestMapping("/fallback")
+    public Mono<RestResult> fallback() {
+        return Mono.just(RestResult.error(CommonErrorCodeEnum.REQUEST_TIMEOUT));
+    }
+}

+ 30 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/ServiceTokenClient.java

@@ -0,0 +1,30 @@
+package cn.itlym.shoulder.platform.gateway.client;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * st 相关
+ *
+ * @author lym
+ */
+public interface ServiceTokenClient {
+
+    /**
+     * 通过 accessToken 获取内部 st
+     *
+     * @param accessToken Oauth2 accessToken
+     * @param appId       接入方应用标识
+     * @return serviceToken
+     */
+    Mono<String> getServiceToken(String accessToken, String appId);
+
+
+    /**
+     * 删除 serviceToken
+     *
+     * @param serviceToken st
+     * @return void
+     */
+    Mono<Void> deleteServiceToken(String serviceToken);
+
+}

+ 39 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/dto/param/AccessToken2ServiceTokenParam.java

@@ -0,0 +1,39 @@
+package cn.itlym.shoulder.platform.gateway.client.dto.param;
+
+import javax.validation.constraints.NotEmpty;
+
+/**
+ * @author lym
+ */
+public class AccessToken2ServiceTokenParam {
+
+    @NotEmpty
+    private String accessToken;
+
+    @NotEmpty
+    private String appId;
+
+    public AccessToken2ServiceTokenParam() {
+    }
+
+    public AccessToken2ServiceTokenParam(String accessToken, String appId) {
+        this.accessToken = accessToken;
+        this.appId = appId;
+    }
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+}

+ 27 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/dto/param/DeleteServiceTokenParam.java

@@ -0,0 +1,27 @@
+package cn.itlym.shoulder.platform.gateway.client.dto.param;
+
+import javax.validation.constraints.NotEmpty;
+
+/**
+ * @author lym
+ */
+public class DeleteServiceTokenParam {
+
+    @NotEmpty
+    private String st;
+
+    public DeleteServiceTokenParam() {
+    }
+
+    public DeleteServiceTokenParam(String st) {
+        this.st = st;
+    }
+
+    public String getSt() {
+        return st;
+    }
+
+    public void setSt(String st) {
+        this.st = st;
+    }
+}

+ 83 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/impl/ServiceTokenClientImpl.java

@@ -0,0 +1,83 @@
+package cn.itlym.shoulder.platform.gateway.client.impl;
+
+import cn.itlym.shoulder.platform.gateway.client.ServiceTokenClient;
+import cn.itlym.shoulder.platform.gateway.client.dto.param.DeleteServiceTokenParam;
+import lombok.extern.slf4j.Slf4j;
+import org.shoulder.core.dto.response.RestResult;
+import org.shoulder.core.exception.BaseRuntimeException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+
+/**
+ * 调用访问管理服务 ST 接口实现
+ *
+ * @author lym
+ */
+@Slf4j
+@Service
+public class ServiceTokenClientImpl implements ServiceTokenClient {
+
+
+    /**
+     * 使用 accessToken 换取 serviceToken 接口路径
+     */
+    private static final String GET_ST_BY_ACCESS_TOKEN_URI = "/api/authentication/st/get?accessToken=%s&appId=%s";
+    /**
+     * 删除 ST 接口路径
+     */
+    private static final String DELETE_ST_URI = "/api/authentication/st/delete";
+    @Autowired
+    private RouteDefinitionLocator locator;
+    private WebClient webClient;
+
+    /**
+     * todo 注入地址
+     *
+     * @param accessManagerServiceUrl
+     */
+    public ServiceTokenClientImpl(@Value("xxx") String accessManagerServiceUrl) {
+        webClient = WebClient.create(accessManagerServiceUrl);
+    }
+
+    @Override
+    public Mono<String> getServiceToken(String accessToken, String appId) {
+        return webClient
+                .post().uri(String.format(GET_ST_BY_ACCESS_TOKEN_URI, accessToken, appId))
+                .accept(APPLICATION_JSON)
+                .retrieve()
+                .bodyToMono(RestResult.class)
+                .map(RestResult::getData)
+                .cast(Map.class)
+                .map(map -> map.get("st"))
+                .cast(String.class)
+
+                .onErrorResume(e -> {
+                    log.warn("get ST fail by [token=" + accessToken + ",appId=" + appId + "]");
+                    throw new BaseRuntimeException("get ST fail", e);
+                });
+    }
+
+    @Override
+    public Mono<Void> deleteServiceToken(String serviceToken) {
+        DeleteServiceTokenParam deleteServiceTokenParam = new DeleteServiceTokenParam();
+        deleteServiceTokenParam.setSt(serviceToken);
+
+        return webClient.post().uri(DELETE_ST_URI)
+                .bodyValue(deleteServiceTokenParam)
+                .retrieve()
+                .bodyToFlux(DataBuffer.class)
+                .map(DataBufferUtils::release)
+                .then();
+    }
+
+}

+ 45 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/config/CorsConfig.java

@@ -0,0 +1,45 @@
+package cn.itlym.shoulder.platform.gateway.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.reactive.CorsWebFilter;
+import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
+import org.springframework.web.util.pattern.PathPatternParser;
+
+/**
+ * 处理跨域请求
+ * 简单跨域: GET,HEAD、以及部分 POST请求(请求头的"Content-Type"为 application/x-www-form-urlencoded、multipart/form-data、text/plain
+ * 其他跨域请求会在实际发送前进行一次 OPTIONS 探测请求
+ *
+ * @author lym
+ */
+@Configuration(
+        proxyBeanMethods = false
+)
+public class CorsConfig {
+
+    @Bean
+    public CorsWebFilter corsFilter() {
+        final String all = "*";
+
+        CorsConfiguration config = new CorsConfiguration();
+        // 是否允许请求带有验证信息 cookie跨域
+        config.setAllowCredentials(Boolean.TRUE);
+        // 允许访问的客户端域名
+        config.addAllowedOrigin(all);
+        // 允许服务端访问的客户端请求头
+        config.addAllowedHeader(all);
+        // 允许访问的方法名,GET POST等
+        config.addAllowedMethod(all);
+        // 允许前端js访问自定义响应头
+        //config.addExposedHeader("setToken");
+
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
+        source.registerCorsConfiguration("/**", config);
+
+        return new CorsWebFilter(source);
+    }
+
+
+}

+ 147 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/config/JsonExceptionHandler.java

@@ -0,0 +1,147 @@
+package cn.itlym.shoulder.platform.gateway.config;
+
+import cn.itlym.shoulder.platform.gateway.ex.ShoulderGatewayException;
+import lombok.extern.shoulder.SLog;
+import org.shoulder.core.dto.response.RestResult;
+import org.shoulder.core.exception.ErrorCode;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.web.ResourceProperties;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
+import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration;
+import org.springframework.boot.web.reactive.error.ErrorAttributes;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.codec.ServerCodecConfigurer;
+import org.springframework.lang.NonNull;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.server.*;
+import org.springframework.web.reactive.result.view.ViewResolver;
+import org.springframework.web.server.ResponseStatusException;
+import reactor.core.publisher.Mono;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 异常 json 化处理,并返回 shoulder 定义的统一返回值类型
+ *
+ * @author lym
+ */
+@SLog
+@Configuration(
+        proxyBeanMethods = false
+)
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
+
+    private static final String ATTRIBUTE_NAME_HTTP_STATUS = "httpStatus";
+
+    private static final String ATTRIBUTE_NAME_RESPONSE = "shoulderResponseBody";
+
+    /**
+     * 默认异常,错误码,500
+     */
+    private static final int DEFAULT_SERVER_ERROR_HTTP_STATUS = HttpStatus.INTERNAL_SERVER_ERROR.value();
+
+    /**
+     * @see ErrorWebFluxAutoConfiguration#errorWebExceptionHandler
+     */
+    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
+                                ServerProperties serverProperties, ApplicationContext applicationContext,
+                                ObjectProvider<ViewResolver> viewResolvers,
+                                ServerCodecConfigurer serverCodecConfigurer) {
+
+        super(errorAttributes, resourceProperties, serverProperties.getError(), applicationContext);
+        super.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
+        super.setMessageWriters(serverCodecConfigurer.getWriters());
+        super.setMessageReaders(serverCodecConfigurer.getReaders());
+    }
+
+    /**
+     * 构建返回的JSON数据格式
+     *
+     * @param httpStatus http 状态码
+     * @param response   如何响应
+     * @return {"httpStatus": 500, "shoulderResponseBody": {"code":"xxx", "msg":"xxx", "data":xxx}}
+     */
+    public static Map<String, Object> buildErrorAttributes(int httpStatus, RestResult response) {
+        Map<String, Object> map = new HashMap<>(2);
+        map.put(ATTRIBUTE_NAME_HTTP_STATUS, httpStatus);
+        map.put(ATTRIBUTE_NAME_RESPONSE, response);
+        return map;
+    }
+
+    /**
+     * 获取异常属性
+     * 为了安全,不打印堆栈信息,即使使用者手动开启
+     */
+    @Override
+    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
+        int httpStatus = DEFAULT_SERVER_ERROR_HTTP_STATUS;
+        Throwable error = super.getError(request);
+        if (error instanceof ResponseStatusException) {
+            // spring 定义的 http 异常
+            httpStatus = ((ResponseStatusException) error).getStatus().value();
+        } else if (error instanceof ErrorCode) {
+            // shoulder 定义的异常
+            ErrorCode errorCode = (ErrorCode) error;
+            httpStatus = errorCode.getHttpStatusCode().value();
+        }
+        return buildErrorAttributes(httpStatus, this.buildResponse(request, error));
+    }
+
+    /**
+     * 指定响应处理方法为JSON处理的方法
+     *
+     * @param errorAttributes 错误的属性
+     */
+    @Override
+    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
+        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
+    }
+
+    /**
+     * Render the error information as a JSON payload.
+     *
+     * @param request the current request
+     * @return a {@code Publisher} of the HTTP response
+     * @see super#renderErrorResponse
+     */
+    @NonNull
+    @Override
+    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
+        boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
+        Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
+        return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
+                .body(BodyInserters.fromValue(error));
+    }
+
+    /**
+     * 根据code获取对应的HttpStatus
+     *
+     * @param errorAttributes 错误的属性
+     */
+    @Override
+    protected int getHttpStatus(Map<String, Object> errorAttributes) {
+        return (int) errorAttributes.get(ATTRIBUTE_NAME_HTTP_STATUS);
+    }
+
+    /**
+     * 构建异常信息
+     *
+     * @param request 请求
+     * @param ex      异常
+     * @return 异常信息
+     */
+    protected RestResult buildResponse(ServerRequest request, Throwable ex) {
+        ShoulderGatewayException exception = new ShoulderGatewayException(request, ex);
+        log.error(exception);
+        return RestResult.error(exception);
+    }
+}

+ 31 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/ex/ShoulderGatewayException.java

@@ -0,0 +1,31 @@
+package cn.itlym.shoulder.platform.gateway.ex;
+
+import org.shoulder.core.exception.BaseRuntimeException;
+import org.shoulder.core.exception.CommonErrorCodeEnum;
+import org.shoulder.core.exception.ErrorCode;
+import org.springframework.web.reactive.function.server.ServerRequest;
+
+/**
+ * 网关异常
+ *
+ * @author lym
+ */
+public class ShoulderGatewayException extends BaseRuntimeException {
+
+    public ShoulderGatewayException(ServerRequest request, Throwable ex) {
+        super(CommonErrorCodeEnum.UNKNOWN.getCode(), new StringBuilder("Failed to handle request [")
+                .append(request.methodName())
+                .append(" ")
+                .append(request.uri())
+                .append("] ")
+                .append(ex.getMessage())
+                .toString(), ex);
+        if (ex instanceof ErrorCode) {
+            ErrorCode errorCode = (ErrorCode) ex;
+            setHttpStatus(errorCode.getHttpStatusCode());
+            setCode(errorCode.getCode());
+        }
+
+    }
+
+}

+ 104 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/filter/AuthenticationGlobalFilter.java

@@ -0,0 +1,104 @@
+package cn.itlym.shoulder.platform.gateway.filter;
+
+import cn.itlym.shoulder.platform.gateway.client.ServiceTokenClient;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+/**
+ * 认证过滤器
+ * 请求头是否有 accessToken、如果有,则将他换成内部的 ST(JWT token),请求结束后,将 ST 过期
+ *
+ * @author lym
+ */
+@Slf4j
+@Component
+public class AuthenticationGlobalFilter implements GlobalFilter, Ordered {
+
+    /**
+     * ST
+     */
+    public static final String SERVICE_TOKEN_ATTRIBUTE = qualify("serviceToken");
+    private static final String ACCESS_TOKEN_IN_HEADER = "Authorization";
+    private static final String APP_ID_IN_HEADER = "appId";
+    private static final String SERVICE_TOKEN_IN_HEADER = "ST";
+    private ServiceTokenClient stClient;
+    private List<String> ignorePath;
+    private AntPathMatcher pathMatcher = new AntPathMatcher();
+
+    public AuthenticationGlobalFilter(ServiceTokenClient stClient, List<String> ignorePath) {
+        this.stClient = stClient;
+        this.ignorePath = ignorePath;
+    }
+
+    /**
+     * 按照 spring 的方式来格式化上下文 key 格式
+     */
+    private static String qualify(String attr) {
+        return AuthenticationGlobalFilter.class.getName() + "." + attr;
+    }
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        // 开发阶段,debug 模式优化
+        if ("true".equals(System.getProperty("permitAll"))) {
+            String check = exchange.getRequest().getHeaders().getFirst("check");
+            if (StringUtils.isEmpty(check)) {
+                return chain.filter(exchange);
+            }
+        }
+
+        String accessToken = exchange.getRequest().getHeaders().getFirst(ACCESS_TOKEN_IN_HEADER);
+
+        if (StringUtils.isEmpty(accessToken)) {
+            String aimPath = exchange.getRequest().getPath().value();
+            // ignorePath
+            if (ignorePath.contains(aimPath)) {
+                log.debug("allow: " + aimPath);
+                return chain.filter(exchange);
+            }
+            // access denied if missing accessToken
+            log.info("deny for accessToken is empty: " + aimPath);
+            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
+            return exchange.getResponse().setComplete();
+        }
+        // preFilter: add header(ST)
+
+        String appId = exchange.getRequest().getHeaders().getFirst(APP_ID_IN_HEADER);
+
+        return stClient.getServiceToken(accessToken, appId)
+                // 请求前,换取 ST
+                .map(st -> {
+                    exchange.getAttributes().put(SERVICE_TOKEN_ATTRIBUTE, st);
+                    ServerHttpRequest request =
+                            exchange.getRequest().mutate().header(SERVICE_TOKEN_IN_HEADER, st).build();
+                    return exchange.mutate().request(request).build();
+                })
+
+
+                .flatMap(chain::filter)
+
+                // 响应后,删除 ST
+                .then(Mono.just(exchange)).map(e -> {
+                    String st = (String) exchange.getAttributes().get(SERVICE_TOKEN_ATTRIBUTE);
+                    return stClient.deleteServiceToken(st);
+                }).then();
+
+    }
+
+    @Override
+    public int getOrder() {
+        return 0;
+    }
+
+}

+ 35 - 0
shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/util/RequestUtil.java

@@ -0,0 +1,35 @@
+package cn.itlym.shoulder.platform.gateway.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import reactor.core.publisher.Flux;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * react 工具类
+ *
+ * @author lym
+ */
+public class RequestUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(RequestUtil.class);
+
+    private static String getBodyFromRequest(ServerHttpRequest serverHttpRequest) {
+        //获取请求体
+        Flux<DataBuffer> body = serverHttpRequest.getBody();
+        StringBuilder sb = new StringBuilder();
+        body.subscribe(buffer -> {
+            byte[] bytes = new byte[buffer.readableByteCount()];
+            buffer.read(bytes);
+            DataBufferUtils.release(buffer);
+            String bodyString = new String(bytes, StandardCharsets.UTF_8);
+            sb.append(bodyString);
+        });
+        log.info("请求体内容;{}", sb.toString());
+        return sb.toString();
+    }
+}

+ 11 - 0
shoulder-gateway/shoulder-api-gateway/src/main/resources/banner.txt

@@ -0,0 +1,11 @@
+${AnsiColor.CYAN} ____  _                 _     _           ${AnsiColor.BRIGHT_YELLOW}      _    ____ ___        ____       _                           ${AnsiColor.CYAN} __   __    __
+${AnsiColor.CYAN}/ ___|| |__   ___  _   _| | __| | ___ _ __ ${AnsiColor.BRIGHT_YELLOW}     / \  |  _ \_ _|      / ___| __ _| |_ _____      ____ _ _   _ ${AnsiColor.CYAN} \ \  \ \   \ \
+${AnsiColor.CYAN}\___ \| '_ \ / _ \| | | | |/ _` |/ _ \ '__|${AnsiColor.BRIGHT_YELLOW}    / _ \ | |_) | |_____ | |  _ / _` | __/ _ \ \ /\ / / _` | | | |${AnsiColor.CYAN}  \ \  \ \   \ \
+${AnsiColor.CYAN} ___) | | | | (_) | |_| | | (_| |  __/ |   ${AnsiColor.BRIGHT_YELLOW}   / ___ \|  __/| |_____|| |_| | (_| | ||  __/\ V  V / (_| | |_| |${AnsiColor.CYAN}  / /  / /   / /
+${AnsiColor.CYAN}|____/|_| |_|\___/ \__,_|_|\__,_|\___|_|   ${AnsiColor.BRIGHT_YELLOW}  /_/   \_\_|  |___|      \____|\__,_|\__\___| \_/\_/ \__,_|\__, |${AnsiColor.CYAN} / /  / /   / /
+${AnsiColor.CYAN}=======================================================================================================${AnsiColor.BRIGHT_YELLOW}|___/${AnsiColor.CYAN}=/_/==/_/===/_/
+
+${AnsiColor.BLUE} :: Spring Boot            ::     ${AnsiColor.CYAN}${spring-boot.formatted-version}
+${AnsiColor.BLUE} :: Shoulder-Framework     ::      ${AnsiColor.CYAN}(v@shoulder.version@)
+${AnsiColor.BRIGHT_GREEN} :: @project.artifactId@   ::      ${AnsiColor.GREEN}(v@project.version@)${AnsiColor.CYAN} @project.description@
+${AnsiColor.DEFAULT}

+ 50 - 0
shoulder-gateway/shoulder-api-gateway/src/main/resources/bootstrap.yml

@@ -0,0 +1,50 @@
+# 先从环境变量里取,若不存在,则以 maven 打包时的配置为准
+shoulder:
+  nacos:
+    ip: ${NACOS_IP:itlym.cn}
+    port: ${NACOS_PORT:8848}
+
+# spring-boot-actuate 展示信息
+info:
+  name: "@project.name@"
+  description: "@project.description@"
+  version: "@project.version@"
+  spring-boot-version: "@spring-boot.version@"
+  spring-cloud-version: "@spring-cloud.version@"
+  shoulder-version: "@shoulder.version@"
+  profile: "@profile.active@"
+
+spring:
+  #main:
+  #allow-bean-definition-overriding: true
+  application:
+    name: ${info.name}
+  profiles:
+    active: ${info.profile}
+  cloud:
+    nacos:
+      config:
+        server-addr: ${shoulder.nacos.ip}:${shoulder.nacos.port}
+        file-extension: yml
+        namespace: ${shoulder.nacos.namespace}
+        shared-configs:
+          - dataId: common.yml
+            refresh: true
+          - dataId: redis.yml
+            refresh: false
+          - dataId: db.yml
+            refresh: true
+          - dataId: mq-rabbitmq.yml
+            refresh: false
+        enabled: true
+
+      discovery:
+        server-addr: ${shoulder.nacos.ip}:${shoulder.nacos.port}
+        namespace: ${shoulder.nacos.namespace}
+        metadata:
+          management.context-path: ${server.servlet.context-path:}${spring.mvc.servlet.path:}${management.endpoints.web.base-path:}
+
+logging:
+  file:
+    path: /logs
+    name: ${logging.file.path}/${spring.application.name}/${spring.application.name}.log

+ 5 - 0
shoulder-gateway/shoulder-storage-gateway/README.md

@@ -0,0 +1,5 @@
+# shoulder-storage-gateway
+
+该网关用于屏蔽不同存储方式的差异,为系统内部提供统一的存储能力
+
+可以在内部支持 `Minio`、`Ceph`、`七牛对象存储`、`阿里云对象存储`、`腾讯对象存储`... 

+ 15 - 0
shoulder-gateway/shoulder-storage-gateway/pom.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>shoulder-gateway</artifactId>
+        <groupId>cn.itlym.platform</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>shoulder-storage-gateway</artifactId>
+
+
+</project>

+ 109 - 0
shoulder-generator/pom.xml

@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.itlym.platform</groupId>
+        <artifactId>shoulder-platform-parent</artifactId>
+        <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+        <relativePath>../../shoulder-platform-common/shoulder-platform-parent/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>shoulder-generator</artifactId>
+    <name>代码生成器</name>
+    <description>代码生成器</description>
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+            <version>1.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.itlym.platform</groupId>
+            <artifactId>shoulder-platform-starter-db</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.itlym.platform</groupId>
+            <artifactId>shoulder-platform-starter-config-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-configuration</groupId>
+            <artifactId>commons-configuration</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.itlym.platform</groupId>
+            <artifactId>shoulder-platform-starter-rpc-server</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <!-- 生成带第三方jar包的可执行jar包  -->
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <configuration>
+                    <!-- 项目名 -->
+                    <!-- ${docker.image.prefix} : dockerHub 上注册的名字 -->
+                    <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
+                    <dockerDirectory>src/main/docker</dockerDirectory>
+                    <!-- docker远程服务器地址 -->
+                    <dockerHost>${docker.host}</dockerHost>
+                    <resources>
+                        <resource>
+                            <targetPath>/</targetPath>
+                            <directory>${project.build.directory}</directory>
+                            <include>${project.build.finalName}.jar</include>
+                        </resource>
+                    </resources>
+                </configuration>
+            </plugin>
+        </plugins>
+
+        <finalName>${project.artifactId}</finalName>
+
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.properties</include>
+                    <include>**/*.xml</include>
+                    <include>**/*.yml</include>
+                </includes>
+                <!-- 是否替换资源中的属性-->
+                <filtering>true</filtering>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+            </resource>
+        </resources>
+
+    </build>
+
+</project>

+ 18 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/GeneratorApp.java

@@ -0,0 +1,18 @@
+package cn.itlym.shoulder.generator;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 启动类
+ *
+ * @author lym
+ */
+@Configuration
+@SpringBootApplication
+public class GeneratorApp {
+    public static void main(String[] args) {
+        SpringApplication.run(GeneratorApp.class, args);
+    }
+}

+ 76 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/controller/GeneratorController.java

@@ -0,0 +1,76 @@
+package cn.itlym.shoulder.generator.controller;
+
+import cn.itlym.shoulder.generator.service.SysGeneratorService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.swagger.annotations.Api;
+import org.apache.commons.io.IOUtils;
+import org.shoulder.core.dto.response.ListResult;
+import org.shoulder.core.dto.response.RestResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * @author lym
+ */
+@RestController
+@Api(tags = "代码生成器")
+@RequestMapping("/generator")
+public class GeneratorController {
+
+    @Autowired
+    private SysGeneratorService sysGeneratorService;
+
+    private ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * 列表
+     */
+    @ResponseBody
+    @RequestMapping("/list")
+    public RestResult<ListResult> list(@RequestParam Map<String, Object> params) {
+
+        return RestResult.success(sysGeneratorService.queryList(params));
+    }
+
+    /**
+     * 生成代码
+     * web 中不需要主动关闭流
+     * http://localhost:8080/generator/code?tables=tb_shop
+     */
+    @RequestMapping("/code")
+    public void code(String tables, HttpServletResponse response) throws IOException {
+
+        if (StringUtils.isEmpty(tables)) {
+            throw new IllegalArgumentException("tableName can't be empty");
+        }
+
+        response.reset();
+        byte[] data = sysGeneratorService.generatorCode(tables.split(","), response.getOutputStream());
+        if (data != null && data.length > 0) {
+            /*
+            // file out put stream 必须及时关闭
+            OutputStream out = new FileOutputStream("F:/te.zip");
+            IOUtils.write(data, out);
+            IOUtils.closeQuietly(out);
+            */
+
+            response.setHeader("Content-Disposition", "attachment; filename=\"generator.zip\"");
+            response.setContentType("application/octet-stream; charset=UTF-8");
+            response.addHeader("Content-Length", String.valueOf(data.length));
+
+            // response out put stream 会自动关闭
+            IOUtils.write(data, response.getOutputStream());
+        }
+
+    }
+
+
+}

+ 24 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/dao/SysGeneratorDao.java

@@ -0,0 +1,24 @@
+package cn.itlym.shoulder.generator.dao;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author lym
+ */
+@Mapper
+@Repository
+public interface SysGeneratorDao {
+
+    List<Map<String, Object>> queryList(Map<String, Object> map);
+
+    int queryTotal(Map<String, Object> map);
+
+    Map<String, String> queryTable(String tableName);
+
+    List<Map<String, String>> queryColumns(String tableName);
+
+}

+ 31 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/dao/SysGeneratorDao.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="cn.itlym.shoulder.generator.dao.SysGeneratorDao">
+    <select id="queryList" resultType="map">
+        select table_name tableName, engine, table_comment tableComment, create_time createTime from
+        information_schema.tables
+        where table_schema = (select database())
+        <if test="tableName != null and tableName.trim() != ''">
+            and table_name like concat('%', #{tableName}, '%')
+        </if>
+        order by create_time desc
+    </select>
+
+    <select id="queryTotal" resultType="int">
+        select count(*) from information_schema.tables where table_schema = (select database())
+        <if test="tableName != null and tableName.trim() != ''">
+            and table_name like concat('%', #{tableName}, '%')
+        </if>
+    </select>
+
+    <select id="queryTable" resultType="map">
+        select table_name tableName, engine, table_comment tableComment, create_time createTime from information_schema.tables
+        where table_schema = (select database()) and table_name = #{tableName}
+    </select>
+
+    <select id="queryColumns" resultType="map">
+        select column_name columnName, data_type dataType, column_comment columnComment, column_key columnKey, extra from information_schema.columns
+        where table_name = #{tableName} and table_schema = (select database()) order by ordinal_position
+    </select>
+</mapper>

+ 34 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/model/ColumnEntity.java

@@ -0,0 +1,34 @@
+package cn.itlym.shoulder.generator.model;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author lym
+ */
+@NoArgsConstructor
+@Data
+public class ColumnEntity {
+
+    //列名
+    private String columnName;
+
+    //列名类型
+    private String dataType;
+
+    //列名备注
+    private String comments;
+
+    //属性名称(第一个字母大写),如:user_name => UserName
+    private String attrName;
+
+    //属性名称(第一个字母小写),如:user_name => userName
+    private String attributeName;
+
+    //属性类型
+    private String attrType;
+
+    //auto_increment
+    private String extra;
+
+}

+ 27 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/model/TableEntity.java

@@ -0,0 +1,27 @@
+package cn.itlym.shoulder.generator.model;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author lym
+ */
+@Data
+public class TableEntity {
+
+    //表的名称
+    private String tableName;
+    //表的备注
+    private String comments;
+    //表的主键
+    private ColumnEntity pk;
+    //表的列名(不包含主键)
+    private List<ColumnEntity> columns;
+
+    //类名(第一个字母大写),如:sys_user => SysUser
+    private String className;
+    //类名(第一个字母小写),如:sys_user => sysUser
+    private String lowClassName;
+
+}

+ 26 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/service/SysGeneratorService.java

@@ -0,0 +1,26 @@
+package cn.itlym.shoulder.generator.service;
+
+import org.shoulder.core.dto.response.PageResult;
+import org.springframework.stereotype.Service;
+
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author lym
+ */
+@Service
+public interface SysGeneratorService {
+
+    PageResult queryList(Map<String, Object> map);
+
+    int queryTotal(Map<String, Object> map);
+
+    Map<String, String> queryTable(String tableName);
+
+    List<Map<String, String>> queryColumns(String tableName);
+
+    byte[] generatorCode(String[] tableNames, OutputStream out);
+
+}

+ 80 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/service/impl/SysGeneratorServiceImpl.java

@@ -0,0 +1,80 @@
+package cn.itlym.shoulder.generator.service.impl;
+
+import cn.itlym.shoulder.generator.dao.SysGeneratorDao;
+import cn.itlym.shoulder.generator.service.SysGeneratorService;
+import cn.itlym.shoulder.generator.utils.GenUtils;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import lombok.extern.shoulder.SLog;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.io.IOUtils;
+import org.shoulder.core.dto.response.PageResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author lym
+ */
+@SLog
+@Service
+public class SysGeneratorServiceImpl implements SysGeneratorService {
+
+    @Autowired
+    private SysGeneratorDao sysGeneratorDao;
+
+
+    @Override
+    public PageResult queryList(Map<String, Object> map) {
+        //设置分页信息,分别是当前页数和每页显示的总记录数【记住:必须在mapper接口中的方法执行之前设置该分页信息】
+        PageHelper.startPage(MapUtils.getInteger(map, "page"), MapUtils.getInteger(map, "limit"), true);
+        List<Map<String, Object>> list = sysGeneratorDao.queryList(map);
+        PageInfo<Map<String, Object>> pageInfo = new PageInfo<>(list);
+
+        return PageResult.PageInfoConverter.toResult(pageInfo);
+    }
+
+    @Override
+    public int queryTotal(Map<String, Object> map) {
+        return 0;
+    }
+
+    @Override
+    public Map<String, String> queryTable(String tableName) {
+        return sysGeneratorDao.queryTable(tableName);
+    }
+
+    @Override
+    public List<Map<String, String>> queryColumns(String tableName) {
+        return sysGeneratorDao.queryColumns(tableName);
+    }
+
+    @Override
+    public byte[] generatorCode(String[] tableNames, OutputStream out) {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+
+        for (String tableName : tableNames) {
+            //查询表信息
+            Map<String, String> table = queryTable(tableName);
+            //查询列信息
+            List<Map<String, String>> columns = queryColumns(tableName);
+            if (MapUtils.isEmpty(table) || CollectionUtils.isEmpty(columns)) {
+                log.warn("table {} not exist or without any columns", table);
+                continue;
+            }
+            //生成代码
+            GenUtils.generatorCode(table, columns, zip);
+            IOUtils.closeQuietly(zip);
+        }
+        return outputStream.toByteArray();
+    }
+
+
+}

+ 35 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/utils/DateUtils.java

@@ -0,0 +1,35 @@
+package cn.itlym.shoulder.generator.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 时间工具类
+ *
+ * @author lym
+ */
+public class DateUtils {
+
+    /**
+     * 时间格式(yyyy-MM-dd)
+     */
+    public final static String DATE_PATTERN = "yyyy-MM-dd";
+
+    /**
+     * 时间格式(yyyy-MM-dd HH:mm:ss)
+     */
+    public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+
+    public static String format(Date date) {
+        return format(date, DATE_PATTERN);
+    }
+
+    public static String format(Date date, String pattern) {
+        if (date != null) {
+            SimpleDateFormat df = new SimpleDateFormat(pattern);
+            return df.format(date);
+        }
+        return null;
+    }
+}

+ 220 - 0
shoulder-generator/src/main/java/cn/itlym/shoulder/generator/utils/GenUtils.java

@@ -0,0 +1,220 @@
+package cn.itlym.shoulder.generator.utils;
+
+import cn.itlym.shoulder.generator.model.ColumnEntity;
+import cn.itlym.shoulder.generator.model.TableEntity;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.WordUtils;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * 代码生成器   工具类
+ *
+ * @author lym
+ */
+public class GenUtils {
+
+    public static List<String> getTemplates() {
+        List<String> templates = new ArrayList<String>();
+        templates.add("template/Entity.java.vm");
+        templates.add("template/Dao.java.vm");
+        templates.add("template/Dao.xml.vm");
+        templates.add("template/Service.java.vm");
+        templates.add("template/ServiceImpl.java.vm");
+        templates.add("template/Controller.java.vm");
+
+        templates.add("template/index.html.vm");
+
+        return templates;
+    }
+
+    /**
+     * 生成代码
+     */
+    public static void generatorCode(Map<String, String> table,
+                                     List<Map<String, String>> columns, ZipOutputStream zip) {
+        //配置信息
+        Configuration config = getConfig();
+        boolean hasBigDecimal = false;
+        //表信息
+        TableEntity tableEntity = new TableEntity();
+        tableEntity.setTableName(table.get("tableName"));
+        tableEntity.setComments(table.get("tableComment"));
+        //表名转换成Java类名
+        String className = tableToJava(tableEntity.getTableName(), config.getString("tablePrefix"));
+        tableEntity.setClassName(className);
+        tableEntity.setLowClassName(StringUtils.uncapitalize(className));
+
+        //列信息
+        hasBigDecimal = fillColumnsInfo(columns, config, tableEntity);
+
+        //没主键,则第一个字段为主键
+        if (tableEntity.getPk() == null) {
+            tableEntity.setPk(tableEntity.getColumns().get(0));
+        }
+
+        //设置velocity资源加载器
+        Properties prop = new Properties();
+        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+        Velocity.init(prop);
+        String mainPath = config.getString("mainPath");
+        mainPath = StringUtils.isBlank(mainPath) ? "io.renren" : mainPath;
+        //封装模板数据
+        Map<String, Object> map = new HashMap<>();
+        map.put("tableName", tableEntity.getTableName());
+        map.put("pkgName", className.toLowerCase());
+        map.put("comments", tableEntity.getComments());
+        map.put("pk", tableEntity.getPk());
+        map.put("className", tableEntity.getClassName());
+        map.put("classname", tableEntity.getLowClassName());
+        map.put("pathName", tableEntity.getLowClassName().toLowerCase());
+        map.put("columns", tableEntity.getColumns());
+        map.put("hasBigDecimal", hasBigDecimal);
+        map.put("mainPath", mainPath);
+        map.put("package", config.getString("package"));
+        map.put("moduleName", config.getString("moduleName"));
+        map.put("author", config.getString("author"));
+        map.put("email", config.getString("email"));
+        map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN));
+        VelocityContext context = new VelocityContext(map);
+
+        //获取模板列表
+        List<String> templates = getTemplates();
+        for (String template : templates) {
+            //渲染模板
+            StringWriter sw = new StringWriter();
+            Template tpl = Velocity.getTemplate(template, "UTF-8");
+            tpl.merge(context, sw);
+
+            try {
+                //添加到zip
+                String fileName = getFileName(template, tableEntity.getClassName(), config.getString("package"), tableEntity.getTableName());
+                zip.putNextEntry(new ZipEntry(fileName));
+                IOUtils.write(sw.toString(), zip, "UTF-8");
+                IOUtils.closeQuietly(sw);
+                zip.closeEntry();
+            } catch (IOException e) {
+                throw new RuntimeException("渲染模板失败,表名:" + tableEntity.getTableName(), e);
+            }
+        }
+    }
+
+    private static boolean fillColumnsInfo(List<Map<String, String>> columns, Configuration config, TableEntity tableEntity) {
+        boolean hasBigDecimal = false;
+        List<ColumnEntity> columnsList = new ArrayList<>();
+        for (Map<String, String> column : columns) {
+            ColumnEntity columnEntity = new ColumnEntity();
+            columnEntity.setColumnName(column.get("columnName"));
+            columnEntity.setDataType(column.get("dataType"));
+            columnEntity.setComments(column.get("columnComment"));
+            columnEntity.setExtra(column.get("extra"));
+
+            //列名转换成Java属性名
+            String attrName = columnToJava(columnEntity.getColumnName());
+            columnEntity.setAttrName(attrName);
+            columnEntity.setAttributeName(StringUtils.uncapitalize(attrName));
+
+            //列的数据类型,转换成Java类型
+            String attrType = config.getString(columnEntity.getDataType(), "unknowType");
+            columnEntity.setAttrType(attrType);
+            if (attrType.equals("BigDecimal")) {
+                hasBigDecimal = true;
+            }
+            //是否主键
+            if ("PRI".equalsIgnoreCase(column.get("columnKey")) && tableEntity.getPk() == null) {
+                tableEntity.setPk(columnEntity);
+            }
+
+            columnsList.add(columnEntity);
+        }
+        tableEntity.setColumns(columnsList);
+        return hasBigDecimal;
+    }
+
+
+    /**
+     * 列名转换成Java属性名
+     */
+    public static String columnToJava(String columnName) {
+        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
+    }
+
+    /**
+     * 表名转换成Java类名
+     */
+    public static String tableToJava(String tableName, String tablePrefix) {
+        if (StringUtils.isNotBlank(tablePrefix)) {
+            tableName = tableName.replace(tablePrefix, "");
+        }
+        return columnToJava(tableName);
+    }
+
+    /**
+     * 获取配置信息
+     */
+    public static Configuration getConfig() {
+        try {
+            return new PropertiesConfiguration("generator.properties");
+        } catch (ConfigurationException e) {
+            throw new RuntimeException("获取配置文件失败,", e);
+        }
+    }
+
+    /**
+     * 获取文件名
+     */
+    public static String getFileName(String template, String className, String packageName, String tableName) {
+        String packagePath = "main" + File.separator + "java" + File.separator;
+        tableName = tableName.replace("-", "").replace("_", "").toLowerCase();
+        if (StringUtils.isNotBlank(packageName)) {
+            packagePath += packageName.replace(".", File.separator) + File.separator + tableName + File.separator;
+        }
+
+        if (template.contains("Entity.java.vm")) {
+            return packagePath + "entity" + File.separator + className + ".java";
+        }
+
+        if (template.contains("Dao.java.vm")) {
+            return packagePath + "dao" + File.separator + className + "Dao.java";
+        }
+
+        if (template.contains("Service.java.vm")) {
+            return packagePath + "service" + File.separator + className + "Service.java";
+        }
+
+        if (template.contains("ServiceImpl.java.vm")) {
+            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
+        }
+
+        if (template.contains("Controller.java.vm")) {
+            return packagePath + "controller" + File.separator + className + "Controller.java";
+        }
+
+        if (template.contains("Dao.xml.vm")) {
+            return packagePath + "dao" + File.separator + className + "Dao.xml";
+        }
+
+        if (template.contains("menu.sql.vm")) {
+            return className.toLowerCase() + "_menu.sql";
+        }
+
+        if (template.contains("index.html.vm")) {
+            return "main" + File.separator + "view" + File.separator + "pages" +
+                    File.separator + tableName + File.separator + tableName + ".html";
+        }
+
+        return null;
+    }
+}

+ 15 - 0
shoulder-generator/src/main/resources/bootstrap.yml

@@ -0,0 +1,15 @@
+spring:
+  #allow-bean-definition-overriding: true
+  application:
+    name: generator
+  cloud:
+    nacos:
+      config:
+        server-addr: itlym.cn:8848
+        file-extension: yml
+        shared-configs:
+          - dataId: common.yml
+            refresh: true
+          - dataId: db.yml
+            refresh: true
+        enabled: true

+ 31 - 0
shoulder-generator/src/main/resources/generator.properties

@@ -0,0 +1,31 @@
+#\u4EE3\u7801\u751F\u6210\u5668\uFF0C\u914D\u7F6E\u4FE1\u606F
+mainPath=cn.itlym.shoulder.platform.demo
+#\u5305\u540D
+package=cn.itlym.shoulder.platform.demo
+moduleName=generator
+#\u4F5C\u8005
+author=lym
+#Email
+email=
+#\u8868\u524D\u7F00(\u7C7B\u540D\u4E0D\u4F1A\u5305\u542B\u8868\u524D\u7F00)
+tablePrefix=tb_
+#\u7C7B\u578B\u8F6C\u6362\uFF0C\u914D\u7F6E\u4FE1\u606F
+tinyint=Integer
+smallint=Integer
+mediumint=Integer
+int=Integer
+integer=Integer
+bigint=Long
+float=Float
+double=Double
+decimal=BigDecimal
+bit=Boolean
+char=String
+varchar=String
+tinytext=String
+text=String
+mediumtext=String
+longtext=String
+date=Date
+datetime=Date
+timestamp=Date

+ 76 - 0
shoulder-generator/src/main/resources/template/Controller.java.vm

@@ -0,0 +1,76 @@
+package ${package}.${pkgName}.controller;
+
+import java.util.Map;
+
+import io.swagger.annotations.Api;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.shoulder.core.dto.response.PageResult;
+import org.shoulder.core.dto.response.RestResult;
+
+import ${package}.${pkgName}.entity.${className};
+import ${package}.${pkgName}.service.${className}Service;
+
+/**
+ * ${comments}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+@RestController
+@RequestMapping("${pathName}")
+@Api(tags = "${comments}")
+public class ${className}Controller {
+
+    @Autowired
+    private ${className}Service ${lowClassName}Service;
+
+    /**
+     * 列表
+     */
+    @RequestMapping("/list")
+    @PreAuthorize("hasAnyAuthority('${tableName}:${pathName}:list')")
+    public PageResult list(@RequestParam Map<String, Object> params){
+        PageResult pageResult = ${lowClassName}Service.findAll(params);
+        return pageResult;
+    }
+
+
+    /**
+     * 保存
+     */
+    @RequestMapping("/save")
+    @PreAuthorize("hasAnyAuthority('generator:sysroleuser:save')")
+    public Result save(@RequestBody ${className} ${lowClassName}){
+			${lowClassName}Service.save(${lowClassName});
+
+        return Result.succeed("保存成功");
+    }
+
+    /**
+     * 修改
+     */
+    @RequestMapping("/update")
+    @PreAuthorize("hasAnyAuthority('generator:sysroleuser:update')")
+    public Result update(@RequestBody ${className} ${lowClassName}){
+			${lowClassName}Service.update(${lowClassName});
+
+        return Result.succeed("修改成功");
+    }
+
+    /**
+     * 删除
+     */
+    @RequestMapping("/delete/{id}")
+    @PreAuthorize("hasAnyAuthority('generator:sysroleuser:delete')")
+    public Result delete(@PathVariable Long  ${pk.attrname}){
+			${lowClassName}Service.delete(${pk.attrname});
+        return Result.succeed("删除成功");
+    }
+
+}

+ 27 - 0
shoulder-generator/src/main/resources/template/Dao.java.vm

@@ -0,0 +1,27 @@
+package ${package}.${pkgName}.dao;
+
+import ${package}.${pkgName}.entity.${className};
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+import java.util.Map;
+/**
+ * ${comments}
+ * 
+ * @author ${author}
+ * @date ${datetime}
+ */
+@Mapper
+public interface ${className}Dao  {
+
+    int save(${className} ${lowClassName});
+
+    int update(${className} ${lowClassName});
+
+    int delete(Long id);
+
+    List<${className}> findAll(Map<String, Object> params);
+
+
+
+}

+ 62 - 0
shoulder-generator/src/main/resources/template/Dao.xml.vm

@@ -0,0 +1,62 @@
+<?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="${package}.${pkgName}.dao.${className}Dao">
+
+	<!-- 可根据自己的需求,是否要使用 -->
+    <resultMap type="${package}.${pkgName}.entity.${className}" id="${lowClassName}Map">
+#foreach($column in $columns)
+        <result property="${column.attrname}" column="${column.columnName}"/>
+#end
+    </resultMap>
+
+    <insert id="save">
+        insert into ${tableName}(
+            #foreach($column in $columns)
+                    #if( $!{velocityCount} == $!{columns.size()})
+                        ${column.columnName}
+                    #else
+                        ${column.columnName},
+                    #end
+            #end
+      ) values (
+         #foreach($column in $columns)
+            #if( $!{velocityCount} == $!{columns.size()})
+               #{${column.columnName}}
+               #else
+               #{ ${column.columnName}},
+            #end
+         #end
+      )
+    </insert>
+
+    <update id="update">
+        update ${tableName}
+        <set>
+            #foreach($column in $columns)
+                <if test="${column.columnName} != null">
+                    ${column.columnName} = #{${column.columnName}},
+                </if>
+            #end
+        </set>
+        where id = #{id}
+    </update>
+
+    <delete id="delete" parameterType="long" flushCache="true">
+        delete from ${tableName} where id = #{id}
+    </delete>
+
+    <select id="findAll" resultType="${package}.${pkgName}.entity.${className}">
+        select * from ${tableName} t
+        <include refid="where"/>
+    </select>
+    <sql id="where">
+        <where>
+            #foreach($column in $columns)
+                <if test="searchKey != null and searchKey != '' and searchKey=='${column.columnName}'">
+                    and t.${column.columnName} like concat('%', #{searchValue}, '%')
+                </if>
+            #end
+        </where>
+    </sql>
+</mapper>

+ 31 - 0
shoulder-generator/src/main/resources/template/Entity.java.vm

@@ -0,0 +1,31 @@
+package ${package}.${pkgName}.entity;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+#if(${hasBigDecimal})
+import java.math.BigDecimal;
+#end
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * ${comments}
+ * 
+ * @author ${author}
+ * @date ${datetime}
+ */
+
+@Data
+@NoArgsConstructor
+public class ${className} implements Serializable {
+	private static final long serialVersionUID = 1L;
+
+#foreach ($column in $columns)
+
+	#if($column.columnName == $pk.columnName)
+	#end
+	private $column.attrType $column.attrname;
+#end
+
+}

+ 43 - 0
shoulder-generator/src/main/resources/template/Service.java.vm

@@ -0,0 +1,43 @@
+package ${package}.${pkgName}.service;
+
+import ${package}.${pkgName}.entity.${className};
+
+import org.shoulder.core.dto.response.PageResult;
+
+import java.util.Map;
+
+/**
+ * ${comments}
+ *
+ * @author ${author}
+ * @date ${datetime}
+ */
+public interface ${className}Service {
+    /**
+     * 添加
+     * @param ${lowClassName}
+     */
+    int save(${className} ${lowClassName});
+
+    /**
+     * 修改
+     * @param ${lowClassName}
+     */
+    int update(${className} ${lowClassName});
+
+    /**
+     * 删除
+     * @param id
+     */
+    int delete(Long id);
+
+
+    /**
+     * 列表
+     * @param params
+     * @return
+     */
+    PageResult<${className}> findAll(Map<String, Object> params);
+
+}
+

+ 66 - 0
shoulder-generator/src/main/resources/template/ServiceImpl.java.vm

@@ -0,0 +1,66 @@
+package ${package}.${pkgName}.service.impl;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.shoulder.core.dto.response.PageResult;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.collections4.MapUtils;
+
+import ${package}.${pkgName}.entity.${className};
+import ${package}.${pkgName}.dao.${className}Dao;
+import ${package}.${pkgName}.service.${className}Service;
+
+
+@Service
+public class ${className}ServiceImpl  implements ${className}Service {
+
+    @Autowired
+    private ${className}Dao ${lowClassName}Dao;
+
+    /**
+     * 添加
+     * @param ${lowClassName}
+     */
+    public int save(${className} ${lowClassName}){
+        return ${lowClassName}Dao.save(${lowClassName});
+    }
+
+    /**
+     * 修改
+     * @param ${lowClassName}
+     */
+    public int update(${className} ${lowClassName}){
+        return ${lowClassName}Dao.update(${lowClassName});
+    }
+
+
+    /**
+     * 删除
+     * @param id
+     */
+    public int delete(Long id){
+        return ${lowClassName}Dao.delete(id);
+    }
+
+
+    /**
+     * 列表
+     * @param params
+     * @return
+     */
+    public PageResult<${className}> findAll(Map<String, Object> params){
+        //设置分页信息,分别是当前页数和每页显示的总记录数【记住:必须在mapper接口中的方法执行之前设置该分页信息】
+        if (MapUtils.getInteger(params, "page")!=null && MapUtils.getInteger(params, "limit")!=null)
+            PageHelper.startPage(MapUtils.getInteger(params, "page"),MapUtils.getInteger(params, "limit"),true);
+
+        List<${className}> list  =  ${lowClassName}Dao.findAll(params);
+        PageInfo<${className}> pageInfo = new PageInfo(list);
+
+        return PageResult.<${className}>builder().data(pageInfo.getList()).code(0).count(pageInfo.getTotal()).build();
+    }
+
+}

+ 169 - 0
shoulder-generator/src/main/resources/template/index.html.vm

@@ -0,0 +1,169 @@
+<div class="layui-card">
+    <div class="layui-card-header">
+        <h2 class="header-title">${comments}</h2>
+        <span class="layui-breadcrumb pull-right">
+          <a href="#!console">首页</a>
+          <a><cite>${comments}</cite></a>
+        </span>
+    </div>
+    <div class="layui-card-body">
+        <div class="layui-form toolbar">
+            搜索:
+            <select id="${lowClassName}-search-key">
+                <option value="">-请选择-</option>
+                <option value="name">名称</option>
+            </select>&emsp;
+            <input id="${lowClassName}-edit-value" class="layui-input search-input" type="text" placeholder="输入关键字"/>&emsp;
+            <button id="${lowClassName}-btn-search" class="layui-btn icon-btn"><i class="layui-icon">&#xe615;</i>搜索
+            </button>
+            <button id="${lowClassName}-btn-add" class="layui-btn icon-btn"><i class="layui-icon">&#xe654;</i>添加</button>
+        </div>
+
+        <!-- 数据表格 -->
+        <table class="layui-table" id="${lowClassName}-table" lay-filter="${lowClassName}-table"></table>
+    </div>
+</div>
+
+<!-- 表单弹窗 -->
+<script type="text/html" id="${lowClassName}-model">
+    <form id="${lowClassName}-form" lay-filter="${lowClassName}-form" class="layui-form model-form">
+        #foreach($column in $columns)
+            <div class="layui-form-item">
+                <label class="layui-form-label">${column.comments}</label>
+                <div class="layui-input-block">
+                    <input name="${column.columnName}" placeholder="请输入${column.comments}" type="text"
+                           class="layui-input" maxlength="20"
+                           lay-verify="required" required/>
+                </div>
+            </div>
+        #end
+        <div class="layui-form-item model-form-footer">
+            <button class="layui-btn layui-btn-primary" ew-event="closeDialog" type="button">取消</button>
+            <button class="layui-btn" lay-filter="${lowClassName}-form-submit" lay-submit>保存</button>
+        </div>
+    </form>
+</script>
+
+<!-- 表格操作列 -->
+<script type="text/html" id="${lowClassName}-table-bar">
+    <a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="edit">修改</a>
+    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
+</script>
+
+<script>
+    layui.use(['form', 'table', 'util', 'config', 'admin', 'formSelects'], function () {
+        var form = layui.form;
+        var table = layui.table;
+        var config = layui.config;
+        var layer = layui.layer;
+        var util = layui.util;
+        var admin = layui.admin;
+        var formSelects = layui.formSelects;
+        //渲染表格
+        table.render({
+            elem: '#${lowClassName}-table',
+            url: config.base_server + 'api-${moduleName}/${pathName}/list',
+            where: {
+                access_token: config.getToken().access_token
+            },
+            page: true,
+            cols: [[
+                {type: 'numbers'},
+                {field: 'id', sort: true, title: 'ID', width: 80},
+                #foreach($column in $columns)
+                    {field: '${column.columnName}', sort: true, title: '${column.comments}'},
+                #end
+                {
+                    field: 'createTime', sort: true, templet: function (d) {
+                        return util.toDateString(d.createTime);
+                    }, title: '创建时间'
+                },
+                {align: 'center', toolbar: '#${lowClassName}-table-bar', title: '操作', width: 250}
+            ]]
+        });
+
+        // 添加按钮点击事件
+        $('#${lowClassName}-btn-add').click(function () {
+            showEditModel();
+        });
+
+        // 表单提交事件
+        form.on('submit(${lowClassName}-form-submit)', function (data) {
+            layer.load(2);
+            var url = "";
+            if (data.field.id == '') {
+                url = 'api-${moduleName}/${pathName}/' + 'save';
+            } else {
+                url = 'api-${moduleName}/${pathName}/' + 'update';
+            }
+            admin.req(url, JSON.stringify(data.field), function (data) {
+                layer.closeAll('loading');
+                if (data.resp_code == 0) {
+                    layer.msg(data.resp_msg, {icon: 1, time: 500});
+                    table.reload('${lowClassName}-table');
+                    layer.closeAll('page');
+                } else {
+                    layer.msg(data.resp_msg, {icon: 2, time: 500});
+                }
+            }, $('#${lowClassName}-form').attr('method'));
+            return false;
+        });
+
+        // 工具条点击事件
+        table.on('tool(${lowClassName}-table)', function (obj) {
+            var data = obj.data;
+            if (obj.event === 'edit') { //修改
+                showEditModel(data);
+            } else if (obj.event === 'del') { //删除
+                doDelete(obj);
+            }
+        });
+
+        // 搜索按钮点击事件
+        $('#${lowClassName}-btn-search').click(function () {
+            var key = $('#${lowClassName}-search-key').val();
+            var value = $('#${lowClassName}-edit-value').val();
+            table.reload('${lowClassName}-table', {where: {searchKey: key, searchValue: value}});
+        });
+
+        // 显示编辑弹窗
+        var showEditModel = function (data) {
+            layer.open({
+                type: 1,
+                title: data ? '修改${comments}' : '添加${comments}',
+                area: '450px',
+                offset: '120px',
+                content: $('#${lowClassName}-model').html(),
+                success: function () {
+                    $('#${lowClassName}-form')[0].reset();
+                    $('#${lowClassName}-form').attr('method', 'POST');
+                    if (data) {
+                        $("input[name='code']").attr('disabled', true);
+                        $("input[name='code']").attr('class', "layui-input layui-disabled");
+                        form.val('${lowClassName}-form', data);
+                        $('#${lowClassName}-form').attr('method', 'POST');
+                    }
+                }
+            });
+        };
+
+        // 删除
+        var doDelete = function (obj) {
+            layer.confirm('确定要删除吗?', function (i) {
+                layer.close(i);
+                layer.load(2);
+                admin.req('api-${moduleName}/${pathName}/delete/' + obj.data.id, {}, function (data) {
+                    layer.closeAll('loading');
+                    if (data.resp_code == 0) {
+                        layer.msg(data.resp_msg, {icon: 1, time: 500});
+                        obj.del();
+                    } else {
+                        layer.msg(data.resp_msg, {icon: 2, time: 500});
+                    }
+                }, 'DELETE');
+            });
+        };
+
+    });
+
+</script>

+ 28 - 0
shoulder-notify-center/README.md

@@ -0,0 +1,28 @@
+# shoulder-通知推送中心
+
+包含
+- 邮件推送
+- 短信推送
+- 站内消息推送
+- xx 推送...
+
+
+## 系统架构
+
+如图 ![消息中心架构](arch.png)
+
+改进:
+- 若 `业务服务` 与 `消息中心` 采用 API 方式通信,可以提供`SDK`,简化调用,另推荐在此之上应添加业务网关,对上层业务屏蔽底层实现。
+- 若 `业务服务` 与 `消息中心` 采用 MQ 方式通信,一般来说,无需再使用网关隔离。
+
+
+TODO:考虑将各种推送方式集成,提供单体服务?
+- 降低初学者的理解难度
+- 减少小型场景的资源占用
+
+
+----
+
+
+
+站内消息:https://www.jianshu.com/p/c180e1510639

BIN
shoulder-notify-center/arch.png


+ 56 - 0
shoulder-notify-center/email.md

@@ -0,0 +1,56 @@
+# 邮件推送服务
+
+## 需求分析
+
+希望邮件推送服务能提供的功能
+
+- 可编程、可扩展(通过程序对接、改造)
+    - 能够通过开发在程序逻辑或者管理界面中自动触发发送
+    - 支持邮件发送验证码和邮件营销推送
+    - 能够支持HTML的邮件内容,而HTML内容能够随时随地进行修改,方便美工和开发去调整
+
+- 可观测
+    - 验证类邮件能够支持IP统计、次数统计,能够进行时间限制、防止恶意发送
+    - 推送类邮件能够支持统计发送数量、发送成功率等反馈数据
+
+- 推送效率
+    - 验证邮件要能在5-10秒内发送成功,到达率高
+
+- 用户友好
+    - 用户可以退订推送类邮件
+
+## 功能设计
+
+
+1. 邮件模板模块。通过模板功能来支持HTML邮件内容以及随时可更新替换的要求,通过模板里的关键词参数设计,来达到验证码、用户名、营销内容等动态输入。
+
+2. ~~验证码模块。用来支撑邮件验证码校验、请求限制等功能~~ (由调用方来限制,放入认证服务中)。
+
+3. 记录发送记录相关信息模块。记录所有发送记录,用于统计和分析。
+
+4. 管理后台模块。包括:用户管理、邮件模板配置、发送记录查询等基本支撑功能,在后台尽量以界面化实现管理。
+
+5. 将企业邮局和邮件推送服务分离;企业内外网邮件域隔离。
+以域名 `XXX.com` 为例,一般企业邮局地为`zhangsan@XXX.com`
+邮件服务不适合也以`XXX.com`为域,因为这会和企业邮局服务相互干扰(也不是没有规避方式,只是配置起来很麻烦)
+隔离方案:增加一个二级域名,例如`mail.XXX.com`,邮件服务地址就是 `service@mail.XXX.com`。
+
+-------------
+
+不建议自己搭邮件服务器,十分容易被拦截,即使使用ip池
+
+
+
+----------
+
+参考:https://www.cnblogs.com/chuma/p/5694744.html
+https://www.cnblogs.com/tellerfuliye/articles/13156469.html
+
+
+----
+shoulder 以外的其他方案
+
+https://gitee.com/52itstyle/spring-boot-mail
+
+
+

+ 22 - 0
shoulder-notify-center/pom.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.itlym.platform</groupId>
+        <artifactId>shoulder-platform</artifactId>
+        <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.itlym.platform.notify</groupId>
+    <artifactId>shoulder-notify-center</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>shoulder-sms</module>
+    </modules>
+
+
+</project>

+ 8 - 0
shoulder-notify-center/shoulder-sms/README.md

@@ -0,0 +1,8 @@
+# shoulder-短信推送中心
+
+- sms-api
+    - shoulder-短信推送中心接口、DTO
+- sms-center
+    - 
+- shoulder-sms-client
+    - 快速开发工具包,使用者只需引入该 jar 包,即可获得与 `shoulder-短信推送中心` 通信的能力

+ 48 - 0
shoulder-notify-center/shoulder-sms/pom.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>cn.itlym.platform</groupId>
+        <artifactId>shoulder-platform-parent</artifactId>
+        <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+        <relativePath>../../shoulder-platform-common/shoulder-platform-parent/pom.xml</relativePath>
+    </parent>
+
+    <groupId>cn.itlym.platform.notify</groupId>
+    <artifactId>shoulder-sms</artifactId>
+    <packaging>pom</packaging>
+    <version>1.0-SNAPSHOT</version>
+
+    <modules>
+        <module>sms-center</module>
+        <module>shoulder-sms-client</module>
+        <module>sms-api</module>
+    </modules>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>cn.itlym.platform.notify</groupId>
+                <artifactId>sms-api</artifactId>
+                <version>${shoulder-platform.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>cn.itlym.platform.notify</groupId>
+                <artifactId>sms-center</artifactId>
+                <version>${shoulder-platform.version}</version>
+            </dependency>
+
+            <!-- sdk -->
+            <dependency>
+                <groupId>cn.itlym.platform.notify</groupId>
+                <artifactId>shoulder-sms-client</artifactId>
+                <version>${shoulder-platform.version}</version>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+
+</project>

+ 33 - 0
shoulder-notify-center/shoulder-sms/shoulder-sms-client/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.itlym.platform.notify</groupId>
+        <artifactId>shoulder-sms</artifactId>
+        <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.itlym.platform.notify</groupId>
+    <artifactId>shoulder-sms-client</artifactId>
+    <description>功能:向sms-center发送请求。供微服务内部,其他服务使用</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- IDE tip -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+    </dependencies>
+
+
+</project>

+ 11 - 0
shoulder-notify-center/shoulder-sms/shoulder-sms-client/src/main/java/cn/itlym/shoulder/platform/notify/sms/sdk/client/SmsClient.java

@@ -0,0 +1,11 @@
+package cn.itlym.shoulder.platform.notify.sms.sdk.client;
+
+/**
+ * 供内部服务使用的 client
+ *
+ * @author lym
+ */
+public class SmsClient {
+
+
+}

+ 7 - 0
shoulder-notify-center/shoulder-sms/shoulder-sms-client/src/main/java/cn/itlym/shoulder/platform/notify/sms/sdk/package-info.java

@@ -0,0 +1,7 @@
+/**
+ * 短信推送服务中心的 SDK
+ * 供其他微服务引用,便于调用短信推送服务接口
+ *
+ * @author lym
+ */
+package cn.itlym.shoulder.platform.notify.sms.sdk;

+ 16 - 0
shoulder-notify-center/shoulder-sms/sms-api/pom.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.itlym.platform.notify</groupId>
+        <artifactId>shoulder-sms</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.itlym.platform.notify</groupId>
+    <artifactId>sms-api</artifactId>
+
+
+</project>

+ 89 - 0
shoulder-notify-center/shoulder-sms/sms-center/pom.xml

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>cn.itlym.platform.notify</groupId>
+        <artifactId>shoulder-sms</artifactId>
+        <version>1.0-SNAPSHOT</version><!-- shoulder-platform-version -->
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.itlym.platform.notify</groupId>
+    <artifactId>sms-center</artifactId>
+    <name>${artifactId}</name>
+    <description>Shoulder平台-短信推送服务</description>
+
+    <properties>
+        <javax.mail.version>1.6.2</javax.mail.version>
+    </properties>
+
+    <dependencies>
+        <!-- sms-api -->
+        <dependency>
+            <groupId>cn.itlym.platform.notify</groupId>
+            <artifactId>sms-api</artifactId>
+        </dependency>
+        <!-- aliyun-sms -->
+        <dependency>
+            <groupId>cn.itlym.platform</groupId>
+            <artifactId>shoulder-sms-aliyun-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- WEB/db/mq/discovery/config/monitor/trace -->
+        <dependency>
+            <groupId>cn.itlym.platform</groupId>
+            <artifactId>shoulder-platform-starter-micro</artifactId>
+        </dependency>
+
+        <!-- JPA -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+
+
+        <!-- mail -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-mail</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>javax.mail</artifactId>
+            <version>${javax.mail.version}</version>
+        </dependency>
+
+        <!-- freemarker 模版 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+
+
+    </dependencies>
+
+    <build>
+        <!-- maven 默认使用 artifactId+version拼接 -->
+        <finalName>${project.artifactId}</finalName>
+
+        <!-- 动态打包环境配置源文件 -->
+        <filters>
+            <filter>../../../dynamicConfig/config-${profile.active}.properties</filter>
+        </filters>
+
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+
+    </build>
+
+
+</project>

+ 31 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/SmsCenterStarter.java

@@ -0,0 +1,31 @@
+package cn.itlym.shoulder.platform.notify.sms;
+
+import cn.itlym.shoulder.platform.notify.sms.enums.EnumDeserializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 微服务的短信中心
+ *
+ * @author lym
+ */
+@SpringBootApplication
+public class SmsCenterStarter {
+
+    public static void main(String[] args) {
+        SpringApplication.run(SmsCenterStarter.class, args);
+    }
+
+    @Bean
+    public ObjectMapper objectMapper() {
+        ObjectMapper objectMapper = new ObjectMapper();
+        SimpleModule module = new SimpleModule();
+        module.addDeserializer(Enum.class, new EnumDeserializer());
+        objectMapper.registerModule(module);
+        return objectMapper;
+    }
+
+}

+ 19 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/config/JmsConfig.java

@@ -0,0 +1,19 @@
+package cn.itlym.shoulder.platform.notify.sms.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.jms.core.JmsTemplate;
+
+/**
+ * @author lym
+ */
+//@Configuration
+public class JmsConfig {
+
+    @Bean
+    @ConditionalOnMissingBean
+    public JmsTemplate jmsTemplate() {
+        return new JmsTemplate();
+    }
+
+}

+ 41 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/constant/EmailTemplateTypeEnum.java

@@ -0,0 +1,41 @@
+package cn.itlym.shoulder.platform.notify.sms.constant;
+
+/**
+ * @author lym
+ */
+public enum EmailTemplateTypeEnum {
+
+    /**
+     * 纯文本
+     */
+    TEXT(1),
+
+    /**
+     * 网页
+     */
+    HTML(2),
+
+    /**
+     * themeleaf
+     */
+    THYMELEAF(3),
+
+    /**
+     * freeMarker
+     */
+    FREEMARKER(4),
+
+    ;
+
+    private int value;
+
+    EmailTemplateTypeEnum(int value) {
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+
+}

+ 46 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/EmailController.java

@@ -0,0 +1,46 @@
+package cn.itlym.shoulder.platform.notify.sms.controller;
+
+import cn.itlym.shoulder.platform.notify.sms.dto.EmailDTO;
+import cn.itlym.shoulder.platform.notify.sms.dto.UiResult;
+import cn.itlym.shoulder.platform.notify.sms.service.EmailSender;
+import cn.itlym.shoulder.platform.notify.sms.service.EmailService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/email")
+public class EmailController {
+
+    @Value("${test.email.receiver-address}")
+    public String receiverAddress;
+    //@Autowired
+    private EmailService emailService;
+    @Autowired
+    private EmailSender emailSender;
+
+    @GetMapping("testSend")
+    public UiResult send() {
+        EmailDTO emailDTO = new EmailDTO();
+
+        emailDTO.setReceiver_emails(new String[]{receiverAddress});
+        emailDTO.setContent("test");
+        emailDTO.setContent("test");
+
+        try {
+            emailSender.send(emailDTO);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return UiResult.error();
+        }
+        return UiResult.ok();
+    }
+
+    @PostMapping("list")
+    public UiResult list(EmailDTO mail) {
+        return emailService.listMail(mail);
+    }
+}

+ 48 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/SmsController.java

@@ -0,0 +1,48 @@
+package cn.itlym.shoulder.platform.notify.sms.controller;
+
+import cn.itlym.shoulder.platform.notify.sms.param.SmsParam;
+import cn.itlym.shoulder.platform.notify.sms.service.SmsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author lym
+ */
+@RestController
+@RequestMapping("sms")
+public class SmsController {
+
+    @Autowired
+    private SmsService smsService;
+
+    @Value("${test.sms.phoneNumber}")
+    private String phoneNumber;
+
+    @Value("${test.sms.template-code}")
+    private String templateCode;
+
+    @PostMapping("send")
+    public boolean send(SmsParam param) {
+        return smsService.sendSms(param);
+    }
+
+    @RequestMapping("test")
+    public String test() {
+        if (!"true".equals(System.getProperty("ebug"))) {
+            throw new IllegalStateException("please add jvm launch param(-Debug=true)");
+        }
+        SmsParam param = SmsParam.newBuilder()
+                .phoneNumber(phoneNumber)
+                .templateCode(templateCode)
+                .addTemplateParam("code", "test")
+                .build();
+
+        boolean result = smsService.sendSms(param);
+        return "your are testing now, and result=" + result;
+    }
+
+
+}

+ 32 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/TestController.java

@@ -0,0 +1,32 @@
+package cn.itlym.shoulder.platform.notify.sms.controller;
+
+import cn.itlym.shoulder.platform.notify.sms.enums.MyEnum;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("test")
+public class TestController {
+
+    @Autowired
+    ObjectMapper objectMapper;
+
+    @RequestMapping("ein")
+    public Object ein(MyEnum v) {
+        System.out.println(v);
+        return v;
+    }
+
+    @RequestMapping("eout")
+    public MyEnum eout() {
+        return MyEnum.V1;
+    }
+
+    @RequestMapping("de")
+    public MyEnum de() throws JsonProcessingException {
+        return objectMapper.readValue("{\"name\":\"1\"}", MyEnum.class);
+    }
+}

+ 74 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/dto/EmailDTO.java

@@ -0,0 +1,74 @@
+package cn.itlym.shoulder.platform.notify.sms.dto;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+/**
+ * EmailDTO
+ */
+public class EmailDTO implements Serializable {
+    private static final long serialVersionUID = 1L;
+    //必填参数
+    private String[] receiver_emails;//接收方邮件
+    private String subject;//主题
+    private String content;//邮件内容
+    //选填
+    private String template;//模板
+    private HashMap<String, String> templateParam;// 自定义参数
+
+
+    public EmailDTO() {
+        super();
+    }
+
+    public EmailDTO(String[] receiver_emails, String subject, String content, String template,
+                    HashMap<String, String> templateParam) {
+        super();
+        this.receiver_emails = receiver_emails;
+        this.subject = subject;
+        this.content = content;
+        this.template = template;
+        this.templateParam = templateParam;
+    }
+
+
+    public String[] getReceiver_emails() {
+        return receiver_emails;
+    }
+
+    public void setReceiver_emails(String[] receiver_emails) {
+        this.receiver_emails = receiver_emails;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getTemplate() {
+        return template;
+    }
+
+    public void setTemplate(String template) {
+        this.template = template;
+    }
+
+    public HashMap<String, String> getTemplateParam() {
+        return templateParam;
+    }
+
+    public void setTemplateParam(HashMap<String, String> templateParam) {
+        this.templateParam = templateParam;
+    }
+}

+ 54 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/dto/UiResult.java

@@ -0,0 +1,54 @@
+package cn.itlym.shoulder.platform.notify.sms.dto;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 页面响应 DTO
+ */
+public class UiResult extends HashMap<String, Object> {
+
+    private static final long serialVersionUID = 1L;
+
+    public UiResult() {
+        put("code", 0);
+    }
+
+    public static UiResult error() {
+        return error(500, "未知异常,请联系管理员");
+    }
+
+    public static UiResult error(String msg) {
+        return error(500, msg);
+    }
+
+    public static UiResult error(int code, String msg) {
+        UiResult r = new UiResult();
+        r.put("code", code);
+        r.put("msg", msg);
+        return r;
+    }
+
+    public static UiResult ok(Object msg) {
+        UiResult r = new UiResult();
+        r.put("msg", msg);
+        return r;
+    }
+
+
+    public static UiResult ok(Map<String, Object> map) {
+        UiResult r = new UiResult();
+        r.putAll(map);
+        return r;
+    }
+
+    public static UiResult ok() {
+        return new UiResult();
+    }
+
+    @Override
+    public UiResult put(String key, Object value) {
+        super.put(key, value);
+        return this;
+    }
+}

+ 33 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/entity/EmailContentTemplate.java

@@ -0,0 +1,33 @@
+package cn.itlym.shoulder.platform.notify.sms.entity;
+
+import java.util.Date;
+
+/**
+ * @author lym
+ */
+public class EmailContentTemplate {
+
+    private String id;
+
+    /**
+     * 内容较长
+     */
+    private String template;
+
+    /**
+     * text\html\thymeleaf\freemarker
+     */
+    private String type;
+
+    /**
+     * 参数名,如 "a,b,c"
+     */
+    private String param;
+
+    private boolean delete;
+
+    private Date createTime;
+
+    private String creator;
+
+}

+ 116 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/entity/EmailEntity.java

@@ -0,0 +1,116 @@
+package cn.itlym.shoulder.platform.notify.sms.entity;
+
+import cn.itlym.shoulder.platform.notify.sms.dto.EmailDTO;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.Date;
+
+//@Entity
+@Table(name = "tb_email")
+public class EmailEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 自增主键
+     */
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    @Column(name = "id", unique = true, nullable = false)
+    private Long id;
+
+    /**
+     * 接收人邮箱(多个逗号分开)
+     */
+    @Column(name = "receive_email", nullable = false, length = 500)
+    private String receiveEmail;
+
+    /**
+     * 主题
+     */
+    @Column(name = "subject", nullable = false, length = 100)
+    private String subject;
+
+    /**
+     * 发送内容
+     */
+    @Column(name = "content", nullable = false, length = 65535)
+    private String content;
+
+    /**
+     * 模板
+     */
+    @Column(name = "template", nullable = false, length = 100)
+    private String template;
+
+    /**
+     * 发送时间
+     */
+    @Column(name = "send_time", nullable = false, length = 19)
+    private Timestamp sendTime;
+
+
+    public EmailEntity() {
+        super();
+    }
+
+    public EmailEntity(EmailDTO mail) {
+        this.receiveEmail = Arrays.toString(mail.getReceiver_emails());
+        this.subject = mail.getSubject();
+        this.content = mail.getContent();
+        this.template = mail.getTemplate();
+        this.sendTime = new Timestamp(new Date().getTime());
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getReceiveEmail() {
+        return receiveEmail;
+    }
+
+    public void setReceiveEmail(String receiveEmail) {
+        this.receiveEmail = receiveEmail;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getTemplate() {
+        return template;
+    }
+
+    public void setTemplate(String template) {
+        this.template = template;
+    }
+
+    public Timestamp getSendTime() {
+        return sendTime;
+    }
+
+    public void setSendTime(Timestamp sendTime) {
+        this.sendTime = sendTime;
+    }
+
+}

+ 37 - 0
shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/EnumConverter.java

@@ -0,0 +1,37 @@
+package cn.itlym.shoulder.platform.notify.sms.enums;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.lang.NonNull;
+
+public class EnumConverter implements Converter<String, Enum<?>> {
+
+    @SuppressWarnings("unchecked")
+    private Class<? extends Enum> enumType;
+
+    public EnumConverter(Class<? extends Enum> enumType) {
+        this.enumType = enumType;
+    }
+
+    @Override
+    public Enum convert(@NonNull String source) {
+        if (source.isBlank()) {
+            return null;
+        }
+        // 尝试用名称匹配。忽略大小写
+        Enum[] enums = enumType.getEnumConstants();
+        for (Enum e : enums) {
+            if (e.name().equalsIgnoreCase(source)) {
+                return e;
+            }
+        }
+        return null;
+
+        // 尝试使用 public static T of(String source) 方法
+
+        // 尝试使用标识字段匹配
+
+        // 尝试使用标识方法匹配
+    }
+
+
+}

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä