commit ddf1f6861566243288ed5b9fc8d1e1c4efacbc5b Author: 15858193327 Date: Wed Oct 7 17:08:13 2020 +0800 主分支由改为main diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md new file mode 100644 index 0000000..c1e2f76 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE.zh-CN.md @@ -0,0 +1,16 @@ +### 该问题是怎么引起的? + + + +### 重现步骤 + + + +### 报错信息 + + +### 尝试过的解决手段 + + + + diff --git a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md b/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md new file mode 100644 index 0000000..33948fd --- /dev/null +++ b/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md @@ -0,0 +1,15 @@ +### 相关的Issue + + +### 原因(目的、解决的问题等) + + +### 描述(做了什么,变更了什么) + + +### 测试用例(新增、改动、可能影响的功能) + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fb83d1 --- /dev/null +++ b/.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* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d7e448 --- /dev/null +++ b/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不干涉该层 | diff --git a/banner-font.txt b/banner-font.txt new file mode 100644 index 0000000..cc51c0f --- /dev/null +++ b/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 + + _____ _____ _______ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ + /\ \ /\ \ /::\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ |\ \ + /::\ \ /::\____\ /::::\ \ /::\____\ /::\____\ /::\ \ /::\ \ /::\ \ /::\ \ /::\ \ /::\ \ /::\ \ /::\____\ /::\ \ |:\____\ + /::::\ \ /:::/ / /::::::\ \ /:::/ / /:::/ / /::::\ \ /::::\ \ /::::\ \ /::::\ \ /::::\ \ \:::\ \ /::::\ \ /:::/ / /::::\ \ |::| | + /::::::\ \ /:::/ / /::::::::\ \ /:::/ / /:::/ / /::::::\ \ /::::::\ \ /::::::\ \ /::::::\ \ /::::::\ \ \:::\ \ /::::::\ \ /:::/ _/___ /::::::\ \ |::| | + /:::/\:::\ \ /:::/ / /:::/~~\:::\ \ /:::/ / /:::/ / /:::/\:::\ \ /:::/\:::\ \ /:::/\:::\ \ /:::/\:::\ \ /:::/\:::\ \ \:::\ \ /:::/\:::\ \ /:::/ /\ \ /:::/\:::\ \ |::| | + /:::/__\:::\ \ /:::/____/ /:::/ \:::\ \ /:::/ / /:::/ / /:::/ \:::\ \ /:::/__\:::\ \ /:::/__\:::\ \ /:::/ \:::\ \ /:::/__\:::\ \ \:::\ \ /:::/__\:::\ \ /:::/ /::\____\ /:::/__\:::\ \ |::| | + \:::\ \:::\ \ /::::\ \ /:::/ / \:::\ \ /:::/ / /:::/ / /:::/ \:::\ \ /::::\ \:::\ \ /::::\ \:::\ \ /:::/ \:::\ \ /::::\ \:::\ \ /::::\ \ /::::\ \:::\ \ /:::/ /:::/ / /::::\ \:::\ \ |::| | + ___\:::\ \:::\ \ /::::::\ \ _____ /:::/____/ \:::\____\ /:::/ / _____ /:::/ / /:::/ / \:::\ \ /::::::\ \:::\ \ /::::::\ \:::\ \ /:::/ / \:::\ \ /::::::\ \:::\ \ /::::::\ \ /::::::\ \:::\ \ /:::/ /:::/ _/___ /::::::\ \:::\ \ |::|___|______ + /\ \:::\ \:::\ \ /:::/\:::\ \ /\ \ |:::| | |:::| | /:::/____/ /\ \ /:::/ / /:::/ / \:::\ ___\ /:::/\:::\ \:::\ \ /:::/\:::\ \:::\____\ /:::/ / \:::\ ___\ /:::/\:::\ \:::\ \ /:::/\:::\ \ /:::/\:::\ \:::\ \ /:::/___/:::/ /\ \ /:::/\:::\ \:::\ \ /::::::::\ \ +/::\ \:::\ \:::\____\/:::/ \:::\ /::\____\|:::|____| |:::| ||:::| / /::\____\/:::/____/ /:::/____/ \:::| |/:::/__\:::\ \:::\____\/:::/ \:::\ \:::| | /:::/____/ ___\:::| |/:::/ \:::\ \:::\____\ /:::/ \:::\____\/:::/__\:::\ \:::\____\|:::| /:::/ /::\____\/:::/ \:::\ \:::\____\ /::::::::::\____\ +\:::\ \:::\ \::/ /\::/ \:::\ /:::/ / \:::\ \ /:::/ / |:::|____\ /:::/ /\:::\ \ \:::\ \ /:::|____|\:::\ \:::\ \::/ /\::/ |::::\ /:::|____| \:::\ \ /\ /:::|____|\::/ \:::\ /:::/ / /:::/ \::/ /\:::\ \:::\ \::/ /|:::|__/:::/ /:::/ /\::/ \:::\ /:::/ / /:::/~~~~/~~ + \:::\ \:::\ \/____/ \/____/ \:::\/:::/ / \:::\ \ /:::/ / \:::\ \ /:::/ / \:::\ \ \:::\ \ /:::/ / \:::\ \:::\ \/____/ \/____|:::::\/:::/ / \:::\ /::\ \::/ / \/____/ \:::\/:::/ / /:::/ / \/____/ \:::\ \:::\ \/____/ \:::\/:::/ /:::/ / \/____/ \:::\/:::/ / /:::/ / + \:::\ \:::\ \ \::::::/ / \:::\ /:::/ / \:::\ \ /:::/ / \:::\ \ \:::\ \ /:::/ / \:::\ \:::\ \ |:::::::::/ / \:::\ \:::\ \/____/ \::::::/ / /:::/ / \:::\ \:::\ \ \::::::/ /:::/ / \::::::/ / /:::/ / + \:::\ \:::\____\ \::::/ / \:::\__/:::/ / \:::\ /:::/ / \:::\ \ \:::\ /:::/ / \:::\ \:::\____\ |::|\::::/ / \:::\ \:::\____\ \::::/ / /:::/ / \:::\ \:::\____\ \::::/___/:::/ / \::::/ / /:::/ / + \:::\ /:::/ / /:::/ / \::::::::/ / \:::\__/:::/ / \:::\ \ \:::\ /:::/ / \:::\ \::/ / |::| \::/____/ \:::\ /:::/ / /:::/ / \::/ / \:::\ \::/ / \:::\__/:::/ / /:::/ / \::/ / + \:::\/:::/ / /:::/ / \::::::/ / \::::::::/ / \:::\ \ \:::\/:::/ / \:::\ \/____/ |::| ~| \:::\/:::/ / /:::/ / \/____/ \:::\ \/____/ \::::::::/ / /:::/ / \/____/ + \::::::/ / /:::/ / \::::/ / \::::::/ / \:::\ \ \::::::/ / \:::\ \ |::| | \::::::/ / /:::/ / \:::\ \ \::::::/ / /:::/ / + \::::/ / /:::/ / \::/____/ \::::/ / \:::\____\ \::::/ / \:::\____\ \::| | \::::/ / /:::/ / \:::\____\ \::::/ / /:::/ / + \::/ / \::/ / ~~ \::/____/ \::/ / \::/____/ \::/ / \:| | \::/____/ \::/ / \::/ / \::/____/ \::/ / + \/____/ \/____/ ~~ \/____/ ~~ \/____/ \|___| \/____/ \/____/ ~~ \/____/ + diff --git a/doc/README.MD b/doc/README.MD new file mode 100644 index 0000000..2c5c539 --- /dev/null +++ b/doc/README.MD @@ -0,0 +1,6 @@ +需要放到 NACOS 里的应用配置信息 + +appConfig 中的配置文件信息是在启动时读取的信息, +实际中这些信息应放到 nacos 中维护并在 mysql 持久化备份, +这里为了将其一并开源,特地放在这里 +注意,配置文件中,ip、端口号、账号密码需要修改为自己的 \ No newline at end of file diff --git a/doc/appConfig/boot-admin.yml b/doc/appConfig/boot-admin.yml new file mode 100644 index 0000000..3e2a03a --- /dev/null +++ b/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 \ No newline at end of file diff --git a/doc/appConfig/common.yml b/doc/appConfig/common.yml new file mode 100644 index 0000000..b0f320c --- /dev/null +++ b/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 \ No newline at end of file diff --git a/doc/appConfig/db-mysql.yml b/doc/appConfig/db-mysql.yml new file mode 100644 index 0000000..9d0c72d --- /dev/null +++ b/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' diff --git a/doc/appConfig/db.yml b/doc/appConfig/db.yml new file mode 100644 index 0000000..aed9269 --- /dev/null +++ b/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' diff --git a/doc/appConfig/freemark.yml b/doc/appConfig/freemark.yml new file mode 100644 index 0000000..b6c4dfc --- /dev/null +++ b/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/ \ No newline at end of file diff --git a/doc/appConfig/mq-rabbit.yml b/doc/appConfig/mq-rabbit.yml new file mode 100644 index 0000000..11e5e72 --- /dev/null +++ b/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 diff --git a/doc/appConfig/redis.yml b/doc/appConfig/redis.yml new file mode 100644 index 0000000..049e5e1 --- /dev/null +++ b/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 diff --git a/doc/appConfig/shoulder-gateway.yml b/doc/appConfig/shoulder-gateway.yml new file mode 100644 index 0000000..ec2c8d4 --- /dev/null +++ b/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 # 解密 diff --git a/doc/appConfig/shoulder-sms-center.yml b/doc/appConfig/shoulder-sms-center.yml new file mode 100644 index 0000000..e26b0ed --- /dev/null +++ b/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 \ No newline at end of file diff --git a/doc/appConfig/shoulder-storage-center.yml b/doc/appConfig/shoulder-storage-center.yml new file mode 100644 index 0000000..f04a017 --- /dev/null +++ b/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 diff --git a/doc/appConfig/third-account.yml b/doc/appConfig/third-account.yml new file mode 100644 index 0000000..c8f56b3 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/doc/nacos.md b/doc/nacos.md new file mode 100644 index 0000000..95d8fc3 --- /dev/null +++ b/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) + + diff --git a/doc/sentinel.yml b/doc/sentinel.yml new file mode 100644 index 0000000..48b7b7d --- /dev/null +++ b/doc/sentinel.yml @@ -0,0 +1,7 @@ +spring: + cloud: + sentinel: + filter: + enabled: false + transport: + dashboard: localhost:8080 \ No newline at end of file diff --git a/dynamicConfig/README.MD b/dynamicConfig/README.MD new file mode 100644 index 0000000..9ab55be --- /dev/null +++ b/dynamicConfig/README.MD @@ -0,0 +1 @@ +maven 多环境打包 配置信息自动切换 \ No newline at end of file diff --git a/dynamicConfig/config-dev.properties b/dynamicConfig/config-dev.properties new file mode 100644 index 0000000..53500c2 --- /dev/null +++ b/dynamicConfig/config-dev.properties @@ -0,0 +1,7 @@ +# ע⣺valueĽβֲҪпոmaven-resources-plugin Զȥ +nacos.ip=nacos.itlym.cn +nacos.port=8848 +# ⻧ʶĬϾ publicҪڸ벻ͬ devtestע⣺nacos1.4ǰҪκֵ public github 3460 +nacos.namespace= +# nacos.group ĬΪ DEFAULT_GROUPҪڸ벻ͬ paynotPayNACOS Ŀǰ˸չ㣬ʵʻ֧ +seata.namespace= diff --git a/dynamicConfig/config-prod.properties b/dynamicConfig/config-prod.properties new file mode 100644 index 0000000..ad08e79 --- /dev/null +++ b/dynamicConfig/config-prod.properties @@ -0,0 +1,4 @@ +nacos.ip=127.0.0.1 +nacos.port=8848 +nacos.namespace= +seata.namespace= diff --git a/img/architecture.png b/img/architecture.png new file mode 100644 index 0000000..5c548c5 Binary files /dev/null and b/img/architecture.png differ diff --git a/img/docker.png b/img/docker.png new file mode 100644 index 0000000..cd57b05 Binary files /dev/null and b/img/docker.png differ diff --git a/img/elk-nginx.png b/img/elk-nginx.png new file mode 100644 index 0000000..ce4b1de Binary files /dev/null and b/img/elk-nginx.png differ diff --git a/img/elk-nginx2.png b/img/elk-nginx2.png new file mode 100644 index 0000000..06066ea Binary files /dev/null and b/img/elk-nginx2.png differ diff --git a/img/host.png b/img/host.png new file mode 100644 index 0000000..0dd5bbd Binary files /dev/null and b/img/host.png differ diff --git a/img/mysql1.png b/img/mysql1.png new file mode 100644 index 0000000..bcb5d95 Binary files /dev/null and b/img/mysql1.png differ diff --git a/img/mysql2.png b/img/mysql2.png new file mode 100644 index 0000000..2f7ac98 Binary files /dev/null and b/img/mysql2.png differ diff --git a/img/mysql3.png b/img/mysql3.png new file mode 100644 index 0000000..30f4413 Binary files /dev/null and b/img/mysql3.png differ diff --git a/img/nacos1.png b/img/nacos1.png new file mode 100644 index 0000000..6441cd3 Binary files /dev/null and b/img/nacos1.png differ diff --git a/img/nacos2.png b/img/nacos2.png new file mode 100644 index 0000000..ffcefd1 Binary files /dev/null and b/img/nacos2.png differ diff --git a/img/redis.png b/img/redis.png new file mode 100644 index 0000000..569b8c6 Binary files /dev/null and b/img/redis.png differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5e529bd --- /dev/null +++ b/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + cn.itlym.platform + shoulder-platform + pom + 1.0-SNAPSHOT + + + shoulder-platform-common + shoulder-gateway + shoulder-auth-center + shoulder-notify-center + shoulder-pay-center + shoulder-storage-center + shoulder-system-center + shoulder-backstage + shoulder-generator + + + + \ No newline at end of file diff --git a/shoulder-auth-center/README.md b/shoulder-auth-center/README.md new file mode 100644 index 0000000..9219a25 --- /dev/null +++ b/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) \ No newline at end of file diff --git a/shoulder-auth-center/pom.xml b/shoulder-auth-center/pom.xml new file mode 100644 index 0000000..f6a63e9 --- /dev/null +++ b/shoulder-auth-center/pom.xml @@ -0,0 +1,24 @@ + + + + 4.0.0 + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-common/shoulder-platform-parent/pom.xml + + + cn.itlym.platform + shoulder-auth-center + pom + + + shoulder-auth-api + + + + \ No newline at end of file diff --git a/shoulder-auth-center/shoulder-auth-api/pom.xml b/shoulder-auth-center/shoulder-auth-api/pom.xml new file mode 100644 index 0000000..62d415b --- /dev/null +++ b/shoulder-auth-center/shoulder-auth-api/pom.xml @@ -0,0 +1,16 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../../shoulder-platform-common/shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-auth-api + + + \ No newline at end of file diff --git a/shoulder-backstage/README.md b/shoulder-backstage/README.md new file mode 100644 index 0000000..e7db47e --- /dev/null +++ b/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命令执行记录) + - 运行日志(应用运行时产生的日志) + - 登录日志。记录管理系统的登录记录 +- 开放能力管理 + - 第三方管理:接入、登记 + - 第三方使用记录 +- 下载中心(可分类增加各种文件) + - 对接文档等 +- 广告管理 + - 广告商 + - 展示位置、大小、优先级、目标用户 + - 定期展示 +- 系统设置 + - 后台系统参数设置 + - 应用系统参数设置:图标,节日图标 + - 企业信息 + - 新闻 + - 语言 +- 应用系统状态监控 + - 各个服务状态 + - 业务指标监控 + - 告警、推送 +- 安全项 + - 数据备份 + - 拦截设置 + - 反爬强度 diff --git a/shoulder-backstage/pom.xml b/shoulder-backstage/pom.xml new file mode 100644 index 0000000..e7dbb68 --- /dev/null +++ b/shoulder-backstage/pom.xml @@ -0,0 +1,15 @@ + + + + shoulder-platform + cn.itlym.platform + 1.0-SNAPSHOT + + 4.0.0 + + shoulder-backstage + + + \ No newline at end of file diff --git a/shoulder-gateway/README.md b/shoulder-gateway/README.md new file mode 100644 index 0000000..87e9f65 --- /dev/null +++ b/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` .: + `:.###:, ,·` + .#. :###::` `--....-` + `·` ``` diff --git a/shoulder-gateway/pom.xml b/shoulder-gateway/pom.xml new file mode 100644 index 0000000..152493f --- /dev/null +++ b/shoulder-gateway/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + pom + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-common/shoulder-platform-parent/pom.xml + + + shoulder-gateway + 1.0-SNAPSHOT + + + shoulder-api-gateway + shoulder-storage-gateway + + + + + 0.4-SNAPSHOT + 2.2.8.RELEASE + Hoxton.SR6 + 2.2.1.RELEASE + + + + diff --git a/shoulder-gateway/shoulder-api-gateway/README.md b/shoulder-gateway/shoulder-api-gateway/README.md new file mode 100644 index 0000000..298e0a0 --- /dev/null +++ b/shoulder-gateway/shoulder-api-gateway/README.md @@ -0,0 +1,10 @@ +# shoulder-api-gateway + + + +接入层网关:安全、限流、日志、监控、缓存(业务无关) + +API 服务网关:超时、缓存、熔断、重试、查询聚合、数据校验(时间、方法、版本、AppKey、签名) +【贴近业务】 + +实际应用中,也可以根据自己的设计,将代理网关的能力赋予 API gateway 中。 diff --git a/shoulder-gateway/shoulder-api-gateway/pom.xml b/shoulder-gateway/shoulder-api-gateway/pom.xml new file mode 100644 index 0000000..e208675 --- /dev/null +++ b/shoulder-gateway/shoulder-api-gateway/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../../shoulder-platform-common/shoulder-platform-parent/pom.xml + + + shoulder-api-gateway + Shoulder 网关,支持跨域认证,追踪,限流,接口发布与撤销 + ${project.artifactId} + + + 0.4-SNAPSHOT + 2.2.8.RELEASE + Hoxton.SR6 + 2.2.1.RELEASE + + + + + cn.itlym + shoulder-core + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + cn.itlym.platform + shoulder-platform-starter-discovery-client + ${shoulder-platform.version} + + + + + cn.itlym.platform + shoulder-platform-starter-config-client + ${shoulder-platform.version} + + + + + + ${project.artifactId} + + + + ../../dynamicConfig/config-${profile.active}.properties + + + + + src/main/resources + + **/* + + true + + + + + + \ No newline at end of file diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/ShoulderApiGateway.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/ShoulderApiGateway.java new file mode 100644 index 0000000..d89051f --- /dev/null +++ b/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 fallback() { + return Mono.just(RestResult.error(CommonErrorCodeEnum.REQUEST_TIMEOUT)); + } +} diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/ServiceTokenClient.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/ServiceTokenClient.java new file mode 100644 index 0000000..7f88080 --- /dev/null +++ b/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 getServiceToken(String accessToken, String appId); + + + /** + * 删除 serviceToken + * + * @param serviceToken st + * @return void + */ + Mono deleteServiceToken(String serviceToken); + +} diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/dto/param/AccessToken2ServiceTokenParam.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/dto/param/AccessToken2ServiceTokenParam.java new file mode 100644 index 0000000..da722a6 --- /dev/null +++ b/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; + } +} diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/dto/param/DeleteServiceTokenParam.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/dto/param/DeleteServiceTokenParam.java new file mode 100644 index 0000000..4a580d0 --- /dev/null +++ b/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; + } +} diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/impl/ServiceTokenClientImpl.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/client/impl/ServiceTokenClientImpl.java new file mode 100644 index 0000000..1dac9e9 --- /dev/null +++ b/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 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 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(); + } + +} diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/config/CorsConfig.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/config/CorsConfig.java new file mode 100644 index 0000000..ca3bbbf --- /dev/null +++ b/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); + } + + +} \ No newline at end of file diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/config/JsonExceptionHandler.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/config/JsonExceptionHandler.java new file mode 100644 index 0000000..1b5c027 --- /dev/null +++ b/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 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 buildErrorAttributes(int httpStatus, RestResult response) { + Map map = new HashMap<>(2); + map.put(ATTRIBUTE_NAME_HTTP_STATUS, httpStatus); + map.put(ATTRIBUTE_NAME_RESPONSE, response); + return map; + } + + /** + * 获取异常属性 + * 为了安全,不打印堆栈信息,即使使用者手动开启 + */ + @Override + protected Map 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 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 renderErrorResponse(ServerRequest request) { + boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL); + Map 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 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); + } +} \ No newline at end of file diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/ex/ShoulderGatewayException.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/ex/ShoulderGatewayException.java new file mode 100644 index 0000000..2ca57dc --- /dev/null +++ b/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()); + } + + } + +} diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/filter/AuthenticationGlobalFilter.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/filter/AuthenticationGlobalFilter.java new file mode 100644 index 0000000..291ce10 --- /dev/null +++ b/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 ignorePath; + private AntPathMatcher pathMatcher = new AntPathMatcher(); + + public AuthenticationGlobalFilter(ServiceTokenClient stClient, List ignorePath) { + this.stClient = stClient; + this.ignorePath = ignorePath; + } + + /** + * 按照 spring 的方式来格式化上下文 key 格式 + */ + private static String qualify(String attr) { + return AuthenticationGlobalFilter.class.getName() + "." + attr; + } + + @Override + public Mono 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; + } + +} diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/util/RequestUtil.java b/shoulder-gateway/shoulder-api-gateway/src/main/java/cn/itlym/shoulder/platform/gateway/util/RequestUtil.java new file mode 100644 index 0000000..0615c1c --- /dev/null +++ b/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 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(); + } +} diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/resources/banner.txt b/shoulder-gateway/shoulder-api-gateway/src/main/resources/banner.txt new file mode 100644 index 0000000..1c850bf --- /dev/null +++ b/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} diff --git a/shoulder-gateway/shoulder-api-gateway/src/main/resources/bootstrap.yml b/shoulder-gateway/shoulder-api-gateway/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..d542bb0 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/shoulder-gateway/shoulder-storage-gateway/README.md b/shoulder-gateway/shoulder-storage-gateway/README.md new file mode 100644 index 0000000..8ee6ae8 --- /dev/null +++ b/shoulder-gateway/shoulder-storage-gateway/README.md @@ -0,0 +1,5 @@ +# shoulder-storage-gateway + +该网关用于屏蔽不同存储方式的差异,为系统内部提供统一的存储能力 + +可以在内部支持 `Minio`、`Ceph`、`七牛对象存储`、`阿里云对象存储`、`腾讯对象存储`... diff --git a/shoulder-gateway/shoulder-storage-gateway/pom.xml b/shoulder-gateway/shoulder-storage-gateway/pom.xml new file mode 100644 index 0000000..af37dbf --- /dev/null +++ b/shoulder-gateway/shoulder-storage-gateway/pom.xml @@ -0,0 +1,15 @@ + + + + shoulder-gateway + cn.itlym.platform + 1.0-SNAPSHOT + + 4.0.0 + + shoulder-storage-gateway + + + \ No newline at end of file diff --git a/shoulder-generator/pom.xml b/shoulder-generator/pom.xml new file mode 100644 index 0000000..a5cab5b --- /dev/null +++ b/shoulder-generator/pom.xml @@ -0,0 +1,109 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../../shoulder-platform-common/shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-generator + 代码生成器 + 代码生成器 + + + + org.apache.velocity + velocity + 1.7 + + + + cn.itlym.platform + shoulder-platform-starter-db + + + + cn.itlym.platform + shoulder-platform-starter-config-client + + + + commons-io + commons-io + + + + commons-configuration + commons-configuration + + + + cn.itlym.platform + shoulder-platform-starter-rpc-server + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + repackage + + + + + + + + com.spotify + docker-maven-plugin + + + + ${docker.image.prefix}/${project.artifactId} + src/main/docker + + ${docker.host} + + + / + ${project.build.directory} + ${project.build.finalName}.jar + + + + + + + ${project.artifactId} + + + + src/main/java + + **/*.properties + **/*.xml + **/*.yml + + + true + + + src/main/resources + + + + + + \ No newline at end of file diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/GeneratorApp.java b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/GeneratorApp.java new file mode 100644 index 0000000..ec5e57e --- /dev/null +++ b/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); + } +} diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/controller/GeneratorController.java b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/controller/GeneratorController.java new file mode 100644 index 0000000..4fc27e8 --- /dev/null +++ b/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 list(@RequestParam Map 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()); + } + + } + + +} diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/dao/SysGeneratorDao.java b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/dao/SysGeneratorDao.java new file mode 100644 index 0000000..af290c8 --- /dev/null +++ b/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> queryList(Map map); + + int queryTotal(Map map); + + Map queryTable(String tableName); + + List> queryColumns(String tableName); + +} diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/dao/SysGeneratorDao.xml b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/dao/SysGeneratorDao.xml new file mode 100644 index 0000000..19c8288 --- /dev/null +++ b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/dao/SysGeneratorDao.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/model/ColumnEntity.java b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/model/ColumnEntity.java new file mode 100644 index 0000000..9be72da --- /dev/null +++ b/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; + +} diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/model/TableEntity.java b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/model/TableEntity.java new file mode 100644 index 0000000..7e99d5f --- /dev/null +++ b/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 columns; + + //类名(第一个字母大写),如:sys_user => SysUser + private String className; + //类名(第一个字母小写),如:sys_user => sysUser + private String lowClassName; + +} diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/service/SysGeneratorService.java b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/service/SysGeneratorService.java new file mode 100644 index 0000000..ccf6e94 --- /dev/null +++ b/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 map); + + int queryTotal(Map map); + + Map queryTable(String tableName); + + List> queryColumns(String tableName); + + byte[] generatorCode(String[] tableNames, OutputStream out); + +} diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/service/impl/SysGeneratorServiceImpl.java b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/service/impl/SysGeneratorServiceImpl.java new file mode 100644 index 0000000..8bd2a71 --- /dev/null +++ b/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 map) { + //设置分页信息,分别是当前页数和每页显示的总记录数【记住:必须在mapper接口中的方法执行之前设置该分页信息】 + PageHelper.startPage(MapUtils.getInteger(map, "page"), MapUtils.getInteger(map, "limit"), true); + List> list = sysGeneratorDao.queryList(map); + PageInfo> pageInfo = new PageInfo<>(list); + + return PageResult.PageInfoConverter.toResult(pageInfo); + } + + @Override + public int queryTotal(Map map) { + return 0; + } + + @Override + public Map queryTable(String tableName) { + return sysGeneratorDao.queryTable(tableName); + } + + @Override + public List> 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 table = queryTable(tableName); + //查询列信息 + List> 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(); + } + + +} diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/utils/DateUtils.java b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/utils/DateUtils.java new file mode 100644 index 0000000..6d4ea46 --- /dev/null +++ b/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; + } +} diff --git a/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/utils/GenUtils.java b/shoulder-generator/src/main/java/cn/itlym/shoulder/generator/utils/GenUtils.java new file mode 100644 index 0000000..2512f59 --- /dev/null +++ b/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 getTemplates() { + List templates = new ArrayList(); + 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 table, + List> 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 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 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> columns, Configuration config, TableEntity tableEntity) { + boolean hasBigDecimal = false; + List columnsList = new ArrayList<>(); + for (Map 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; + } +} diff --git a/shoulder-generator/src/main/resources/bootstrap.yml b/shoulder-generator/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..73d754e --- /dev/null +++ b/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 \ No newline at end of file diff --git a/shoulder-generator/src/main/resources/generator.properties b/shoulder-generator/src/main/resources/generator.properties new file mode 100644 index 0000000..5dbed0a --- /dev/null +++ b/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 \ No newline at end of file diff --git a/shoulder-generator/src/main/resources/template/Controller.java.vm b/shoulder-generator/src/main/resources/template/Controller.java.vm new file mode 100644 index 0000000..626dbab --- /dev/null +++ b/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 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("删除成功"); + } + +} diff --git a/shoulder-generator/src/main/resources/template/Dao.java.vm b/shoulder-generator/src/main/resources/template/Dao.java.vm new file mode 100644 index 0000000..3ce5743 --- /dev/null +++ b/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 params); + + + +} diff --git a/shoulder-generator/src/main/resources/template/Dao.xml.vm b/shoulder-generator/src/main/resources/template/Dao.xml.vm new file mode 100644 index 0000000..0441939 --- /dev/null +++ b/shoulder-generator/src/main/resources/template/Dao.xml.vm @@ -0,0 +1,62 @@ + + + + + + + +#foreach($column in $columns) + +#end + + + + 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 + ) + + + + update ${tableName} + + #foreach($column in $columns) + + ${column.columnName} = #{${column.columnName}}, + + #end + + where id = #{id} + + + + delete from ${tableName} where id = #{id} + + + + + + #foreach($column in $columns) + + and t.${column.columnName} like concat('%', #{searchValue}, '%') + + #end + + + \ No newline at end of file diff --git a/shoulder-generator/src/main/resources/template/Entity.java.vm b/shoulder-generator/src/main/resources/template/Entity.java.vm new file mode 100644 index 0000000..892de82 --- /dev/null +++ b/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 + +} diff --git a/shoulder-generator/src/main/resources/template/Service.java.vm b/shoulder-generator/src/main/resources/template/Service.java.vm new file mode 100644 index 0000000..fceab32 --- /dev/null +++ b/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 params); + +} + diff --git a/shoulder-generator/src/main/resources/template/ServiceImpl.java.vm b/shoulder-generator/src/main/resources/template/ServiceImpl.java.vm new file mode 100644 index 0000000..1ac3a8b --- /dev/null +++ b/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 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(); + } + +} diff --git a/shoulder-generator/src/main/resources/template/index.html.vm b/shoulder-generator/src/main/resources/template/index.html.vm new file mode 100644 index 0000000..2167533 --- /dev/null +++ b/shoulder-generator/src/main/resources/template/index.html.vm @@ -0,0 +1,169 @@ +
+
+

${comments}

+ + 首页 + ${comments} + +
+
+
+ 搜索: +   +   + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/shoulder-notify-center/README.md b/shoulder-notify-center/README.md new file mode 100644 index 0000000..d0009ce --- /dev/null +++ b/shoulder-notify-center/README.md @@ -0,0 +1,28 @@ +# shoulder-通知推送中心 + +包含 +- 邮件推送 +- 短信推送 +- 站内消息推送 +- xx 推送... + + +## 系统架构 + +如图 ![消息中心架构](arch.png) + +改进: +- 若 `业务服务` 与 `消息中心` 采用 API 方式通信,可以提供`SDK`,简化调用,另推荐在此之上应添加业务网关,对上层业务屏蔽底层实现。 +- 若 `业务服务` 与 `消息中心` 采用 MQ 方式通信,一般来说,无需再使用网关隔离。 + + +TODO:考虑将各种推送方式集成,提供单体服务? +- 降低初学者的理解难度 +- 减少小型场景的资源占用 + + +---- + + + +站内消息:https://www.jianshu.com/p/c180e1510639 \ No newline at end of file diff --git a/shoulder-notify-center/arch.png b/shoulder-notify-center/arch.png new file mode 100644 index 0000000..bf1a406 Binary files /dev/null and b/shoulder-notify-center/arch.png differ diff --git a/shoulder-notify-center/email.md b/shoulder-notify-center/email.md new file mode 100644 index 0000000..d455e4d --- /dev/null +++ b/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 + + + diff --git a/shoulder-notify-center/pom.xml b/shoulder-notify-center/pom.xml new file mode 100644 index 0000000..e08dac2 --- /dev/null +++ b/shoulder-notify-center/pom.xml @@ -0,0 +1,22 @@ + + + + cn.itlym.platform + shoulder-platform + 1.0-SNAPSHOT + + + 4.0.0 + + cn.itlym.platform.notify + shoulder-notify-center + pom + + + shoulder-sms + + + + \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/README.md b/shoulder-notify-center/shoulder-sms/README.md new file mode 100644 index 0000000..f4fa71e --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/README.md @@ -0,0 +1,8 @@ +# shoulder-短信推送中心 + +- sms-api + - shoulder-短信推送中心接口、DTO +- sms-center + - +- shoulder-sms-client + - 快速开发工具包,使用者只需引入该 jar 包,即可获得与 `shoulder-短信推送中心` 通信的能力 \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/pom.xml b/shoulder-notify-center/shoulder-sms/pom.xml new file mode 100644 index 0000000..9c95a9a --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../../shoulder-platform-common/shoulder-platform-parent/pom.xml + + + cn.itlym.platform.notify + shoulder-sms + pom + 1.0-SNAPSHOT + + + sms-center + shoulder-sms-client + sms-api + + + + + + cn.itlym.platform.notify + sms-api + ${shoulder-platform.version} + + + cn.itlym.platform.notify + sms-center + ${shoulder-platform.version} + + + + + cn.itlym.platform.notify + shoulder-sms-client + ${shoulder-platform.version} + + + + + + \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/shoulder-sms-client/pom.xml b/shoulder-notify-center/shoulder-sms/shoulder-sms-client/pom.xml new file mode 100644 index 0000000..dc8c4e0 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/shoulder-sms-client/pom.xml @@ -0,0 +1,33 @@ + + + + cn.itlym.platform.notify + shoulder-sms + 1.0-SNAPSHOT + + 4.0.0 + + cn.itlym.platform.notify + shoulder-sms-client + 功能:向sms-center发送请求。供微服务内部,其他服务使用 + + + + org.springframework.boot + spring-boot-starter + true + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/shoulder-sms-client/src/main/java/cn/itlym/shoulder/platform/notify/sms/sdk/client/SmsClient.java b/shoulder-notify-center/shoulder-sms/shoulder-sms-client/src/main/java/cn/itlym/shoulder/platform/notify/sms/sdk/client/SmsClient.java new file mode 100644 index 0000000..e8452fc --- /dev/null +++ b/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 { + + +} diff --git a/shoulder-notify-center/shoulder-sms/shoulder-sms-client/src/main/java/cn/itlym/shoulder/platform/notify/sms/sdk/package-info.java b/shoulder-notify-center/shoulder-sms/shoulder-sms-client/src/main/java/cn/itlym/shoulder/platform/notify/sms/sdk/package-info.java new file mode 100644 index 0000000..9179010 --- /dev/null +++ b/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; \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-api/pom.xml b/shoulder-notify-center/shoulder-sms/sms-api/pom.xml new file mode 100644 index 0000000..4df7fdf --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-api/pom.xml @@ -0,0 +1,16 @@ + + + + cn.itlym.platform.notify + shoulder-sms + 1.0-SNAPSHOT + + 4.0.0 + + cn.itlym.platform.notify + sms-api + + + \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-center/pom.xml b/shoulder-notify-center/shoulder-sms/sms-center/pom.xml new file mode 100644 index 0000000..a6086b2 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/pom.xml @@ -0,0 +1,89 @@ + + + + cn.itlym.platform.notify + shoulder-sms + 1.0-SNAPSHOT + + 4.0.0 + + cn.itlym.platform.notify + sms-center + ${artifactId} + Shoulder平台-短信推送服务 + + + 1.6.2 + + + + + + cn.itlym.platform.notify + sms-api + + + + cn.itlym.platform + shoulder-sms-aliyun-spring-boot-starter + + + + + cn.itlym.platform + shoulder-platform-starter-micro + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + + org.springframework.boot + spring-boot-starter-mail + + + + com.sun.mail + javax.mail + ${javax.mail.version} + + + + + org.springframework.boot + spring-boot-starter-freemarker + + + + + + + + ${project.artifactId} + + + + ../../../dynamicConfig/config-${profile.active}.properties + + + + + src/main/resources + + **/* + + true + + + + + + + \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/SmsCenterStarter.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/SmsCenterStarter.java new file mode 100644 index 0000000..14e8814 --- /dev/null +++ b/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; + } + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/config/JmsConfig.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/config/JmsConfig.java new file mode 100644 index 0000000..c41eaac --- /dev/null +++ b/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(); + } + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/constant/EmailTemplateTypeEnum.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/constant/EmailTemplateTypeEnum.java new file mode 100644 index 0000000..8b681a3 --- /dev/null +++ b/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; + } + + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/EmailController.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/EmailController.java new file mode 100644 index 0000000..b0afb12 --- /dev/null +++ b/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); + } +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/SmsController.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/SmsController.java new file mode 100644 index 0000000..f7e300f --- /dev/null +++ b/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; + } + + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/TestController.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/controller/TestController.java new file mode 100644 index 0000000..3106933 --- /dev/null +++ b/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); + } +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/dto/EmailDTO.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/dto/EmailDTO.java new file mode 100644 index 0000000..4dfdf3b --- /dev/null +++ b/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 templateParam;// 自定义参数 + + + public EmailDTO() { + super(); + } + + public EmailDTO(String[] receiver_emails, String subject, String content, String template, + HashMap 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 getTemplateParam() { + return templateParam; + } + + public void setTemplateParam(HashMap templateParam) { + this.templateParam = templateParam; + } +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/dto/UiResult.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/dto/UiResult.java new file mode 100644 index 0000000..270cf1a --- /dev/null +++ b/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 { + + 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 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; + } +} \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/entity/EmailContentTemplate.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/entity/EmailContentTemplate.java new file mode 100644 index 0000000..89784cb --- /dev/null +++ b/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; + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/entity/EmailEntity.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/entity/EmailEntity.java new file mode 100644 index 0000000..d63f2c6 --- /dev/null +++ b/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; + } + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/EnumConverter.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/EnumConverter.java new file mode 100644 index 0000000..afa6d4e --- /dev/null +++ b/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> { + + @SuppressWarnings("unchecked") + private Class enumType; + + public EnumConverter(Class 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) 方法 + + // 尝试使用标识字段匹配 + + // 尝试使用标识方法匹配 + } + + +} \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/EnumDeserializer.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/EnumDeserializer.java new file mode 100644 index 0000000..93df8d3 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/EnumDeserializer.java @@ -0,0 +1,80 @@ +package cn.itlym.shoulder.platform.notify.sms.enums; + +import cn.hutool.core.util.ReflectUtil; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +@Component +public class EnumDeserializer extends StdDeserializer> { + + private static final Logger log = LoggerFactory.getLogger(EnumDeserializer.class); + + /** + * 约定方法名,当且仅当枚举中存在 public static <自身> from(String str) 的方法时,才能转换 + */ + private final String stringToEnumMethodName; + + private final String indexFieldName; + + public EnumDeserializer() { + this(null, null); + } + + public EnumDeserializer(String stringToEnumMethodName, String indexFieldName) { + super(Enum.class); + String defaultStringToEnumMethodName = "from"; + String defaultIndexFieldName = "code"; + this.stringToEnumMethodName = StringUtils.isEmpty(stringToEnumMethodName) ? defaultStringToEnumMethodName : stringToEnumMethodName; + this.indexFieldName = StringUtils.isEmpty(stringToEnumMethodName) ? defaultIndexFieldName : indexFieldName; + } + + @Override + public Enum deserialize(JsonParser p, DeserializationContext context) throws IOException { + JsonToken token = p.getCurrentToken(); + String value = null; + while (!token.isStructEnd()) { + if (indexFieldName.equals(p.getText())) { + p.nextToken(); + value = p.getValueAsString(); + } else { + p.nextToken(); + } + token = p.getCurrentToken(); + } + if (value == null || "".equals(value)) { + return null; + } + + Object obj = p.getCurrentValue(); + if (obj == null) { + return null; + } + Field field = ReflectUtil.getField(obj.getClass(), p.getCurrentName()); + //找不到字段 + if (field == null) { + return null; + } + Class fieldType = field.getType(); + try { + Method method = fieldType.getMethod(stringToEnumMethodName, String.class); + return (Enum) method.invoke(null, value); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e) { + log.warn("Deserialize enum fail! Can't invoke the method named '" + stringToEnumMethodName + "'", e); + return null; + } + } + +} + + diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/MvcConfigure.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/MvcConfigure.java new file mode 100644 index 0000000..eb8cc35 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/MvcConfigure.java @@ -0,0 +1,19 @@ +package cn.itlym.shoulder.platform.notify.sms.enums; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class MvcConfigure implements WebMvcConfigurer { + + @Autowired + private MyEnumConverterFactory myEnumConverterFactory; + + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverterFactory(myEnumConverterFactory); + } + +} \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/MyEnum.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/MyEnum.java new file mode 100644 index 0000000..17c3f89 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/MyEnum.java @@ -0,0 +1,22 @@ +package cn.itlym.shoulder.platform.notify.sms.enums; + +public enum MyEnum { + + /** + * + */ + V1, + V2, + + ; + + public static MyEnum from(String source) { + switch (source) { + case "2": + return V2; + default: + return V1; + } + } + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/MyEnumConverterFactory.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/MyEnumConverterFactory.java new file mode 100644 index 0000000..7035dbb --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/enums/MyEnumConverterFactory.java @@ -0,0 +1,13 @@ +package cn.itlym.shoulder.platform.notify.sms.enums; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.stereotype.Component; + +@Component +public class MyEnumConverterFactory implements ConverterFactory { + @Override + public Converter getConverter(Class targetType) { + return (Converter) new EnumConverter(targetType); + } +} \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/notify/consumer/EmailConsumer.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/notify/consumer/EmailConsumer.java new file mode 100644 index 0000000..826a40c --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/notify/consumer/EmailConsumer.java @@ -0,0 +1,19 @@ +package cn.itlym.shoulder.platform.notify.sms.notify.consumer; + +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +/** + * @author lym + */ +@Component +public class EmailConsumer { + + private static final String DESTINATION = "sys.mail"; + + @JmsListener(destination = DESTINATION) + public void processMessage(String content) { + + } + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/notify/producer/EmailProducer.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/notify/producer/EmailProducer.java new file mode 100644 index 0000000..658fecf --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/notify/producer/EmailProducer.java @@ -0,0 +1,19 @@ +package cn.itlym.shoulder.platform.notify.sms.notify.producer; + +import cn.itlym.shoulder.platform.notify.sms.dto.EmailDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jms.core.JmsTemplate; + +/** + * @author lym + */ +//@Component +public class EmailProducer { + + @Autowired + private JmsTemplate jmsTemplate; + + public void produce(EmailDTO email) { + + } +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/package-info.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/package-info.java new file mode 100644 index 0000000..8712940 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/package-info.java @@ -0,0 +1,8 @@ +/** + * 推送服务中心 + * - 发送短信 + * - 发送电子邮件 + * + * @author lym + */ +package cn.itlym.shoulder.platform.notify.sms; \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/param/SmsBatchParam.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/param/SmsBatchParam.java new file mode 100644 index 0000000..cac6073 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/param/SmsBatchParam.java @@ -0,0 +1,114 @@ +package cn.itlym.shoulder.platform.notify.sms.param; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * 阿里云 SMS 短信批量模板. + * + * @author lym + */ +public class SmsBatchParam { + + /** + * 模板id + */ + private String templateCode; + /** + * 每条短信的参数 + */ + private List> templateParams; + /** + * 手机号 + */ + private List phoneNumbers; + + public SmsBatchParam() { + } + + public SmsBatchParam(String templateCode, List> templateParams, List phoneNumbers) { + this.templateCode = templateCode; + this.templateParams = templateParams; + this.phoneNumbers = phoneNumbers; + } + + private SmsBatchParam(Builder builder) { + setTemplateCode(builder.templateCode); + setTemplateParams(builder.templateParams); + setPhoneNumbers(builder.phoneNumbers); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static Builder newBuilder(SmsBatchParam copy) { + Builder builder = new Builder(); + builder.templateCode = copy.getTemplateCode(); + builder.templateParams = copy.getTemplateParams(); + builder.phoneNumbers = copy.getPhoneNumbers(); + return builder; + } + + public String getTemplateCode() { + return templateCode; + } + + public void setTemplateCode(String templateCode) { + this.templateCode = templateCode; + } + + public List> getTemplateParams() { + return templateParams; + } + + public void setTemplateParams(List> templateParams) { + this.templateParams = templateParams; + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + + public static final class Builder { + private String templateCode; + private List> templateParams; + private List phoneNumbers; + + private Builder() { + } + + public Builder templateCode(String templateCode) { + this.templateCode = templateCode; + return this; + } + + public Builder templateParams(List> templateParams) { + this.templateParams = templateParams; + return this; + } + + public Builder phoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + return this; + } + + public Builder phoneNumber(String phoneNumbers) { + if (null == this.phoneNumbers) { + this.phoneNumbers = new LinkedList<>(); + } + this.phoneNumbers.add(phoneNumbers); + return this; + } + + public SmsBatchParam build() { + return new SmsBatchParam(this); + } + } +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/param/SmsParam.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/param/SmsParam.java new file mode 100644 index 0000000..6282f08 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/param/SmsParam.java @@ -0,0 +1,128 @@ +package cn.itlym.shoulder.platform.notify.sms.param; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * 阿里云 SMS 短信模板. + * + * @author lym + */ +public class SmsParam { + + /** + * 模板名称,在阿里云控制台上添加模板时,会有个模板名称 + * https://dysms.console.aliyun.com/dysms.htm#/domestic/text/template + */ + private String templateCode; + + /** + * 模板参数,用于填充模板 + */ + private Map templateParam; + /** + * 手机号 + */ + private List phoneNumbers; + + public SmsParam() { + } + + private SmsParam(Builder builder) { + templateCode = builder.templateCode; + templateParam = builder.templateParam; + phoneNumbers = builder.phoneNumbers; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static Builder newBuilder(SmsParam copy) { + Builder builder = new Builder(); + builder.templateCode = copy.getTemplateCode(); + builder.templateParam = copy.getTemplateParam(); + builder.phoneNumbers = copy.getPhoneNumbers(); + return builder; + } + + public String getTemplateCode() { + return templateCode; + } + + public void setTemplateCode(String templateCode) { + this.templateCode = templateCode; + } + + public Map getTemplateParam() { + return templateParam; + } + + public void setTemplateParam(Map templateParam) { + this.templateParam = templateParam; + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + public static class Builder { + private String templateCode; + private Map templateParam; + private List phoneNumbers; + + private Builder() { + } + + /** + * 添加短信模板参数. + * + * @param key the key + * @param value the value + * @return this + */ + public Builder addTemplateParam(final String key, final String value) { + if (null == this.templateParam) { + this.templateParam = new HashMap<>(3); + } + + this.templateParam.put(key, value); + return this; + } + + public Builder templateCode(String templateCode) { + this.templateCode = templateCode; + return this; + } + + public Builder templateParam(Map templateParam) { + this.templateParam = templateParam; + return this; + } + + public Builder phoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + return this; + } + + public Builder phoneNumber(String phoneNumbers) { + if (null == this.phoneNumbers) { + this.phoneNumbers = new LinkedList<>(); + } + this.phoneNumbers.add(phoneNumbers); + return this; + } + + public SmsParam build() { + return new SmsParam(this); + } + } + + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/repository/MailRepository.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/repository/MailRepository.java new file mode 100644 index 0000000..43feab3 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/repository/MailRepository.java @@ -0,0 +1,10 @@ +package cn.itlym.shoulder.platform.notify.sms.repository; + +/** + * 邮件管理 + * 创建者 小柒2012 + * 创建时间 2017年9月9日 + */ +//public interface MailRepository extends JpaRepository { + +//} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/EmailSender.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/EmailSender.java new file mode 100644 index 0000000..603f907 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/EmailSender.java @@ -0,0 +1,10 @@ +package cn.itlym.shoulder.platform.notify.sms.service; + + +import cn.itlym.shoulder.platform.notify.sms.dto.EmailDTO; + +public interface EmailSender { + + void send(EmailDTO mail) throws Exception; + +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/EmailService.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/EmailService.java new file mode 100644 index 0000000..1f3c05f --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/EmailService.java @@ -0,0 +1,35 @@ +package cn.itlym.shoulder.platform.notify.sms.service; + + +import cn.itlym.shoulder.platform.notify.sms.dto.EmailDTO; +import cn.itlym.shoulder.platform.notify.sms.dto.UiResult; + +public interface EmailService { + /** + * 纯文本 + */ + void send(EmailDTO mail) throws Exception; + + /** + * 富文本 + */ + void sendHtml(EmailDTO mail) throws Exception; + + /** + * 模版发送 freemarker + */ + void sendFreemarker(EmailDTO mail) throws Exception; + + /** + * 模版发送 thymeleaf(弃用、需要配合模板) + */ + void sendThymeleaf(EmailDTO mail) throws Exception; + + /** + * 队列 + */ + void sendQueue(EmailDTO mail) throws Exception; + + + UiResult listMail(EmailDTO mail); +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/SmsService.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/SmsService.java new file mode 100644 index 0000000..043b142 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/SmsService.java @@ -0,0 +1,21 @@ +package cn.itlym.shoulder.platform.notify.sms.service; + +import cn.itlym.shoulder.platform.notify.sms.param.SmsBatchParam; +import cn.itlym.shoulder.platform.notify.sms.param.SmsParam; + +public interface SmsService { + /** + * 发送短信 + * + * @param smsParam 参数 + * @return 是否成功 + */ + boolean sendSms(SmsParam smsParam); + + /** + * 批量发送短信 + * + * @param smsBatchParam 参数 + */ + boolean sendSms(SmsBatchParam smsBatchParam); +} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/impl/EmailSenderImpl.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/impl/EmailSenderImpl.java new file mode 100644 index 0000000..4c3f5d2 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/impl/EmailSenderImpl.java @@ -0,0 +1,35 @@ +package cn.itlym.shoulder.platform.notify.sms.service.impl; + +import cn.itlym.shoulder.platform.notify.sms.dto.EmailDTO; +import cn.itlym.shoulder.platform.notify.sms.service.EmailSender; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +@Service +public class EmailSenderImpl implements EmailSender { + + static { + // 避免在linux下利用javamail1.4.4发邮件带附件,附件名过长而被被截断,导致接收端解析失败的异常 + System.setProperty("mail.mime.splitlongparameters", "false"); + } + + @Value("${spring.mail.username}") + public String USER_NAME;//发送者 + + @Autowired + private JavaMailSender mailSender;//执行者 + + @Override + public void send(EmailDTO mail) throws Exception { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(USER_NAME); + message.setTo(mail.getReceiver_emails()); + message.setSubject(mail.getSubject()); + message.setText(mail.getContent()); + mailSender.send(message); + } + +} \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/impl/SmsServiceImpl.java b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/impl/SmsServiceImpl.java new file mode 100644 index 0000000..cb046ef --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/java/cn/itlym/shoulder/platform/notify/sms/service/impl/SmsServiceImpl.java @@ -0,0 +1,78 @@ +package cn.itlym.shoulder.platform.notify.sms.service.impl; + +import cn.itlym.shoulder.platform.notify.sms.param.SmsBatchParam; +import cn.itlym.shoulder.platform.notify.sms.param.SmsParam; +import cn.itlym.shoulder.platform.notify.sms.service.SmsService; +import org.shoulder.sms.aliyun.client.AliSmsClient; +import org.shoulder.sms.aliyun.dto.param.AliSmsBatchParam; +import org.shoulder.sms.aliyun.dto.param.AliSmsParam; +import org.shoulder.sms.aliyun.exception.AliSmsException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author lym + */ +@Service +public class SmsServiceImpl implements SmsService { + + private final AliSmsClient aliSmsClient; + + private final String signName; + + public SmsServiceImpl(AliSmsClient smsClient, @Value("智汇信息") String signName) { + this.aliSmsClient = smsClient; + this.signName = signName; + } + + + @Override + public boolean sendSms(SmsParam smsParam) { + + try { + return aliSmsClient.send(convert(smsParam)); + } catch (AliSmsException e) { + // todo + return false; + } + } + + @Override + public boolean sendSms(SmsBatchParam smsBatchParam) { + try { + return aliSmsClient.send(convert(smsBatchParam)); + } catch (AliSmsException e) { + // todo + return false; + } + } + + // --------------------------- convert ------------------------- + + public AliSmsParam convert(SmsParam smsParam) { + return AliSmsParam.newBuilder() + .phoneNumbers(smsParam.getPhoneNumbers()) + .templateCode(smsParam.getTemplateCode()) + .templateParam(smsParam.getTemplateParam()) + .signName(signName) + .build(); + } + + public AliSmsBatchParam convert(SmsBatchParam smsBatchParam) { + int batchSize = smsBatchParam.getPhoneNumbers().size(); + List signNames = new ArrayList<>(batchSize); + for (int i = 0; i < batchSize; i++) { + signNames.add(signName); + } + return AliSmsBatchParam.newBuilder() + .phoneNumbers(smsBatchParam.getPhoneNumbers()) + .signNames(signNames) + .templateCode(smsBatchParam.getTemplateCode()) + .templateParams(smsBatchParam.getTemplateParams()) + .build(); + } + +} \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/resources/banner.txt b/shoulder-notify-center/shoulder-sms/sms-center/src/main/resources/banner.txt new file mode 100644 index 0000000..1535221 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/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} ___) | | | |___) |_____| |__| __/ | | | || __/ | ${AnsiColor.CYAN} / / / / / / +${AnsiColor.CYAN}|____/|_| |_|\___/ \__,_|_|\__,_|\___|_| ${AnsiColor.BRIGHT_YELLOW} |____/|_| |_|____/ \____\___|_| |_|\__\___|_| ${AnsiColor.CYAN} / / / / / / +${AnsiColor.CYAN}==================================================================================================${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} diff --git a/shoulder-notify-center/shoulder-sms/sms-center/src/main/resources/bootstrap.yml b/shoulder-notify-center/shoulder-sms/sms-center/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..d94fb53 --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms-center/src/main/resources/bootstrap.yml @@ -0,0 +1,60 @@ +# 先从环境变量里取,若不存在,则以 maven 打包时的配置为准 +shoulder: + nacos: + ip: ${NACOS_IP:@nacos.ip@} + port: ${NACOS_PORT:@nacos.port@} + namespace: ${NACOS_ID:@nacos.namespace@} + +# 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: + #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: sms-center.yml + refresh: true + - dataId: third-account.yml # 对接第三方需要的信息 + refresh: false + - 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:} + +boot: + admin: + client: + url: localhost:12365 + #username: + #password: + instance: + prefer-ip: true + service-url: localhost8080 \ No newline at end of file diff --git a/shoulder-notify-center/shoulder-sms/sms.md b/shoulder-notify-center/shoulder-sms/sms.md new file mode 100644 index 0000000..aef871b --- /dev/null +++ b/shoulder-notify-center/shoulder-sms/sms.md @@ -0,0 +1,23 @@ +# 短信推送服务 + +## 需求分析 + +希望邮件推送服务能提供的功能 + +- 可编程、可扩展(通过程序对接、改造) + - 能够通过开发在程序逻辑或者管理界面中自动触发发送 + - 支持邮件发送验证码和邮件营销推送 + - 能够支持HTML的邮件内容,而HTML内容能够随时随地进行修改,方便美工和开发去调整 + +- 可观测 + - 验证类邮件能够支持IP统计、次数统计,能够进行时间限制、防止恶意发送 + - 推送类邮件能够支持统计发送数量、发送成功率等反馈数据 + +- 推送效率 + - 验证邮件要能在5-10秒内发送成功,到达率高 + +- 用户友好 + - 用户可以退订推送类邮件 + +## 功能设计 + diff --git a/shoulder-notify-center/web.md b/shoulder-notify-center/web.md new file mode 100644 index 0000000..ba03a88 --- /dev/null +++ b/shoulder-notify-center/web.md @@ -0,0 +1,8 @@ +# 短信推送服务 + +## 需求分析 + +- 页面中的红点,消息标题,新增消息实时提醒 + +## 功能设计 + diff --git a/shoulder-pay-center/pom.xml b/shoulder-pay-center/pom.xml new file mode 100644 index 0000000..0ae801e --- /dev/null +++ b/shoulder-pay-center/pom.xml @@ -0,0 +1,15 @@ + + + + shoulder-platform + cn.itlym.platform + 1.0-SNAPSHOT + + 4.0.0 + + shoulder-pay-center + + + \ No newline at end of file diff --git a/shoulder-platform-common/README.md b/shoulder-platform-common/README.md new file mode 100644 index 0000000..704e261 --- /dev/null +++ b/shoulder-platform-common/README.md @@ -0,0 +1,4 @@ +shoulder-platform-common + +统一平台的技术栈选型,项目中一般不应引入除了本模块中以外的能力,以保证平台的安全与易维护 +(由于仓库较多,未创新新仓库维护) diff --git a/shoulder-platform-common/pom.xml b/shoulder-platform-common/pom.xml new file mode 100644 index 0000000..587e383 --- /dev/null +++ b/shoulder-platform-common/pom.xml @@ -0,0 +1,48 @@ + + + + shoulder-platform + cn.itlym.platform + 1.0-SNAPSHOT + + 4.0.0 + + shoulder-platform-common + pom + + + shoulder-platform-parent + shoulder-platform-starter-db + shoulder-platform-starter-mq + shoulder-platform-starter-config-client + shoulder-platform-starter-discovery-client + shoulder-platform-starter-rpc-client + shoulder-platform-starter-rpc-server + shoulder-platform-starter-micro + shoulder-platform-starter-job + shoulder-platform-starter-monitor + shoulder-platform-starter-trace + + shoulder-sms-aliyun-spring-boot-starter + + + + 0.4-SNAPSHOT + 0.1-SNAPSHOT + + + + + + cn.itlym + shoulder-dependencies + ${shoulder.version} + pom + import + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-parent/README.md b/shoulder-platform-common/shoulder-platform-parent/README.md new file mode 100644 index 0000000..581b454 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-parent/README.md @@ -0,0 +1,3 @@ +# shoulder-platform-parent + +管理 shoulder-platform 通用部件的版本号,微服务中常用的构建配置 \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-parent/pom.xml b/shoulder-platform-common/shoulder-platform-parent/pom.xml new file mode 100644 index 0000000..779beac --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-parent/pom.xml @@ -0,0 +1,322 @@ + + + + cn.itlym + shoulder-parent + 0.4-SNAPSHOT + + 4.0.0 + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + pom + + https://github.com/ChinaLym/Shoulder-Platform + + + + 0.4-SNAPSHOT + 1.0-SNAPSHOT + + 1.0-SNAPSHOT + + + + + + 2.2.1.RELEASE + 1.3.2 + + + 1.2.73 + + 7.0.2 + + + 2.3.0 + 2.3.0 + + + + + 3.2.0 + 1.4.19 + shoulder + 2.2.8.RELEASE + + + ${project.artifactId} + shoulder platform 的通用父类,管理了平台的技术选型和通用配置 + + + lym + cn_lym@foxmail.com + + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + + + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + ${spring-cloud-alibaba.version} + pom + import + + + + + cn.itlym.platform + shoulder-platform-starter-config-client + ${shoulder-platform.version} + + + cn.itlym.platform + shoulder-platform-starter-db + ${shoulder-platform.version} + + + cn.itlym.platform + shoulder-platform-starter-discovery-client + ${shoulder-platform.version} + + + cn.itlym.platform + shoulder-platform-starter-job + ${shoulder-platform.version} + + + cn.itlym.platform + shoulder-platform-starter-rpc-client + ${shoulder-platform.version} + + + cn.itlym.platform + shoulder-platform-starter-rpc-server + ${shoulder-platform.version} + + + cn.itlym.platform + shoulder-platform-starter-monitor + ${shoulder-platform.version} + + + cn.itlym.platform + shoulder-platform-starter-mq + ${shoulder-platform.version} + + + cn.itlym.platform + shoulder-platform-starter-trace + ${shoulder-platform.version} + + + + cn.itlym.platform + shoulder-platform-starter-micro + ${shoulder-platform.version} + + + + cn.itlym.platform + shoulder-sms-aliyun-spring-boot-starter + ${shoulder-sms-aliyun.version} + + + + + + + com.alibaba + fastjson + ${fastjson.version} + + + + + + com.alibaba.nacos + nacos-client + ${nacos.client.version} + + + + de.codecentric + spring-boot-admin-starter-client + ${spring-boot-admin-starter-client.version} + + + + io.micrometer + micrometer-registry-prometheus + ${micrometer-registry-prometheus.version} + + + + io.minio + minio + ${minio.version} + + + + + + + + + + + + + dev + + dev + + + true + + + + + + test + + test + + + + + + prod + + prod + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + false + + @ + + + ${project.build.sourceEncoding} + + + pem + pfx + p12 + key + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-maven-plugin.version} + + + + repackage + + + + + + + + com.spotify + dockerfile-maven-plugin + ${dockerfile-maven-plugin.version} + + ${docker.image.prefix}/${project.artifactId} + ${shoulder-platform.version} + + target/${project.build.finalName}.jar + + + + + + + + + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-config-client/README.md b/shoulder-platform-common/shoulder-platform-starter-config-client/README.md new file mode 100644 index 0000000..389234c --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-config-client/README.md @@ -0,0 +1,3 @@ +# shoulder-platform-starter-config-client + +快速对接配置中心 \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-config-client/pom.xml b/shoulder-platform-common/shoulder-platform-starter-config-client/pom.xml new file mode 100644 index 0000000..f313f5b --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-config-client/pom.xml @@ -0,0 +1,53 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-config-client + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + com.alibaba + fastjson + + + com.google.guava + guava + + + com.alibaba.nacos + nacos-client + + + + + + com.alibaba + fastjson + + + + com.google.guava + guava + + + + com.alibaba.nacos + nacos-client + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-db/README.md b/shoulder-platform-common/shoulder-platform-starter-db/README.md new file mode 100644 index 0000000..8f9f01a --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-db/README.md @@ -0,0 +1,3 @@ +# shoulder-platform-starter-db + +包含如 `mysql` 客户端、连接池、数据库监控等快速对接数据库 \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-db/pom.xml b/shoulder-platform-common/shoulder-platform-starter-db/pom.xml new file mode 100644 index 0000000..131bc5d --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-db/pom.xml @@ -0,0 +1,94 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-db + + + + + cn.itlym + shoulder-data-db + + + + cn.itlym + shoulder-starter + + + + + com.baomidou + mybatis-plus-boot-starter + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + mysql + mysql-connector-java + + + + + com.github.chris2018998 + BeeCP + + + + + + + p6spy + p6spy + + + + + + + org.mybatis + mybatis-typehandlers-jsr310 + 1.0.2 + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + + + + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-db/src/main/java/cn/itlym/shoulder/platform/db/beecp/BeeCpDataSourceAutoConfiguration.java b/shoulder-platform-common/shoulder-platform-starter-db/src/main/java/cn/itlym/shoulder/platform/db/beecp/BeeCpDataSourceAutoConfiguration.java new file mode 100644 index 0000000..0e451d9 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-db/src/main/java/cn/itlym/shoulder/platform/db/beecp/BeeCpDataSourceAutoConfiguration.java @@ -0,0 +1,27 @@ +package cn.itlym.shoulder.platform.db.beecp; + +import org.springframework.beans.BeansException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +/** + * beecp 数据源自动配置(beecp 官方提供的 starter 有 bug 因为其 provide 属性会导致编译失败) + * + * @author lym + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(cn.beecp.BeeDataSource.class) +@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "cn.beecp.BeeDataSource", matchIfMissing = true) +public class BeeCpDataSourceAutoConfiguration { + + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DataSource beeDataSource() throws BeansException { + return new cn.beecp.BeeDataSource(); + } +} \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-db/src/main/java/cn/itlym/shoulder/platform/db/p6spy/P6spyLogFormat.java b/shoulder-platform-common/shoulder-platform-starter-db/src/main/java/cn/itlym/shoulder/platform/db/p6spy/P6spyLogFormat.java new file mode 100644 index 0000000..8ed8ac6 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-db/src/main/java/cn/itlym/shoulder/platform/db/p6spy/P6spyLogFormat.java @@ -0,0 +1,17 @@ +package cn.itlym.shoulder.platform.db.p6spy; + +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; +import org.springframework.util.StringUtils; + +/** + * P6spy SQL 日志格式化 + * + * @author lym + */ +public class P6spyLogFormat implements MessageFormattingStrategy { + + @Override + public String formatMessage(final int connectionId, final String now, final long elapsed, final String category, final String prepared, final String sql, final String url) { + return !StringUtils.isEmpty(sql) ? "Execute SQL:" + sql.replaceAll("[\\s]+", " ") : null; + } +} \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-db/src/main/java/cn/itlym/shoulder/platform/db/p6spy/P6spyLogger.java b/shoulder-platform-common/shoulder-platform-starter-db/src/main/java/cn/itlym/shoulder/platform/db/p6spy/P6spyLogger.java new file mode 100644 index 0000000..81d56f9 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-db/src/main/java/cn/itlym/shoulder/platform/db/p6spy/P6spyLogger.java @@ -0,0 +1,57 @@ +package cn.itlym.shoulder.platform.db.p6spy; + +import com.p6spy.engine.logging.Category; +import com.p6spy.engine.spy.appender.FormattedLogger; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +/** + * P6spy日志实现 + * + * @author lym + */ +@Slf4j +public class P6spyLogger extends FormattedLogger { + + @Override + public void logException(Exception e) { + log.info("", e); + } + + @Override + public void logText(String text) { + log.info(text); + } + + @Override + public void logSQL(int connectionId, String now, long elapsed, Category category, String prepared, String sql, String url) { + String msg = strategy.formatMessage(connectionId, now, elapsed, + category.toString(), prepared, sql, url); + + if (StringUtils.isEmpty(msg)) { + return; + } + if (Category.ERROR.equals(category)) { + log.error(msg); + } else if (Category.WARN.equals(category)) { + log.warn(msg); + } else if (Category.DEBUG.equals(category)) { + log.debug(msg); + } else { + log.info(msg); + } + } + + @Override + public boolean isCategoryEnabled(Category category) { + if (Category.ERROR.equals(category)) { + return log.isErrorEnabled(); + } else if (Category.WARN.equals(category)) { + return log.isWarnEnabled(); + } else if (Category.DEBUG.equals(category)) { + return log.isDebugEnabled(); + } else { + return log.isInfoEnabled(); + } + } +} \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-db/src/main/resources/META-INF/spring.factories b/shoulder-platform-common/shoulder-platform-starter-db/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..81d9f56 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-db/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +cn.itlym.shoulder.platform.db.beecp.BeeCpDataSourceAutoConfiguration \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-db/src/main/resources/spy.properties b/shoulder-platform-common/shoulder-platform-starter-db/src/main/resources/spy.properties new file mode 100644 index 0000000..def7ce5 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-db/src/main/resources/spy.properties @@ -0,0 +1,205 @@ +# P6Spy http://p6spy.github.io/p6spy/2.0/configandusage.html +# module.logԱããP6SPYκãã +module.log=com.p6spy.engine.logging.P6LogFactory +#module.outage=com.p6spy.engine.outage.P6OutageFactory +#modulelist=com.p6spy.engine.spy.P6SpyFactory,com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory +################################################################ +# CORE (P6SPY) PROPERTIES # +################################################################ +# A comma separated list of JDBC drivers to load and register. +# (default is empty) +# +# Note: This is normally only needed when using P6Spy in an +# application server environment with a JNDI data source or when +# using a JDBC driver that does not implement the JDBC 4.0 API +# (specifically automatic registration). +driverlist=com.mysql.cj.jdbc.Driver +# for flushing per statement +# (default is false) +autoflush=true +# ڸʽ Java's SimpleDateFormat routine. +dateformat=yyyy-MM-dd HH:mm:ss:SSS +# prints a stack trace for every statement logged +#stacktrace=false +# if stacktrace=true, specifies the stack trace to print +#stacktraceclass= +# determines if property file should be reloaded +# Please note: reload means forgetting all the previously set +# settings (even those set during runtime - via JMX) +# and starting with the clean table +# (default is false) +#reloadproperties=false +# determines how often should be reloaded in seconds +# (default is 60) +#reloadpropertiesinterval=60 +# specifies the appender to use for logging +# Please note: reload means forgetting all the previously set +# settings (even those set during runtime - via JMX) +# and starting with the clean table +# (only the properties read from the configuration file) +# (default is com.p6spy.engine.spy.appender.FileLogger) +#appender=com.p6spy.engine.spy.appender.Slf4JLogger +appender=com.p6spy.engine.spy.appender.StdoutLogger +#appender=com.p6spy.engine.spy.appender.FileLogger +# name of logfile to use, note Windows users should make sure to use forward slashes in their pathname (e:/test/spy.log) +# (used for com.p6spy.engine.spy.appender.FileLogger only) +# (default is spy.log) +logfile=spy.log +#logfile=D:\\springboot.log +# append to the p6spy log file. if this is set to false the +# log file is truncated every time. (file logger only) +# (default is true) +#append=true +# class to use for formatting log messages (default is: com.p6spy.engine.spy.appender.SingleLineFormat) +logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat +# Զsql logʽ +#logMessageFormat=cn.infomany.P6SpyLogger +# Custom log message format used ONLY IF logMessageFormat is set to com.p6spy.engine.spy.appender.CustomLineFormat +# default is %(currentTime)|%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine) +# Available placeholders are: +# %(connectionId) the id of the connection +# %(currentTime) the current time expressing in milliseconds +# %(executionTime) the time in milliseconds that the operation took to complete +# %(category) the category of the operation +# %(effectiveSql) the SQL statement as submitted to the driver +# %(effectiveSqlSingleLine) the SQL statement as submitted to the driver, with all new lines removed +# %(sql) the SQL statement with all bind variables replaced with actual values +# %(sqlSingleLine) the SQL statement with all bind variables replaced with actual values, with all new lines removed +#customLogMessageFormat=%(currentTime)|%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine) +# format that is used for logging of the java.util.Date implementations (has to be compatible with java.text.SimpleDateFormat) +# (default is yyyy-MM-dd'T'HH:mm:ss.SSSZ) +#databaseDialectDateFormat=yyyy-MM-dd'T'HH:mm:ss.SSSZ +# format that is used for logging of the java.sql.Timestamp implementations (has to be compatible with java.text.SimpleDateFormat) +# (default is yyyy-MM-dd'T'HH:mm:ss.SSSZ) +#databaseDialectTimestampFormat=yyyy-MM-dd'T'HH:mm:ss.SSSZ +# format that is used for logging booleans, possible values: boolean, numeric +# (default is boolean) +#databaseDialectBooleanFormat=boolean +# whether to expose options via JMX or not +# (default is true) +#jmx=true +# if exposing options via jmx (see option: jmx), what should be the prefix used? +# jmx naming pattern constructed is: com.p6spy(.)?:name= +# please note, if there is already such a name in use it would be unregistered first (the last registered wins) +# (default is none) +#jmxPrefix= +# if set to true, the execution time will be measured in nanoseconds as opposed to milliseconds +# (default is false) +#useNanoTime=false +################################################################# +# DataSource replacement # +# # +# Replace the real DataSource class in your application server # +# configuration with the name com.p6spy.engine.spy.P6DataSource # +# (that provides also connection pooling and xa support). # +# then add the JNDI name and class name of the real # +# DataSource here # +# # +# Values set in this item cannot be reloaded using the # +# reloadproperties variable. Once it is loaded, it remains # +# in memory until the application is restarted. # +# # +################################################################# +#realdatasource=/RealMySqlDS +#realdatasourceclass=com.mysql.jdbc.jdbc2.optional.MysqlDataSource +################################################################# +# DataSource properties # +# # +# If you are using the DataSource support to intercept calls # +# to a DataSource that requires properties for proper setup, # +# define those properties here. Use name value pairs, separate # +# the name and value with a semicolon, and separate the # +# pairs with commas. # +# # +# The example shown here is for mysql # +# # +################################################################# +#realdatasourceproperties=port;3306,serverName;myhost,databaseName;jbossdb,foo;bar +################################################################# +# JNDI DataSource lookup # +# # +# If you are using the DataSource support outside of an app # +# server, you will probably need to define the JNDI Context # +# environment. # +# # +# If the P6Spy code will be executing inside an app server then # +# do not use these properties, and the DataSource lookup will # +# use the naming context defined by the app server. # +# # +# The two standard elements of the naming environment are # +# jndicontextfactory and jndicontextproviderurl. If you need # +# additional elements, use the jndicontextcustom property. # +# You can define multiple properties in jndicontextcustom, # +# in name value pairs. Separate the name and value with a # +# semicolon, and separate the pairs with commas. # +# # +# The example shown here is for a standalone program running on # +# a machine that is also running JBoss, so the JNDI context # +# is configured for JBoss (3.0.4). # +# # +# (by default all these are empty) # +################################################################# +#jndicontextfactory=org.jnp.interfaces.NamingContextFactory +#jndicontextproviderurl=localhost:1099 +#jndicontextcustom=java.naming.factory.url.pkgs;org.jboss.naming:org.jnp.interfaces +#jndicontextfactory=com.ibm.websphere.naming.WsnInitialContextFactory +#jndicontextproviderurl=iiop://localhost:900 +################################################################ +# P6 LOGGING SPECIFIC PROPERTIES # +################################################################ +# filter what is logged +# please note this is a precondition for usage of: include/exclude/sqlexpression +# (default is false) +#filter=false +# comma separated list of strings to include +# please note that special characters escaping (used in java) has to be done for the provided regular expression +# (default is empty) +#include= +# comma separated list of strings to exclude +# (default is empty) +#exclude= +# sql expression to evaluate if using regex +# please note that special characters escaping (used in java) has to be done for the provided regular expression +# (default is empty) +#sqlexpression= +#list of categories to exclude: error, info, batch, debug, statement, +#commit, rollback, result and resultset are valid values +# (default is info,debug,result,resultset,batch) +#excludecategories=info,debug,result,resultset,batch +#whether the binary values (passed to DB or retrieved ones) should be logged with placeholder: [binary] or not. +# (default is false) +#excludebinary=false +# Execution threshold applies to the standard logging of P6Spy. +# While the standard logging logs out every statement +# regardless of its execution time, this feature puts a time +# condition on that logging. Only statements that have taken +# longer than the time specified (in milliseconds) will be +# logged. This way it is possible to see only statements that +# have exceeded some high water mark. +# This time is reloadable. +# +# executionThreshold=integer time (milliseconds) +# (default is 0) +#executionThreshold= +################################################################ +# P6 OUTAGE SPECIFIC PROPERTIES # +################################################################ +# Outage Detection +# +# This feature detects long-running statements that may be indicative of +# a database outage problem. If this feature is turned on, it will log any +# statement that surpasses the configurable time boundary during its execution. +# When this feature is enabled, no other statements are logged except the long +# running statements. The interval property is the boundary time set in seconds. +# For example, if this is set to 2, then any statement requiring at least 2 +# seconds will be logged. Note that the same statement will continue to be logged +# for as long as it executes. So if the interval is set to 2, and the query takes +# 11 seconds, it will be logged 5 times (at the 2, 4, 6, 8, 10 second intervals). +# +# outagedetection=true|false +# outagedetectioninterval=integer time (seconds) +# +# (default is false) +#outagedetection=false +# (default is 60) +#outagedetectioninterval=30 \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-discovery-client/README.md b/shoulder-platform-common/shoulder-platform-starter-discovery-client/README.md new file mode 100644 index 0000000..236917d --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-discovery-client/README.md @@ -0,0 +1,3 @@ +# shoulder-platform-starter-discovery-client + +包含如 `nacos` 客户端,快速对接服务注册与发现 diff --git a/shoulder-platform-common/shoulder-platform-starter-discovery-client/pom.xml b/shoulder-platform-common/shoulder-platform-starter-discovery-client/pom.xml new file mode 100644 index 0000000..70b4fa2 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-discovery-client/pom.xml @@ -0,0 +1,53 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-discovery-client + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + com.alibaba + fastjson + + + com.google.guava + guava + + + com.alibaba.nacos + nacos-client + + + + + + com.alibaba + fastjson + + + + com.google.guava + guava + + + + com.alibaba.nacos + nacos-client + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-job/README.md b/shoulder-platform-common/shoulder-platform-starter-job/README.md new file mode 100644 index 0000000..cb7a9e2 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-job/README.md @@ -0,0 +1,9 @@ +# shoulder-platform-starter-job + +包含如 `power-job` 客户端,快速对接分布式定时任务 + +TODO 技术选型调研: + +- elasticJob(刚发布新版本) +- xxlJob(主流,适合较小型) +- powerJob(暂时较小众) \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-job/pom.xml b/shoulder-platform-common/shoulder-platform-starter-job/pom.xml new file mode 100644 index 0000000..7b9c465 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-job/pom.xml @@ -0,0 +1,16 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-job + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-micro/README.md b/shoulder-platform-common/shoulder-platform-starter-micro/README.md new file mode 100644 index 0000000..1a9763f --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-micro/README.md @@ -0,0 +1,22 @@ +# shoulder-platform + +管理了 shoulder-platform 微服务开发中常用的jar(包含了shoulder-platform-common中几个常用的) + +包含以下: +- 统一服务发现 & 注册 +- 统一配置中心 +- 统一链路追踪 +- 统一性能监控、指标监控 +- 统一服务调用 +- 统一服务提供 +- 统一消息通知 + +使用: + +```xml + + + cn.itlym.platform + shoulder-platform-starter-micro + +``` \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-micro/pom.xml b/shoulder-platform-common/shoulder-platform-starter-micro/pom.xml new file mode 100644 index 0000000..6b73dd4 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-micro/pom.xml @@ -0,0 +1,75 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-micro + + + + + + + cn.itlym.platform + shoulder-platform-starter-db + ${shoulder-platform.version} + + + + + cn.itlym.platform + shoulder-platform-starter-discovery-client + ${shoulder-platform.version} + + + + + cn.itlym.platform + shoulder-platform-starter-config-client + ${shoulder-platform.version} + + + + + cn.itlym.platform + shoulder-platform-starter-mq + ${shoulder-platform.version} + + + + + cn.itlym.platform + shoulder-platform-starter-rpc-server + ${shoulder-platform.version} + + + + cn.itlym.platform + shoulder-platform-starter-rpc-client + ${shoulder-platform.version} + + + + + + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-micro/src/main/resources/banner.txt b/shoulder-platform-common/shoulder-platform-starter-micro/src/main/resources/banner.txt new file mode 100644 index 0000000..fe385e7 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-micro/src/main/resources/banner.txt @@ -0,0 +1,11 @@ +${AnsiColor.BRIGHT_YELLOW} + ______ _ _ _ ______ _ ___ + / _____) | | | | | (_____ \| | _ / __) +( (____ | |__ ___ _ _| | __| |_____ ____ _____) ) | _____ _| |_ _| |__ ___ ____ ____ + \____ \| _ \ / _ \| | | | |/ _ | ___ |/ ___) | ____/| |(____ (_ _|_ __) _ \ / ___) \ + _____) ) | | | |_| | |_| | ( (_| | ____| | | | | |/ ___ | | |_ | | | |_| | | | | | | +(______/|_| |_|\___/|____/ \_)____|_____)_| |_| \_)_____| \__) |_| \___/|_| |_|_|_| + +Application Version: @project.description@ - @project.version@ +Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version} +${AnsiColor.DEFAULT} diff --git a/shoulder-platform-common/shoulder-platform-starter-monitor/README.md b/shoulder-platform-common/shoulder-platform-starter-monitor/README.md new file mode 100644 index 0000000..5a69b61 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-monitor/README.md @@ -0,0 +1,5 @@ +# shoulder-platform-starter-monitor + +提供性能监控、指标监控的客户端。 + +包含如 `spring-boot-admin-client`、`prometheus` 等监控相关 \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-monitor/pom.xml b/shoulder-platform-common/shoulder-platform-starter-monitor/pom.xml new file mode 100644 index 0000000..44fe8b4 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-monitor/pom.xml @@ -0,0 +1,38 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-monitor + + 应用服务器指标监控:spring-boot-actuator + Prometheus + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-mq/README.md b/shoulder-platform-common/shoulder-platform-starter-mq/README.md new file mode 100644 index 0000000..39b2d9e --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-mq/README.md @@ -0,0 +1,20 @@ +# shoulder-platform-starter-mq + +包含如 `RabbitMQ`、`spring cloud stream`等 + +MQ 技术选型: +- ~~ActiveMQ~~ + - Java开发、符合 Jms、且功能全面,对于小型项目足够 + - 但由于使用的项目越来越少,从长远角度不看好 + - 若原使用该技术,推荐替换为 `RabbitMQ` 或 `RocketMQ` +- RabbitMQ + - 使用广泛,功能全面,默认选型 +- KafKa、RocketMQ + - 适用于堆积场景,大数据量场景,如追踪日志、监控日志、操作日志等数据流 + - 其中 KafKa 由于使用广泛且诞生较早,第三方支持更全面,更推荐 + - RocketMQ 使用 Java 编写,更适合维护团队偏 Java 的系统, +- Plusar + - 高性能,性能随节点数线性扩展 + - 功能强大支持租户 + - 较新,文档和经验较少,准备选型 + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-mq/pom.xml b/shoulder-platform-common/shoulder-platform-starter-mq/pom.xml new file mode 100644 index 0000000..da07728 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-mq/pom.xml @@ -0,0 +1,33 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-mq + + + + + + + org.springframework.boot + spring-boot-starter-activemq + + + org.apache.activemq + activemq-broker + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-rpc-client/README.md b/shoulder-platform-common/shoulder-platform-starter-rpc-client/README.md new file mode 100644 index 0000000..9028d77 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-rpc-client/README.md @@ -0,0 +1,37 @@ +# shoulder-platform-starter-rpc-client + +包含如 `Feign`、`Ribbon` 、统一认证客户端等 + +## 功能 & 选型 + +- 服务发现,自动寻址 + - nacos-discovery-client +- 负载均衡 + - Ribbon +- 简化调用 + - Feign +- 服务间调用认证 + - 支持 RestTemplate + - 支持 Feign + +- 先控制并发再熔断最后重试 + +- 缓存(与业务相关,默认不提供) + - 调用后缓存结果 + - 有缓存或熔断时使用缓存,自己决定 + +## 离群实例摘除 + +> 集群中部分实例节点异常),此时直接触发服务降级不如针对性地仅对故障实例隔离,摘除服务列表。 + +场景介绍: +- A为服务提供方,有2个实例。 +- B为调用方,当调用A服务50%几率失败时,将自动触发降级。 +- 若A服务一台机器出现故障(如磁盘满、线程池满,滚动发布新版本出问题),很可能会导致B服务触发 + +处理手段 +- 自动处理宕机夯死:心跳中断,由注册中心自动将其暂时下线。 +- 自动处理高负载,慢响应:监控服务监控其负载、当达到限值触发告警、告警中触发web端点,调用注册中心接口,主动将其下线 +- 手动:在注册中心手动将其下线 + +https://www.jianshu.com/p/304250a65c82 \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-rpc-client/pom.xml b/shoulder-platform-common/shoulder-platform-starter-rpc-client/pom.xml new file mode 100644 index 0000000..f344ad9 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-rpc-client/pom.xml @@ -0,0 +1,16 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-rpc-client + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-rpc-server/README.md b/shoulder-platform-common/shoulder-platform-starter-rpc-server/README.md new file mode 100644 index 0000000..8ef7ff4 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-rpc-server/README.md @@ -0,0 +1,30 @@ +# shoulder-platform-starter-rpc-server + +包含如 `spring-boot-starter-web`、`shoulder-starter-web` 参数校验、参数转换、统一异常处理等 + +- 请求限流 + - `Sentinel` +- 熔断 + - 当必需依赖无法使用时,返回熔断值,(如数据库无法连接、依赖的其他服务无法正确响应) +- 降级 + - 返回默认值 + - 返回缓存值 + - 返回失败 +- 缓存 + - `spring-cache` + + +## 限流 [Sentinel](https://sentinelguard.io/zh-cn/) + + +[sentinel](https://github.com/alibaba/Sentinel) + +[sentinel-wiki 生产环境使用 sentinel](https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel) + +核心注解: +`@SentinelResource(value = "sayHello")` + +[sentinel demo 指南](https://github.com/sentinel-group/sentinel-guides) + +[sentinel 高级玩法与案例](https://github.com/sentinel-group/sentinel-awesome) + diff --git a/shoulder-platform-common/shoulder-platform-starter-rpc-server/pom.xml b/shoulder-platform-common/shoulder-platform-starter-rpc-server/pom.xml new file mode 100644 index 0000000..497a914 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-rpc-server/pom.xml @@ -0,0 +1,26 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-rpc-server + + + + + + + cn.itlym + shoulder-starter-web + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-trace/README.md b/shoulder-platform-common/shoulder-platform-starter-trace/README.md new file mode 100644 index 0000000..f5df194 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-trace/README.md @@ -0,0 +1,62 @@ +# shoulder-platform-starter-trace + +统一链路追踪技术方案。 + +分布式链路追踪:[谷歌Dapper论文-中文](http://bigbully.github.io/Dapper-translation/) + + +选型指南,符合业界规范:OpenTracing;低入侵。 + + +主流开源方案: + +- [Twitter-Zipkin](https://zipkin.io/) + - Twitter 提供。大规模应用,UI较弱 +- [Skywalking](http://skywalking.apache.org/) + - 国产优秀开源项目。高性能,shoulder 最初对接方案 +- [美团点评-CAT](https://zipkin.io/) + - 大众点评。UI强大,代码侵入,非 `OpenTracing` +- [Pinpoint](https://github.com/naver/pinpoint) + - 韩国。记录数据详细、UI强大,非 `OpenTracing` + + +这里推荐 `Skywalking`,但其服务端会比其他两个消耗更多的资源(CPU、内存)。 + +由于这三者均可以做到代码无入侵,选型平滑切换。为了减少资源占用 Shoulder 默认选用了 `Zipkin` + + +注意事项: + +- 涉及 websocket 时会产生一些问题 + - [github](https://github.com/spring-cloud/spring-cloud-sleuth/issues/276) + - [spring WebSocket 指南](https://spring.io/guides/gs/messaging-stomp-websocket/) + - 最好指定下 `webjars-locator-core` 包的版本,不要使用`spring-boot-parent`管理的该版本,否则会出现 `http://localhost:8080/webjars/jquery/jquery.min.js` `404`问题。 + +--- +## 使用 + +```java + + @Bean + public Sampler defaultSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + +``` + +```properties + spring.zipkin.baseUrl=http://xxx:9411/ + spring.zipkin.sender.type=web + + spring.rabbitmq.host=my.site + spring.rabbitmq.username=zipkin + spring.rabbitmq.password=zipkin +``` + + +--- + +## 参考 + +https://www.jianshu.com/p/92a12de11f18 \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-platform-starter-trace/pom.xml b/shoulder-platform-common/shoulder-platform-starter-trace/pom.xml new file mode 100644 index 0000000..f0a4390 --- /dev/null +++ b/shoulder-platform-common/shoulder-platform-starter-trace/pom.xml @@ -0,0 +1,31 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-platform-starter-trace + + 链路追踪:选用 zipkin + mq 的方式 + + + + + org.springframework.cloud + spring-cloud-starter-zipkin + + + + cn.itlym.platform + shoulder-platform-starter-mq + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/README.md b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/README.md new file mode 100644 index 0000000..c45a0df --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/README.md @@ -0,0 +1,3 @@ +# shoulder-platform-starter-rpc-server + +开箱即用的 阿里云-短信 服务客户端SDK,针对阿里云短信服务封装,与 shoulder 无任何依赖。 \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/pom.xml b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/pom.xml new file mode 100644 index 0000000..4b7328d --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/pom.xml @@ -0,0 +1,60 @@ + + + + cn.itlym + shoulder-parent + 0.4-SNAPSHOT + + 4.0.0 + + cn.itlym.platform + shoulder-sms-aliyun-spring-boot-starter + 1.0-SNAPSHOT + + + + 4.5.0 + 2.1.0 + + + + + + + + com.aliyun + aliyun-java-sdk-core + ${aliyun-java-sdk-core.version} + + + com.aliyun + aliyun-java-sdk-dysmsapi + ${aliyun-java-sdk-dysmsapi.version} + + + + + org.springframework.boot + spring-boot-starter + true + + + + + org.springframework.boot + spring-boot-autoconfigure-processor + true + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/client/AliSmsClient.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/client/AliSmsClient.java new file mode 100644 index 0000000..0a0c338 --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/client/AliSmsClient.java @@ -0,0 +1,31 @@ +package org.shoulder.sms.aliyun.client; + +import org.shoulder.sms.aliyun.dto.param.AliSmsBatchParam; +import org.shoulder.sms.aliyun.dto.param.AliSmsParam; +import org.shoulder.sms.aliyun.exception.AliSmsException; + +/** + * 阿里云短信发送客户端 + * + * @author lym + */ +public interface AliSmsClient { + + /** + * 发短信 + * + * @param aliSmsParam 短信相关参数 + * @return 是否发送成功 + * @throws AliSmsException 发送失败 + */ + boolean send(AliSmsParam aliSmsParam) throws AliSmsException; + + /** + * 批量发短信 + * + * @param aliSmsBatchParam 短信相关参数 + * @return 是否发送成功 + * @throws AliSmsException 发送失败 + */ + boolean send(AliSmsBatchParam aliSmsBatchParam) throws AliSmsException; +} diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/client/impl/AliSmsClientImpl.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/client/impl/AliSmsClientImpl.java new file mode 100644 index 0000000..cb40773 --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/client/impl/AliSmsClientImpl.java @@ -0,0 +1,155 @@ +package org.shoulder.sms.aliyun.client.impl; + +import com.aliyuncs.AcsRequest; +import com.aliyuncs.AcsResponse; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.dysmsapi.model.v20170525.SendBatchSmsRequest; +import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.exceptions.ServerException; +import com.aliyuncs.profile.DefaultProfile; +import com.aliyuncs.profile.IClientProfile; +import com.google.gson.Gson; +import org.apache.http.util.Args; +import org.shoulder.sms.aliyun.client.AliSmsClient; +import org.shoulder.sms.aliyun.dto.param.AliSmsBatchParam; +import org.shoulder.sms.aliyun.dto.param.AliSmsParam; +import org.shoulder.sms.aliyun.exception.AliSmsException; +import org.shoulder.sms.aliyun.util.AliSmsUtils; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +/** + * 阿里云 SMS 客户端. + * + * @author lym + */ +public class AliSmsClientImpl implements AliSmsClient { + + private final IAcsClient acsClient; + private final Map smsTemplateTemplate; + private final Gson gson = new Gson(); + + /** + * construction + * + * @param accessKeyId 阿里云短信 accessKeyId + * @param accessKeySecret 阿里云短信 accessKeySecret + */ + public AliSmsClientImpl(final String accessKeyId, final String accessKeySecret) { + this(accessKeyId, accessKeySecret, Collections.emptyMap()); + } + + /** + * construction + * + * @param accessKeyId 阿里云短信 accessKeyId + * @param accessKeySecret 阿里云短信 accessKeySecret + * @param smsTemplateTemplate 预置短信模板 + */ + public AliSmsClientImpl(final String accessKeyId, + final String accessKeySecret, + final Map smsTemplateTemplate) { + Args.notEmpty(accessKeyId, "'accessKeyId' must be not empty"); + Args.notEmpty(accessKeySecret, "'accessKeySecret' must be not empty"); + + try { + DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", "Dysmsapi", "dysmsapi.aliyuncs.com"); + } catch (ClientException e) { + e.printStackTrace(); + } + + + final IClientProfile clientProfile = DefaultProfile.getProfile( + "cn-hangzhou", accessKeyId, accessKeySecret); + + this.acsClient = new DefaultAcsClient(clientProfile); + this.smsTemplateTemplate = smsTemplateTemplate; + } + + /** + * construction + * + * @param acsClient IAcsClient + * @param smsTemplateTemplate 预置短信模板 + */ + public AliSmsClientImpl(final IAcsClient acsClient, final Map smsTemplateTemplate) { + this.acsClient = acsClient; + this.smsTemplateTemplate = smsTemplateTemplate; + } + + // -------------------------------------------------------- + + /** + * 发送短信. + * + * @param aliSmsParam 短信模板 + */ + @Override + public boolean send(final AliSmsParam aliSmsParam) throws AliSmsException { + + AliSmsUtils.checkSmsParam(aliSmsParam); + + SendSmsRequest request = new SendSmsRequest(); + request.setPhoneNumbers(String.join(",", aliSmsParam.getPhoneNumbers())); + request.setSignName(aliSmsParam.getSignName()); + request.setTemplateCode(aliSmsParam.getTemplateCode()); + request.setTemplateParam(AliSmsUtils.toJsonStr(aliSmsParam.getTemplateParam())); + return "OK".equals(getAcsResponse(request).getCode()); + } + + /** + * 批量发送短信. + * + *

+ * 批量发送短信接口,支持在一次请求中分别向多个不同的手机号码发送不同签名的短信。 + * 手机号码,签名,模板参数字段个数相同,一一对应,短信服务根据字段的顺序判断发往指定手机号码的签名。 + * + *

+ * 如果您需要往多个手机号码中发送同样签名的短信,请使用 {@link #send(AliSmsParam)}。 + * + * @param aliSmsBatchParam 批量发送短信模板 + */ + @Override + public boolean send(final AliSmsBatchParam aliSmsBatchParam) throws AliSmsException { + Objects.requireNonNull(aliSmsBatchParam); + AliSmsUtils.checkBatchSmsParam(aliSmsBatchParam); + + SendBatchSmsRequest request = new SendBatchSmsRequest(); + + request.setPhoneNumberJson(gson.toJson(aliSmsBatchParam.getPhoneNumbers())); + request.setSignNameJson(gson.toJson(aliSmsBatchParam.getSignNames())); + request.setTemplateCode(aliSmsBatchParam.getTemplateCode()); + request.setTemplateParamJson(gson.toJson(aliSmsBatchParam.getTemplateParams())); + + return "OK".equals(getAcsResponse(request).getCode()); + } + + /* just for test + public static void main(String[] args) throws AliSmsException { + AliSmsClientImpl client = new AliSmsClientImpl("xxx", "xxx"); + + AliSmsParam param = AliSmsParam.newBuilder() + .signName("xxxx") + .templateCode("SMS_xxxx") + .phoneNumber("123") + .addTemplateParam("code", "123456") + .build(); + client.send(param); + }*/ + + private T getAcsResponse(AcsRequest request) + throws AliSmsException { + try { + return this.acsClient.getAcsResponse(request); + } catch (ServerException e) { + throw new AliSmsException("server exception", e); + } catch (ClientException e) { + throw new AliSmsException("client exception", e); + } + } + +} diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/config/AliYunAutoConfiguration.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/config/AliYunAutoConfiguration.java new file mode 100644 index 0000000..87d5cbf --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/config/AliYunAutoConfiguration.java @@ -0,0 +1,27 @@ +package org.shoulder.sms.aliyun.config; + +import org.shoulder.sms.aliyun.client.AliSmsClient; +import org.shoulder.sms.aliyun.client.impl.AliSmsClientImpl; +import org.shoulder.sms.aliyun.properties.AliYunSmsProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author lym + */ +@Configuration( + proxyBeanMethods = false +) +@EnableConfigurationProperties(value = AliYunSmsProperties.class) +public class AliYunAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public AliSmsClient aliYunSmsService(AliYunSmsProperties properties) { + return new AliSmsClientImpl(properties.getAccessKeyId(), properties.getAccessSecret()); + } + +} + \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/consts/AliSmsAction.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/consts/AliSmsAction.java new file mode 100644 index 0000000..4321d25 --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/consts/AliSmsAction.java @@ -0,0 +1,107 @@ +package org.shoulder.sms.aliyun.consts; + +/** + * 阿里云短信接口 action https://help.aliyun.com/document_detail/102715.html + * + * @author lym + */ +public enum AliSmsAction { + + // ======================= 短信发送接口 ======================= + + /** + * 发送短信 + */ + SEND_SMS("SendSms"), + + /** + * 批量发送短信 + */ + SEND_BATCH_SMS("SendBatchSms"), + + // ======================= 短信查询接口 ======================= + + /** + * 查询短信发送的状态 + */ + QUERY_SEND_DETAILS("QuerySendDetails"), + + + // ======================= 签名申请接口 ======================= + + /** + * 申请短信签名 + */ + ADD_SMS_SIGN("AddSmsSign"), + + /** + * 删除短信签名 + */ + DELETE_SMS_SIGN("DeleteSmsSign"), + + /** + * 查询短信签名申请状态 + */ + QUERY_SMS_SIGN("QuerySmsSign"), + + /** + * 修改未审核通过的短信签名,并重新提交审核。 + */ + MODIFY_SMS_SIGN("ModifySmsSign"), + + // ======================= 模板申请接口 ======================= + + /** + * 修改未通过审核的短信模板 + */ + MODIFY_SMS_TEMPLATE("ModifySmsTemplate"), + + /** + * 查询短信模板的审核状态 + */ + QUERY_SMS_TEMPLATE("QuerySmsTemplate"), + + /** + * 申请短信模板 + */ + ADD_SMS_TEMPLATE("AddSmsTemplate"), + + /** + * 删除短信模板 + */ + DELETE_SMS_TEMPLATE("DeleteSmsTemplate"), + + // ======================= 回执消息 ======================= + + /** + * 订阅SmsReport短信状态报告,获取短信发送状态 + */ + SMS_REPORT("SmsReport"), + + /** + * 订阅SmsUp上行短信消息,获取终端用户回复短信的内容 + */ + SMS_UP("SmsUp"), + + /** + * 订阅签名审核状态消息(SignSmsReport),获取指定签名的审核状态 + */ + SIGN_SMS_REPORT("SignSmsReport"), + + /** + * 订阅模板审核状态消息(TemplateSmsReport),获取指定模板的审核状态 + */ + TEMPLATE_SMS_REPORT("TemplateSmsReport"), + ; + + + private final String action; + + AliSmsAction(String action) { + this.action = action; + } + + public String getAction() { + return action; + } +} diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/dto/param/AliSmsBatchParam.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/dto/param/AliSmsBatchParam.java new file mode 100644 index 0000000..5b63b68 --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/dto/param/AliSmsBatchParam.java @@ -0,0 +1,136 @@ +package org.shoulder.sms.aliyun.dto.param; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * 阿里云 SMS 短信批量模板. + * todo OOP + * + * @author lym + */ +public class AliSmsBatchParam { + + /** + * 每条短信的签名 + */ + private List signNames; + /** + * 模板id + */ + private String templateCode; + /** + * 每条短信的参数 + */ + private List> templateParams; + /** + * 手机号 + */ + private List phoneNumbers; + + public AliSmsBatchParam() { + } + + public AliSmsBatchParam(List signNames, String templateCode, List> templateParams, List phoneNumbers) { + this.signNames = signNames; + this.templateCode = templateCode; + this.templateParams = templateParams; + this.phoneNumbers = phoneNumbers; + } + + private AliSmsBatchParam(Builder builder) { + setSignNames(builder.signNames); + setTemplateCode(builder.templateCode); + setTemplateParams(builder.templateParams); + setPhoneNumbers(builder.phoneNumbers); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static Builder newBuilder(AliSmsBatchParam copy) { + Builder builder = new Builder(); + builder.signNames = copy.getSignNames(); + builder.templateCode = copy.getTemplateCode(); + builder.templateParams = copy.getTemplateParams(); + builder.phoneNumbers = copy.getPhoneNumbers(); + return builder; + } + + public List getSignNames() { + return signNames; + } + + public void setSignNames(List signNames) { + this.signNames = signNames; + } + + public String getTemplateCode() { + return templateCode; + } + + public void setTemplateCode(String templateCode) { + this.templateCode = templateCode; + } + + public List> getTemplateParams() { + return templateParams; + } + + public void setTemplateParams(List> templateParams) { + this.templateParams = templateParams; + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + + public static final class Builder { + private List signNames; + private String templateCode; + private List> templateParams; + private List phoneNumbers; + + private Builder() { + } + + public Builder signNames(List signNames) { + this.signNames = signNames; + return this; + } + + public Builder templateCode(String templateCode) { + this.templateCode = templateCode; + return this; + } + + public Builder templateParams(List> templateParams) { + this.templateParams = templateParams; + return this; + } + + public Builder phoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + return this; + } + + public Builder phoneNumber(String phoneNumbers) { + if (null == this.phoneNumbers) { + this.phoneNumbers = new LinkedList<>(); + } + this.phoneNumbers.add(phoneNumbers); + return this; + } + + public AliSmsBatchParam build() { + return new AliSmsBatchParam(this); + } + } +} diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/dto/param/AliSmsParam.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/dto/param/AliSmsParam.java new file mode 100644 index 0000000..d0d5c90 --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/dto/param/AliSmsParam.java @@ -0,0 +1,150 @@ +package org.shoulder.sms.aliyun.dto.param; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * 阿里云 SMS 短信模板. + * + * @author lym + */ +public class AliSmsParam { + + /** + * 签名,如 "LYM",则短信发送后为 【LYM】 开头,需要与阿里云控制台上的签名对应 + * 查看自己的签名:https://dysms.console.aliyun.com/dysms.htm#/domestic/text/sign + */ + private String signName; + + /** + * 模板名称,在阿里云控制台上添加模板时,会有个模板名称 + * https://dysms.console.aliyun.com/dysms.htm#/domestic/text/template + */ + private String templateCode; + + /** + * 模板参数,用于填充模板 + */ + private Map templateParam; + /** + * 手机号 + */ + private List phoneNumbers; + + public AliSmsParam() { + } + + private AliSmsParam(Builder builder) { + signName = builder.signName; + templateCode = builder.templateCode; + templateParam = builder.templateParam; + phoneNumbers = builder.phoneNumbers; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static Builder newBuilder(AliSmsParam copy) { + Builder builder = new Builder(); + builder.signName = copy.getSignName(); + builder.templateCode = copy.getTemplateCode(); + builder.templateParam = copy.getTemplateParam(); + builder.phoneNumbers = copy.getPhoneNumbers(); + return builder; + } + + public String getSignName() { + return signName; + } + + public void setSignName(String signName) { + this.signName = signName; + } + + public String getTemplateCode() { + return templateCode; + } + + public void setTemplateCode(String templateCode) { + this.templateCode = templateCode; + } + + public Map getTemplateParam() { + return templateParam; + } + + public void setTemplateParam(Map templateParam) { + this.templateParam = templateParam; + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + public static class Builder { + private String signName; + private String templateCode; + private Map templateParam; + private List phoneNumbers; + + private Builder() { + } + + /** + * 添加短信模板参数. + * + * @param key the key + * @param value the value + * @return this + */ + public Builder addTemplateParam(final String key, final String value) { + if (null == this.templateParam) { + this.templateParam = new HashMap<>(3); + } + + this.templateParam.put(key, value); + return this; + } + + public Builder signName(String signName) { + this.signName = signName; + return this; + } + + public Builder templateCode(String templateCode) { + this.templateCode = templateCode; + return this; + } + + public Builder templateParam(Map templateParam) { + this.templateParam = templateParam; + return this; + } + + public Builder phoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + return this; + } + + public Builder phoneNumber(String phoneNumbers) { + if (null == this.phoneNumbers) { + this.phoneNumbers = new LinkedList<>(); + } + this.phoneNumbers.add(phoneNumbers); + return this; + } + + public AliSmsParam build() { + return new AliSmsParam(this); + } + } + + +} diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/exception/AliSmsException.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/exception/AliSmsException.java new file mode 100644 index 0000000..79abb4b --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/exception/AliSmsException.java @@ -0,0 +1,27 @@ +package org.shoulder.sms.aliyun.exception; + +/** + * 阿里云短信服务异常 + * + * @author lym + */ +public class AliSmsException extends Exception { + /** + * Instantiates a new AliSmsException. + * + * @param message the message + */ + public AliSmsException(final String message) { + super(message); + } + + /** + * Instantiates a new AliSmsException. + * + * @param client_exception + * @param cause the cause + */ + public AliSmsException(String client_exception, final Throwable cause) { + super(client_exception, cause); + } +} diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/package-info.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/package-info.java new file mode 100644 index 0000000..e458299 --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/package-info.java @@ -0,0 +1,6 @@ +/** + * 阿里云短信 sdk 封装 + * + * @author lym + */ +package org.shoulder.sms.aliyun; \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/properties/AliYunSmsProperties.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/properties/AliYunSmsProperties.java new file mode 100644 index 0000000..1c7c9b1 --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/properties/AliYunSmsProperties.java @@ -0,0 +1,75 @@ +package org.shoulder.sms.aliyun.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 阿里云短信相关配置 + * + * @author lym + */ +@ConfigurationProperties(prefix = "ali-cloud") +public class AliYunSmsProperties { + + /** + * 阿里 accessKeyId + */ + private String accessKeyId; + /** + * 阿里 accessSecret + */ + private String accessSecret; + + /** + * 短信API产品名称(短信产品名固定,无需修改) + */ + private String product = "Dysmsapi"; + + /** + * 短信API产品域名(接口地址固定,无需修改) + */ + private String domain = "dysmsapi.aliyuncs.com"; + /** + * regionId + */ + private String regionId = "cn-hangzhou"; + + public String getProduct() { + return product; + } + + public void setProduct(String product) { + this.product = product; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public String getRegionId() { + return regionId; + } + + public void setRegionId(String regionId) { + this.regionId = regionId; + } + + public String getAccessKeyId() { + return accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getAccessSecret() { + return accessSecret; + } + + public void setAccessSecret(String accessSecret) { + this.accessSecret = accessSecret; + } +} \ No newline at end of file diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/util/AliSmsUtils.java b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/util/AliSmsUtils.java new file mode 100644 index 0000000..1a9527f --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/java/org/shoulder/sms/aliyun/util/AliSmsUtils.java @@ -0,0 +1,138 @@ +package org.shoulder.sms.aliyun.util; + +import com.aliyuncs.CommonRequest; +import com.aliyuncs.CommonResponse; +import com.aliyuncs.http.MethodType; +import com.google.gson.Gson; +import org.apache.http.util.Args; +import org.shoulder.sms.aliyun.consts.AliSmsAction; +import org.shoulder.sms.aliyun.dto.param.AliSmsBatchParam; +import org.shoulder.sms.aliyun.dto.param.AliSmsParam; +import org.shoulder.sms.aliyun.exception.AliSmsException; + +import java.util.Map; +import java.util.Objects; + +/** + * sms 工具类. + * + * @author lym + */ +public class AliSmsUtils { + + /** + * 成功响应码 + */ + private static final String SUCCESS_CODE = "OK"; + + /** + * 严格匹配中国大陆内支持短信功能的号码 + * https://github.com/VincentSit/ChinaMobilePhoneNumberRegex/blob/master/README-CN.md + */ + private static final String PHONE_NUMBER_REGEX = "^(?:\\+?86)?1(?:3\\d{3}|5[^4\\D]\\d{2}|8\\d{3}|7(?:[01356789]\\d{2}|4(?:0\\d|1[0-2]|9\\d))|9[01356789]\\d{2}|6[2567]\\d{2}|4[579]\\d{2})\\d{6}$"; + + + /** + * Map 转 json 字符串的简单实现. + * + * @param map the map + * @return the json string + */ + public static String toJsonStr(final Map map) { + if (null == map || map.isEmpty()) { + return null; + } + + final StringBuilder sb = new StringBuilder(); + sb.append('{'); + for (final Map.Entry entry : map.entrySet()) { + sb.append('"') + .append(entry.getKey().replace("\"", "\\\"")) + .append('"') + .append(':') + .append('"') + .append(entry.getValue().replace("\"", "\\\"")) + .append('"') + .append(','); + } + sb.deleteCharAt(sb.length() - 1); + sb.append('}'); + return sb.toString(); + } + + /** + * 校验 AliSmsParam. + * + * @param param the AliSmsParam + */ + public static void checkSmsParam(final AliSmsParam param) { + Objects.requireNonNull(param); + Args.notEmpty(param.getSignName(), "signName"); + Args.notEmpty(param.getTemplateCode(), "templateCode"); + Args.notEmpty(param.getPhoneNumbers(), "phoneNumbers"); + param.getPhoneNumbers().forEach(AliSmsUtils::checkPhoneNumber); + Args.check(param.getPhoneNumbers().size() <= 1000, "phoneNumbers can't more than 1000"); + + } + + /** + * 校验 AliSmsBatchParam. + * + * @param param the AliSmsBatchParam + */ + public static void checkBatchSmsParam(final AliSmsBatchParam param) { + Objects.requireNonNull(param); + Args.notEmpty(param.getSignNames(), "signNames"); + Args.notEmpty(param.getTemplateCode(), "templateCode"); + Args.notEmpty(param.getPhoneNumbers(), "phoneNumber"); + param.getPhoneNumbers().forEach(AliSmsUtils::checkPhoneNumber); + Args.check(param.getSignNames().size() == param.getPhoneNumbers().size() && + param.getPhoneNumbers().size() == param.getTemplateParams().size(), "phoneNumbers, signNames, templateParams must have same size"); + + } + + /** + * 校验 SendSmsResponse 状态. + * + * @param response the SendSmsResponse + */ + @SuppressWarnings("unchecked") + public static void checkSmsResponse(final CommonResponse response) throws AliSmsException { + if (null == response) { + throw new AliSmsException("Response is null"); + } + final Gson gson = new Gson(); + final Map json = gson.fromJson(response.getData(), Map.class); + if (!SUCCESS_CODE.equalsIgnoreCase(json.get("Code"))) { + // todo 错误处理 + throw new AliSmsException("Http status: " + response.getHttpStatus() + ", response: " + response.getData()); + } + } + + /** + * 校验手机号码(中国). + * + * @param phoneNumber the phone number + */ + public static void checkPhoneNumber(final String phoneNumber) { + if (null == phoneNumber || !phoneNumber.matches(PHONE_NUMBER_REGEX)) { + throw new IllegalArgumentException("Invalid phone number"); + } + } + + public static CommonRequest newRequest(AliSmsAction action) { + final String aliCloudDomain = "dysmsapi.aliyuncs.com"; + final String aliCloudVersion = "2017-05-25"; + final String product = "Dysmsapi"; + + final CommonRequest request = new CommonRequest(); + request.setMethod(MethodType.POST); + + request.setDomain(aliCloudDomain); + request.setVersion(aliCloudVersion); + request.setAction(action.getAction()); + request.setProduct(product); + return request; + } + +} diff --git a/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/resources/META-INF/spring.factories b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..a847748 --- /dev/null +++ b/shoulder-platform-common/shoulder-sms-aliyun-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configuration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.shoulder.sms.aliyun.config.AliYunAutoConfiguration \ No newline at end of file diff --git a/shoulder-storage-center/pom.xml b/shoulder-storage-center/pom.xml new file mode 100644 index 0000000..6b808a3 --- /dev/null +++ b/shoulder-storage-center/pom.xml @@ -0,0 +1,20 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-common/shoulder-platform-parent/pom.xml + + 4.0.0 + + shoulder-storage-center + pom + + storage-modules + + + + \ No newline at end of file diff --git a/shoulder-storage-center/storage-modules/pom.xml b/shoulder-storage-center/storage-modules/pom.xml new file mode 100644 index 0000000..5dc6cc3 --- /dev/null +++ b/shoulder-storage-center/storage-modules/pom.xml @@ -0,0 +1,19 @@ + + + + shoulder-storage-center + cn.itlym.platform + 1.0-SNAPSHOT + + 4.0.0 + + storage-modules + pom + + storage-modules-minio + + + + \ No newline at end of file diff --git a/shoulder-storage-center/storage-modules/storage-modules-minio/pom.xml b/shoulder-storage-center/storage-modules/storage-modules-minio/pom.xml new file mode 100644 index 0000000..fab56e2 --- /dev/null +++ b/shoulder-storage-center/storage-modules/storage-modules-minio/pom.xml @@ -0,0 +1,38 @@ + + + + storage-modules + cn.itlym.platform + 1.0-SNAPSHOT + + 4.0.0 + + storage-modules-minio + + + + io.minio + minio + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-autoconfigure-processor + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + \ No newline at end of file diff --git a/shoulder-storage-center/storage-modules/storage-modules-minio/src/main/java/org/shoulder/minio/config/MinioAutoConfiguration.java b/shoulder-storage-center/storage-modules/storage-modules-minio/src/main/java/org/shoulder/minio/config/MinioAutoConfiguration.java new file mode 100644 index 0000000..62ed4d8 --- /dev/null +++ b/shoulder-storage-center/storage-modules/storage-modules-minio/src/main/java/org/shoulder/minio/config/MinioAutoConfiguration.java @@ -0,0 +1,41 @@ +package org.shoulder.minio.config; + +import io.minio.MinioClient; +import io.minio.errors.InvalidEndpointException; +import io.minio.errors.InvalidPortException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * minio 自动配置 + * + * @author lym + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(MinioProperties.class) +@ConditionalOnProperty(prefix = "shoulder.minio", name = "enabled", havingValue = "true", matchIfMissing = true) +public class MinioAutoConfiguration { + + private final MinioProperties minioProperties; + + public MinioAutoConfiguration(MinioProperties minioProperties) { + this.minioProperties = minioProperties; + } + + /** + * MinioClient Bean + * + * @return MinioClient + * @throws InvalidEndpointException endpoint 错误 + * @throws InvalidPortException port 错误 + */ + @Bean + public MinioClient minioClient() throws InvalidPortException, InvalidEndpointException { + return new MinioClient(minioProperties.getEndpoint(), minioProperties.getPort(), + minioProperties.getAccessKey(), minioProperties.getSecretKey(), + minioProperties.getRegion(), minioProperties.isSecure() + ); + } +} diff --git a/shoulder-storage-center/storage-modules/storage-modules-minio/src/main/java/org/shoulder/minio/config/MinioProperties.java b/shoulder-storage-center/storage-modules/storage-modules-minio/src/main/java/org/shoulder/minio/config/MinioProperties.java new file mode 100644 index 0000000..2f96251 --- /dev/null +++ b/shoulder-storage-center/storage-modules/storage-modules-minio/src/main/java/org/shoulder/minio/config/MinioProperties.java @@ -0,0 +1,97 @@ +package org.shoulder.minio.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * minio 配置项 + * minio 使用教程参见官网 https://docs.min.io/cn/java-client-quickstart-guide.html + * + * @author lym + */ +@ConfigurationProperties("shoulder.minio") +public class MinioProperties { + /** + * 是否开启自动装配 + */ + private Boolean enable = Boolean.TRUE; + /** + * minio Server 主机名或IP地址 + */ + private String endpoint = "127.0.0.1"; + /** + * 端口号 + */ + private int port = 9000; + /** + * ak 类似于用户ID,用于唯一标识你的账户 + */ + private String accessKey; + /** + * sk 账户的密码 + */ + private String secretKey; + /** + * 设置该值将覆盖自动发现存储桶region。(可选) + */ + private String region; + /** + * 使用 ssl + */ + private boolean secure = false; + + public Boolean getEnable() { + return enable; + } + + public void setEnable(Boolean enable) { + this.enable = enable; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public boolean isSecure() { + return secure; + } + + public void setSecure(boolean secure) { + this.secure = secure; + } +} diff --git a/shoulder-storage-center/storage-modules/storage-modules-minio/src/main/resources/META-INF/spring.factories b/shoulder-storage-center/storage-modules/storage-modules-minio/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..4cb1276 --- /dev/null +++ b/shoulder-storage-center/storage-modules/storage-modules-minio/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.shoulder.minio.config.MinioAutoConfiguration \ No newline at end of file diff --git a/shoulder-system-center/README.md b/shoulder-system-center/README.md new file mode 100644 index 0000000..7d16dac --- /dev/null +++ b/shoulder-system-center/README.md @@ -0,0 +1,5 @@ +# shoulder-system-center + +系统/业务字典、系统配置,以及配置变更通知 + + diff --git a/shoulder-system-center/pom.xml b/shoulder-system-center/pom.xml new file mode 100644 index 0000000..b35617e --- /dev/null +++ b/shoulder-system-center/pom.xml @@ -0,0 +1,60 @@ + + + + cn.itlym.platform + shoulder-platform-parent + 1.0-SNAPSHOT + ../shoulder-platform-common/shoulder-platform-parent/pom.xml + + + 4.0.0 + + shoulder-system-center + pom + 1.0-SNAPSHOT + + + system-api + system-common + system-core + system-modules + system-start + + + + + + cn.itlym.platform + system-api + ${project.version} + + + + cn.itlym.platform + system-common + ${project.version} + + + + cn.itlym.platform + system-core + ${project.version} + + + + cn.itlym.platform + system-config + ${project.version} + + + cn.itlym.platform + system-dictionary + ${project.version} + + + + + + \ No newline at end of file diff --git a/shoulder-system-center/system-api/pom.xml b/shoulder-system-center/system-api/pom.xml new file mode 100644 index 0000000..346b0ae --- /dev/null +++ b/shoulder-system-center/system-api/pom.xml @@ -0,0 +1,35 @@ + + + + cn.itlym.platform + shoulder-system-center + 1.0-SNAPSHOT + + 4.0.0 + + system-api + + + + org.projectlombok + lombok + provided + + + javax.ws.rs + javax.ws.rs-api + + + javax.validation + validation-api + + + + cn.itlym + shoulder-core + + + + \ No newline at end of file diff --git a/shoulder-system-center/system-api/src/main/java/cn/itlym/shoulder/platform/system/api/SysConfigRestfulApi.java b/shoulder-system-center/system-api/src/main/java/cn/itlym/shoulder/platform/system/api/SysConfigRestfulApi.java new file mode 100644 index 0000000..15fede1 --- /dev/null +++ b/shoulder-system-center/system-api/src/main/java/cn/itlym/shoulder/platform/system/api/SysConfigRestfulApi.java @@ -0,0 +1,29 @@ +package cn.itlym.shoulder.platform.system.api; + +import org.shoulder.core.dto.response.RestResult; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +/** + * 系统配置 + * + * @author lym + */ +@Path("/rest/config/v1") +@Produces(MediaType.APPLICATION_JSON) +public interface SysConfigRestfulApi { + + /** + * 配置项 + * + * @return BaseResult + */ + @GET + @Path("/item/{key}") + RestResult configItem(@PathParam("key") String key); + +} diff --git a/shoulder-system-center/system-api/src/main/java/cn/itlym/shoulder/platform/system/api/SysDictionaryRestfulApi.java b/shoulder-system-center/system-api/src/main/java/cn/itlym/shoulder/platform/system/api/SysDictionaryRestfulApi.java new file mode 100644 index 0000000..18066a6 --- /dev/null +++ b/shoulder-system-center/system-api/src/main/java/cn/itlym/shoulder/platform/system/api/SysDictionaryRestfulApi.java @@ -0,0 +1,29 @@ +package cn.itlym.shoulder.platform.system.api; + +import org.shoulder.core.dto.response.RestResult; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +/** + * 数据字典 + * + * @author lym + */ +@Path("/rest/dictionary/v1") +@Produces(MediaType.APPLICATION_JSON) +public interface SysDictionaryRestfulApi { + + /** + * 字典项 + * + * @return BaseResult + */ + @GET + @Path("/item/{key}") + RestResult configItem(@PathParam("key") String key); + +} diff --git a/shoulder-system-center/system-api/src/main/java/cn/itlym/shoulder/platform/system/notify/ConfigChangeEvent.java b/shoulder-system-center/system-api/src/main/java/cn/itlym/shoulder/platform/system/notify/ConfigChangeEvent.java new file mode 100644 index 0000000..013d36a --- /dev/null +++ b/shoulder-system-center/system-api/src/main/java/cn/itlym/shoulder/platform/system/notify/ConfigChangeEvent.java @@ -0,0 +1,24 @@ +package cn.itlym.shoulder.platform.system.notify; + +import lombok.Data; + +/** + * 配置变更通知 + * + * @author lym + */ +@Data +public class ConfigChangeEvent { + + private String type; + + private String key; + + private Object value; + + /** + * 配置项版本号,每次变更加一 + */ + private Integer version; + +} diff --git a/shoulder-system-center/system-common/pom.xml b/shoulder-system-center/system-common/pom.xml new file mode 100644 index 0000000..7e3ab0c --- /dev/null +++ b/shoulder-system-center/system-common/pom.xml @@ -0,0 +1,22 @@ + + + + shoulder-system-center + cn.itlym.platform + 1.0-SNAPSHOT + + 4.0.0 + + system-common + + + + cn.itlym.platform + system-api + + + + + \ No newline at end of file diff --git a/shoulder-system-center/system-core/pom.xml b/shoulder-system-center/system-core/pom.xml new file mode 100644 index 0000000..5af25da --- /dev/null +++ b/shoulder-system-center/system-core/pom.xml @@ -0,0 +1,27 @@ + + + + cn.itlym.platform + shoulder-system-center + 1.0-SNAPSHOT + + 4.0.0 + + system-core + + + + cn.itlym.platform + system-common + + + + cn.itlym.platform + shoulder-platform-starter-micro + + + + + \ No newline at end of file diff --git a/shoulder-system-center/system-modules/pom.xml b/shoulder-system-center/system-modules/pom.xml new file mode 100644 index 0000000..b998ff2 --- /dev/null +++ b/shoulder-system-center/system-modules/pom.xml @@ -0,0 +1,29 @@ + + + + cn.itlym.platform + shoulder-system-center + 1.0-SNAPSHOT + + 4.0.0 + + system-modules + pom + + + system-config + system-dictionary + system-error-code + + + + + + cn.itlym.platform + system-core + + + + \ No newline at end of file diff --git a/shoulder-system-center/system-modules/system-config/pom.xml b/shoulder-system-center/system-modules/system-config/pom.xml new file mode 100644 index 0000000..753046a --- /dev/null +++ b/shoulder-system-center/system-modules/system-config/pom.xml @@ -0,0 +1,15 @@ + + + + cn.itlym.platform + system-modules + 1.0-SNAPSHOT + + 4.0.0 + + system-config + + + \ No newline at end of file diff --git a/shoulder-system-center/system-modules/system-dictionary/pom.xml b/shoulder-system-center/system-modules/system-dictionary/pom.xml new file mode 100644 index 0000000..9a21fef --- /dev/null +++ b/shoulder-system-center/system-modules/system-dictionary/pom.xml @@ -0,0 +1,15 @@ + + + + cn.itlym.platform + system-modules + 1.0-SNAPSHOT + + 4.0.0 + + system-dictionary + + + \ No newline at end of file diff --git a/shoulder-system-center/system-modules/system-error-code/pom.xml b/shoulder-system-center/system-modules/system-error-code/pom.xml new file mode 100644 index 0000000..61cb79e --- /dev/null +++ b/shoulder-system-center/system-modules/system-error-code/pom.xml @@ -0,0 +1,15 @@ + + + + cn.itlym.platform + system-modules + 1.0-SNAPSHOT + + 4.0.0 + + system-error-code + + + \ No newline at end of file diff --git a/shoulder-system-center/system-start/pom.xml b/shoulder-system-center/system-start/pom.xml new file mode 100644 index 0000000..3432210 --- /dev/null +++ b/shoulder-system-center/system-start/pom.xml @@ -0,0 +1,80 @@ + + + + cn.itlym.platform + shoulder-system-center + 1.0-SNAPSHOT + + 4.0.0 + + system-start + ${artifactId} + Shoulder平台-系统配置中心 + + + + + cn.itlym.platform + system-config + + + cn.itlym.platform + system-dictionary + + + + + + + + dev + + dev + + + true + + + + + + test + + test + + + + + + prod + + prod + + + + + + + ${project.artifactId} + + + + ../../dynamicConfig/config-${profile.active}.properties + + + + + src/main/resources + + **/* + + + true + + + + + + \ No newline at end of file diff --git a/shoulder-system-center/system-start/src/main/java/cn/itlym/shoulder/platform/system/star/SystemCenter.java b/shoulder-system-center/system-start/src/main/java/cn/itlym/shoulder/platform/system/star/SystemCenter.java new file mode 100644 index 0000000..d970f84 --- /dev/null +++ b/shoulder-system-center/system-start/src/main/java/cn/itlym/shoulder/platform/system/star/SystemCenter.java @@ -0,0 +1,18 @@ +package cn.itlym.shoulder.platform.system.star; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 系统中心 + * + * @author lym + */ +@SpringBootApplication(scanBasePackages = "cn.itlym.shoulder.platform.system") +public class SystemCenter { + + public static void main(String[] args) { + SpringApplication.run(SystemCenter.class, args); + } + +} diff --git a/shoulder-system-center/system-start/src/main/resources/banner.txt b/shoulder-system-center/system-start/src/main/resources/banner.txt new file mode 100644 index 0000000..bdde36d --- /dev/null +++ b/shoulder-system-center/system-start/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} ___) | |_| \__ \ || __/ | | | | |_____| |__| __/ | | | || __/ | ${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} diff --git a/shoulder-system-center/system-start/src/main/resources/bootstrap.yml b/shoulder-system-center/system-start/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..6bd4129 --- /dev/null +++ b/shoulder-system-center/system-start/src/main/resources/bootstrap.yml @@ -0,0 +1,58 @@ +# 先从环境变量里取,若不存在,则以 maven 打包时的配置为准 +shoulder: + nacos: + ip: ${NACOS_IP:@nacos.ip@} + port: ${NACOS_PORT:@nacos.port@} + namespace: ${NACOS_ID:@nacos.namespace@} + +# 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: + #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: ${spring.application.name}.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:} + +boot: + admin: + client: + url: localhost:12365 + #username: + #password: + instance: + prefer-ip: true + service-url: localhost:8080 \ No newline at end of file diff --git a/wiki.md b/wiki.md new file mode 100644 index 0000000..0aaf4ab --- /dev/null +++ b/wiki.md @@ -0,0 +1,38 @@ +# shoulder-platform wiki + +# 目录 & 能力介绍 +创建工程 +- 通过 `maven` 的 `archetype` 快速创建工程骨架 + - 基于 `shoulder-framework`(面向spring-boot) + - 基于 `shoulder-platform`(面向 spring-cloud) +版本管理pom:`shoulder-dependencies` +父级pom:`shoulder-parent` +web 增强 shoulder-web(spring-boot-starter-web增强) + - 统一业务异常拦截 + - 统一返回值包装 + - xss、csrf(考虑抽出来?) + - 参数校验 + - 请求日志美化(仅开发态) +核心包:shoulder-core + - 异常定义 + - 日志增强 + - 通用工具 + - 应用信息 + - 定义了通用的返回值,参数 + +日志相关(运行日志,操作日志,追踪日志) + 运行日志:shoulder-core 里 + 操作日志:shoulder-business-log + 追踪日志:未封装,采用 zipkin + + +安全与认证(基于 `spring security`) + 单点登录:shoulder-cas + 支持跨域的认证:JWT +缓存:未封装,采用 spring-cache +集群会话共享:未封装,采用 spring-session + +---- + +cloud +