diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000000000000000000000000000000000000..893e111d9619187a9efcea653696961b87157902 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,72 @@ +name: Bug Report +title: "[Bug] [Module Name] Please describe the problem in a short sentence" +description: When you find a Bug in your code that causes your application to crash or throw an exception, or there is a problem with a component, or something doesn't look right, you can bring it up here. +labels: [ "bug" ] +body: + - type: markdown + attributes: + value: | + Thank you for your support and attention to the project. Before asking a question, make sure you have reviewed the [documentation](https://github.com/forward-seen/share/tree/master/docs)。 + + - type: checkboxes + attributes: + label: Search for issues + description: Please first check in the issues to see if the issue already exists to avoid submitting duplicate questions. + options: + - label: I've looked through all the [issues](https://github.com/forward-seen/share/issues), could not find a solution to my problem. + required: true + + - type: textarea + attributes: + label: How to reproduce + description: Please tell us in detail how to reproduce the problem you encountered. If it involves code, you can provide a minimal code example and attach it using backticks ```. + placeholder: | + 1. ... + 2. ... + 3. ... + validations: + required: true + + - type: textarea + attributes: + label: Expected result + description: Please tell us what you expect to happen. + validations: + required: true + + - type: textarea + attributes: + label: Actual result + description: Please tell us what actually happened. + validations: + required: true + + - type: textarea + attributes: + label: Screenshots or videos + description: If you can, upload any screenshots of the bug. + placeholder: | + [Upload pictures here] + + - type: dropdown + id: version + attributes: + label: Version + description: Which version/branch of our software are you currently using? + options: + - master + validations: + required: true + + - type: checkboxes + attributes: + label: Code of conduct + description: | + The code of conduct helps to create a harmonious communication environment for all. We ask everyone to agree to abide by this code. + options: + - label: > + I agree with the program's [Code of conduct](https://www.apache.org/foundation/policies/conduct) + required: true + - type: markdown + attributes: + value: "Thank you for your feedback, we will reply to your question as soon as possible." \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000000000000000000000000000000000000..8c1ce9561bd9763f4b4c22ca2cee1ded37c98920 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,70 @@ +name: Feature Request +description: Make a functional suggestion for this project. +title: "[Feature][Module Name] Please describe the feature proposal in a short sentence" +labels: [ "feature" ] +body: + - type: markdown + attributes: + value: | + Thanks for the feature suggestion, we will consider it carefully! + + - type: textarea + id: desired-solution + attributes: + label: Description + description: Please describe the functionality you want in as much detail as possible. + validations: + required: true + + - type: textarea + id: desired-solution + attributes: + label: Application scenario + description: Please describe the usage scenario of this function. + validations: + required: false + + - type: textarea + id: related-problem + attributes: + label: Correlation problem + description: Are there other issues related to this at the moment? Clearly and concisely describe what the problem is, for example, when I... When, I always feel troubled. + validations: + required: false + + - type: textarea + id: alternatives + attributes: + label: Alternative scheme + description: What alternatives have you considered? Clearly and concisely describe any alternative solutions or features that you have considered. + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Reference material + description: Do you have additional context or screenshots? Add any additional context or screenshots about the feature request here. + validations: + required: false + + - type: checkboxes + attributes: + label: Participation contribution + description: > + Would you like to mention PR? This is by no means mandatory, new contributors are welcome, and we are happy to guide you through the contribution process. + options: + - label: I intend to participate in the development implementation of specific features and contribute code back to the upstream community. + + - type: checkboxes + attributes: + label: Code of conduct + description: | + The code of conduct helps to create a harmonious communication environment for all. We ask everyone to agree to abide by this code. + options: + - label: > + I agree with the program [code of conduct](https://www.apache.org/foundation/policies/conduct) + required: true + - type: markdown + attributes: + value: "Thank you for your advice and we will get back to you as soon as possible." \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000000000000000000000000000000000000..261f67110dc18b3c0ad9332c2d0fd97b3bedc40b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,39 @@ +name: Question +title: "[Question] [Module Name] Please describe the topic of discussion in a short sentence" +description: If you have any questions, suggestions or comments about the project, please feel free to discuss them here. +labels: [ "question" ] +body: + - type: checkboxes + attributes: + label: Search for question + description: Please first check in the issues to see if the issue already exists to avoid submitting duplicate questions. + options: + - label: I've looked through all the [issues] (https://github.com/forward-seen/share/issues), didn't find what I want. + required: true + + - type: textarea + attributes: + label: backdrop + description: Please provide background information about this discussion topic, such as your usage scenarios, problems encountered, etc. + validations: + required: true + + - type: textarea + attributes: + label: Expected result + description: What kind of feedback or outcome do you expect from this discussion? + validations: + required: true + + - type: checkboxes + attributes: + label: Code of conduct + description: | + The code of conduct helps to create a harmonious communication environment for all. We ask everyone to agree to abide by this code. + options: + - label: > + I agree with the program [code of conduct](https://www.apache.org/foundation/policies/conduct) + required: true + - type: markdown + attributes: + value: "Thank you for submitting the Discussion to us. We will respond to the discussion you initiated as soon as possible." \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/umbrella.yml b/.github/ISSUE_TEMPLATE/umbrella.yml new file mode 100644 index 0000000000000000000000000000000000000000..021449b365d5589f32708639a4a871de9cbdef11 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/umbrella.yml @@ -0,0 +1,34 @@ +name: Umbrella +title: "[Umbrella] Please describe the problem in a short sentence" +description: A broader problem, or a complex problem that needs to be broken down and solved step-by-step. +labels: [ "umbrella" ] +body: + + - type: checkboxes + attributes: + label: Search for issues + description: Please first check in the issues to see if the issue already exists to avoid submitting duplicate questions. + options: + - label: I've looked through all the [issues](https://github.com/forward-seen/share/issues), could not find a solution to my problem. + required: true + + - type: textarea + attributes: + label: Complete description + placeholder: > + Please describe your problem clearly. + validations: + required: true + + - type: checkboxes + attributes: + label: 行为准则 + description: | + The code of conduct helps to create a harmonious communication environment for all. We ask everyone to agree to abide by this code. + options: + - label: > + I agree with the program [code of conduct](https://www.apache.org/foundation/policies/conduct) + required: true + - type: markdown + attributes: + value: "Thank you for taking the time to ask!" \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000000000000000000000000000000..778be67de2eb1a28cefb29de2eaf46fea04970a0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,49 @@ + + +### Related Issue + + +### Reason + + +### Description + + +### Test case + + + diff --git a/.gitignore b/.gitignore index a463bc240bd6a2196fa58e1e8cb61b2b3acf76d1..a86e15ed5d99f3000214ac9e8c312eb65061933a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +logs **.log ### IntelliJ IDEA ### diff --git a/bin/update_version.ps1 b/bin/update_version.ps1 index 434af4dd82b90067cc83ac3aba7715bb3e4defb7..0aa63f9d120f2d7f27722839c58f20c356d9fcee 100644 --- a/bin/update_version.ps1 +++ b/bin/update_version.ps1 @@ -9,8 +9,11 @@ $pomFiles = @( "..\share-auth\pom.xml", "..\share-common\pom.xml", "..\share-common\share-core\pom.xml", + "..\share-common\share-core\share-cloud\pom.xml", "..\share-common\share-core\share-protocol\pom.xml", + "..\share-common\share-core\share-web\pom.xml", "..\share-common\share-manager\pom.xml", + "..\share-common\share-manager\redis-manager\pom.xml", "..\share-common\share-starter\pom.xml", "..\share-common\share-starter\oss-spring-boot-starter\pom.xml", "..\share-domain\pom.xml", diff --git a/docs/contribution/coding-guide.md b/docs/contribution/coding-guide.md index 3c7b43d22a6ab89590ee218450071614c116eb76..69a31232b2a9816354a399378822f6a6f12a85ce 100644 --- a/docs/contribution/coding-guide.md +++ b/docs/contribution/coding-guide.md @@ -21,12 +21,26 @@ 接下来可以拉代码到你本地。 +```shell +git clone git@atomgit.com:xinfengwen/share.git +``` + +除了Open Atom,我们在Github、Gitee、GitCode也有仓库,你可以根据你的偏好选择平台。 + +```shell +git clone git@github.com:forward-seen/share.git +``` ```shell git clone git@gitee.com:forward-seen/share.git ``` +```shell +git clone git@gitcode.com:forward-seen/share.git +``` ### IDEA 配置 +使用IDEA打开项目,并且需要完成以下项目和工具的配置。 + **编码统一改为UTF-8:** > File > Settings > Editor > File Encodings @@ -82,12 +96,12 @@ java -jar share-xxx-${version}.jar ### 提交PR -1. 前提是你需要掌握Git以及Gitee的基本使用和配置; -2. fork仓库到你的Gitee账户; -3. 在本地提交你代码,并push到你的Gitee仓库分支; +1. 前提是你需要掌握Git以及云上托管平台(Atom/Github/Gitee/GitCode)的基本使用和配置; +2. fork仓库到你的平台账户; +3. 在本地提交你代码,并push到你的平台仓库分支; 4. 在你的分支向源仓库申请PR,按照提示要求检查和填写。 -> 注意:由于提交模板限制,本仓库不支持轻量级PR,且你的PR标题应与release-note记录保持一致。 +> 注意:由于提交模板限制,本仓库不支持轻量级PR,且你的PR标题应与release-note新增记录保持一致。 ### 发布版本 @@ -112,4 +126,4 @@ $pomFiles = @( ### 发布release版本 -release版本应创建单独分支,并以`版本号-release`结尾命名。 +release版本应创建单独的保护分支,并以`版本号.release`形式命名分支。 diff --git a/pom.xml b/pom.xml index 9107f55ae237d93f69fb3d6b34ff3d72157604d1..2a9d158be4a3f5634f44fa9d810c1af8209f2d9c 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.shine share - 0.0.3 + 0.0.4 Shine Share https://gitee.com/forward-seen/share @@ -14,14 +14,23 @@ pom - ${project.version} + 3.15.0 + 2.3.3 + 4.3.0 + 2.0.47 + 5.8.11 17 - UTF-8 - 3.3.1 - 2021.0.8 - 2021.0.5.0 1.18.32 + 3.5.6 + 1.4.13 1.12.261 + 42.7.2 + UTF-8 + ${project.version} + 3.2.4 + 3.0.0-M4 + 2023.0.0 + 2023.0.1.2 @@ -37,19 +46,6 @@ - - - org.projectlombok - lombok - ${lombok.version} - provided - - - com.shine - share-core - ${share.version} - - org.springframework.boot @@ -59,7 +55,7 @@ import - + org.springframework.cloud spring-cloud-dependencies @@ -74,6 +70,32 @@ pom import + + de.codecentric + spring-boot-admin-dependencies + ${spring-boot-admin.version} + pom + import + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + com.github.yulichang + mybatis-plus-join-boot-starter + ${mybatis-plus-join.version} + + + + + org.postgresql + postgresql + ${postgresql.version} + @@ -81,6 +103,66 @@ aws-java-sdk-s3 ${oss-aws.version} + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + pro.fessional + kaptcha + ${kaptcha.version} + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + org.apache.commons + commons-lang3 + ${apache-commons.version} + + + + com.shine + share-core + ${share.version} + + + com.shine + share-protocol + ${share.version} + + + com.shine + share-web + ${share.version} + + + com.shine + share-cloud + ${share.version} + + + com.shine + share-manager + ${share.version} + + + com.shine + redis-manager + ${share.version} + + + com.shine + share-starter + ${share.version} + + @@ -129,18 +211,26 @@ - public - aliyun nexus + aliyun-nexus + Aliyun Nexus https://maven.aliyun.com/repository/public true + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + - public - aliyun nexus + aliyun-nexus + Aliyun Nexus https://maven.aliyun.com/repository/public true diff --git a/release-note.md b/release-note.md index a1cc5a24184833ec449cc0f88a2b335b898b7b77..3ae6d31cabba8536860e5e63c67449f688618cdf 100644 --- a/release-note.md +++ b/release-note.md @@ -2,28 +2,31 @@ [//]: # (说明:) -[//]: # (1. Bug/Improve/Feature/Docs只记录面向用户的变化;) +[//]: # (1. Bugfix/Feature/Improve/Docs只记录面向用户的变化;) -[//]: # (2. Project记录面向开发者的重要变化,发布release时不展示该部分内容。) +[//]: # (2. Technology记录面向开发者的重要变化。) -[//]: # (## Bug fix) +[//]: # (## 问题修复 Bugfix) -[//]: # (## Improve) +## 新功能 Feature +- [Feature] [Gateway] 增加网关应用,支持数字和字符验证码校验 +[//]: # (## 优化改进 Improve) -[//]: # (## Feature) +## 日常维护 Chore -## Docs +- [Project] 初始化项目 +- [Project] 创建项目结构的各个模块 + +## 文档更新 Docs - [Docs] 增加项目介绍 - [Docs] 增加编码指南开发环境说明 -## Project +## 技术相关 Technology -- [Project] 初始化项目 -- [Project] 创建项目结构的各个模块 -- [Project] [Bin] 增加一键修改升级版本号的命令脚本 +- [Bin] 增加一键修改升级版本号的命令脚本 - [Common] [Starter] [OSS] 增加通用文件对象存储服务Starter \ No newline at end of file diff --git a/share-api/pom.xml b/share-api/pom.xml index 1653180de3729fca68a3550167a4be5ff05068bc..c31770295bfc5b67f9a9bcbee216034d7e62c9fb 100644 --- a/share-api/pom.xml +++ b/share-api/pom.xml @@ -6,7 +6,7 @@ com.shine share - 0.0.3 + 0.0.4 share-api diff --git a/share-auth/pom.xml b/share-auth/pom.xml index 8219c0a90e5f23c6a84a798fcbfe772f97adbcc1..10e41592ebe048f0f1278a34593460970a22de61 100644 --- a/share-auth/pom.xml +++ b/share-auth/pom.xml @@ -6,7 +6,7 @@ com.shine share - 0.0.3 + 0.0.4 share-auth @@ -35,5 +35,11 @@ + + + src/main/resources + true + + diff --git a/share-auth/src/main/java/com/shine/share/auth/AuthApplication.java b/share-auth/src/main/java/com/shine/share/auth/AuthApplication.java index a0b913a6635fd88a4eea93a08e1a46e1e7132b87..b7afe20f5e78e2180643baf0546414a3ff49d5d5 100644 --- a/share-auth/src/main/java/com/shine/share/auth/AuthApplication.java +++ b/share-auth/src/main/java/com/shine/share/auth/AuthApplication.java @@ -4,7 +4,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** - * TODO to describe AuthApplication + * 认证授权服务中心应用启动入口 * * @author 辛凤文 * @since 1.0 diff --git a/share-common/pom.xml b/share-common/pom.xml index e7adc90edf29888641984a2a0086d4b7af88e188..399ff2385cdfc784cb131030e81b693b60369281 100644 --- a/share-common/pom.xml +++ b/share-common/pom.xml @@ -6,7 +6,7 @@ com.shine share - 0.0.3 + 0.0.4 share-common diff --git a/share-common/share-core/pom.xml b/share-common/share-core/pom.xml index 9edffb2c37e037719d3b857253ab58a9f4a0a2fd..e2d1b0a4ce0af1a895f78aa110cf09daf2f95b23 100644 --- a/share-common/share-core/pom.xml +++ b/share-common/share-core/pom.xml @@ -6,7 +6,7 @@ com.shine share-common - 0.0.3 + 0.0.4 share-core @@ -16,6 +16,8 @@ share-protocol + share-web + share-cloud diff --git a/share-common/share-core/share-cloud/pom.xml b/share-common/share-core/share-cloud/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..909e9982102e0a320808ec208149da47694f5e93 --- /dev/null +++ b/share-common/share-core/share-cloud/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + com.shine + share-core + 0.0.4 + + + share-cloud + Share Common Core Cloud + Share微服务依赖 + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + diff --git a/share-common/share-core/share-protocol/pom.xml b/share-common/share-core/share-protocol/pom.xml index a96e350a8f76d4bef71cc8406bfbdafd5931ffb3..84a3f5be688b49d41d2c84a6f9bfd052e8a01a54 100644 --- a/share-common/share-core/share-protocol/pom.xml +++ b/share-common/share-core/share-protocol/pom.xml @@ -6,7 +6,7 @@ com.shine share-core - 0.0.3 + 0.0.4 share-protocol diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/ErrorCode.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/Error.java similarity index 97% rename from share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/ErrorCode.java rename to share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/Error.java index 75227daaefc70723e04b6911d28e447e10cef3af..1618ffa0bf665cf2a3e9dc8aa11dbc9512b86e9f 100644 --- a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/ErrorCode.java +++ b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/Error.java @@ -15,7 +15,7 @@ import lombok.Getter; * @since 1.0 */ @Getter -public enum ErrorCode { +public enum Error implements ErrorDefinition { /** * 成功 */ @@ -83,7 +83,6 @@ public enum ErrorCode { A0323("A0323", "非法IP地址"), A0324("A0324", "网关访问受限"), A0325("A0325", "地域黑名单"), - A0330("A0330", "服务已欠费"), A0340("A0340", "用户签名异常"), A0341("A0341", "RSA签名错误"), /** @@ -112,9 +111,6 @@ public enum ErrorCode { A0432("A0432", "图片包含违禁信息"), A0433("A0433", "文件侵犯版权"), A0440("A0440", "用户操作异常"), - A0441("A0441", "用户支付超时"), - A0442("A0442", "确认订单超时"), - A0443("A0443", "订单已关闭"), /** * 二级宏观错误码 */ @@ -182,7 +178,7 @@ public enum ErrorCode { * 系统异常 * 一级宏观错误码 */ - B0001("B0001", "系统执行出错"), + B0001("B0001", "抱歉,系统出现一些意外~"), /** * 二级宏观错误码 @@ -286,7 +282,7 @@ public enum ErrorCode { private final String desc; - ErrorCode(String code, String desc) { + Error(String code, String desc) { this.code = code; this.desc = desc; } diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/Code.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/ErrorDefinition.java similarity index 40% rename from share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/Code.java rename to share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/ErrorDefinition.java index 8d60db11aa85b050915100a1ebab246828d9da39..79577088e4feeb871a35b9353611c7f45230ce79 100644 --- a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/Code.java +++ b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/constant/ErrorDefinition.java @@ -1,15 +1,15 @@ package com.shine.share.protocol.constant; /** - * TODO to describe Code + * 错误码定义接口 + * 错误码枚举类实现该接口 * * @author 辛凤文 * @since 1.0 */ -public interface Code { +public interface ErrorDefinition { - String code(); - - String desc(); + String getCode(); + String getDesc(); } diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/domain/BaseEntity.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/domain/BaseEntity.java index 0931aeb29e52f13bf9bddad9db6507da8fcf0706..6a859d6837321630fad3b6fb32629f64fb3cdd03 100644 --- a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/domain/BaseEntity.java +++ b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/domain/BaseEntity.java @@ -2,18 +2,21 @@ package com.shine.share.protocol.domain; import lombok.Data; -import java.io.Serializable; import java.time.LocalDateTime; /** * 实体类基础属性,用于映射数据库表的通用字段 - * 业务entity类继承该类,并加上lombok注解@EqualsAndHashCode(callSuper = true)、@ToString(callSuper = true)、@Data + * 业务entity类继承该类,并加上lombok注解@EqualsAndHashCode(callSuper = true) + * 、@ToString(callSuper = true)、@Data * * @author 辛凤文 * @since 1.0 */ @Data -public class BaseEntity implements Serializable { +public class BaseEntity implements java.io.Serializable { + + @java.io.Serial + private static final long serialVersionUID = 1L; private Integer id; diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/exception/BusinessException.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/exception/BusinessException.java index 9082197e4e81b56e8a5c876c567675fa9351c385..7b442e55be7d4aa7001371363872c5aff28efc32 100644 --- a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/exception/BusinessException.java +++ b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/exception/BusinessException.java @@ -1,30 +1,34 @@ package com.shine.share.protocol.exception; -import com.shine.share.protocol.constant.ErrorCode; +import com.shine.share.protocol.constant.Error; +import com.shine.share.protocol.constant.ErrorDefinition; +import lombok.Getter; /** * 业务异常 - * 因业务逻辑错误而诱发的异常 + * 因业务逻辑错误而发生的异常 * * @author 辛凤文 * @since 1.0 */ +@Getter public class BusinessException extends RuntimeException { - protected ErrorCode errorCode; + protected ErrorDefinition error; protected String desc; - public BusinessException(ErrorCode errorCode) { - this.errorCode = errorCode; + public BusinessException() { + this.error = Error.B0001; } - public BusinessException(ErrorCode errorCode, String desc) { - this.errorCode = errorCode; - this.desc = desc; + public BusinessException(ErrorDefinition error) { + this.error = error; } - public BusinessException() { - this.errorCode = ErrorCode.B0001; + public BusinessException(ErrorDefinition error, String desc) { + this.error = error; + this.desc = desc; } + } diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/exception/ServiceException.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/exception/ServiceException.java index 52dc1e58df9dc3f7bec50582be4dbba895baf6e7..745fd2a62190493848aeecd560ae7c67cfe0a8ae 100644 --- a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/exception/ServiceException.java +++ b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/exception/ServiceException.java @@ -1,31 +1,34 @@ package com.shine.share.protocol.exception; -import com.shine.share.protocol.constant.ErrorCode; +import com.shine.share.protocol.constant.Error; +import com.shine.share.protocol.constant.ErrorDefinition; +import lombok.Getter; /** * 服务异常 - * 因系统服务出错诱发的异常 + * 因系统服务出错而发生的异常 * * @author 辛凤文 * @since 1.0 */ +@Getter public class ServiceException extends RuntimeException { - protected ErrorCode errorCode; + protected ErrorDefinition error; protected String desc; - public ServiceException(ErrorCode errorCode) { - this.errorCode = errorCode; + public ServiceException() { + this.error = Error.B0001; } - public ServiceException(ErrorCode errorCode, String desc) { - this.errorCode = errorCode; - this.desc = desc; + public ServiceException(ErrorDefinition error) { + this.error = error; } - public ServiceException() { - this.errorCode = ErrorCode.B0001; + public ServiceException(ErrorDefinition error, String desc) { + this.error = error; + this.desc = desc; } } diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/helper/ExceptionHelper.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/helper/ExceptionHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..d1256ad66e6eb7b2636d50498a23944c02dc33c5 --- /dev/null +++ b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/helper/ExceptionHelper.java @@ -0,0 +1,34 @@ +package com.shine.share.protocol.helper; + +import com.shine.share.protocol.constant.Error; +import com.shine.share.protocol.exception.BusinessException; +import com.shine.share.protocol.exception.ServiceException; +import com.shine.share.protocol.web.Result; + +/** + * 将抛出的异常统一转换为结果集 + * + * @author 辛凤文 + * @since 1.0 + */ +public class ExceptionHelper { + + public static Result toResult(ServiceException e) { + if (e.getDesc() == null) { + return Result.error(e.getError()); + } + return Result.error(e.getError().getCode(), "[" + e.getError().getCode() + "]-" + e.getDesc()); + } + + public static Result toResult(BusinessException e) { + if (e.getDesc() == null) { + return Result.error(e.getError()); + } + return Result.error(e.getError().getCode(), "[" + e.getError().getCode() + "]-" + e.getDesc()); + } + + public static Result toResult() { + return Result.error(Error.B0001); + } + +} diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/helper/RemoteHelper.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/helper/RemoteHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..b0dff798f386c4f9dbc18565dc11a894f059cd4c --- /dev/null +++ b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/helper/RemoteHelper.java @@ -0,0 +1,25 @@ +package com.shine.share.protocol.helper; + +import com.shine.share.protocol.constant.Error; +import com.shine.share.protocol.web.Result; + +/** + * RPC远程调用结果处理 + * + * @author 辛凤文 + * @since 1.0 + */ +public class RemoteHelper { + + public static boolean success(Result res) { + if (res == null) { + return false; + } + return Error.SUCCESS.getCode().equals(res.getCode()); + } + + public static boolean fail(Result res) { + return !success(res); + } + +} diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/BaseController.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/BaseController.java deleted file mode 100644 index 989b2163c0c0fcac996a2371f5e25c53e741a9b5..0000000000000000000000000000000000000000 --- a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/BaseController.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.shine.share.protocol.web; - -/** - * 实现该接口使用success或error方法传递参数封装统一返回结果集 - * - * @author 辛凤文 - * @since 1.0 - */ -public class BaseController { -} diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/BaseMapping.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/BaseMapping.java deleted file mode 100644 index a87f44982da9dbe52a672ea7db84a77008ce5453..0000000000000000000000000000000000000000 --- a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/BaseMapping.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.shine.share.protocol.web; - -/** - * TODO to describe BaseMapping - * - * @author 辛凤文 - * @since 1.0 - */ -public interface BaseMapping { - -} diff --git a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/Result.java b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/Result.java index 95e4b07dfe0a6d833794b2596d252bb665d559a1..d0bebee04eea2be74d93ee48447a341d13dab08c 100644 --- a/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/Result.java +++ b/share-common/share-core/share-protocol/src/main/java/com/shine/share/protocol/web/Result.java @@ -1,15 +1,20 @@ package com.shine.share.protocol.web; +import com.shine.share.protocol.constant.Error; +import com.shine.share.protocol.constant.ErrorDefinition; import lombok.Data; /** - * TODO to describe Result + * API接口统一返回格式 * * @author 辛凤文 * @since 1.0 */ @Data -public class Result { +public class Result implements java.io.Serializable { + + @java.io.Serial + private static final long serialVersionUID = 1L; private String code; @@ -25,19 +30,32 @@ public class Result { this.msg = msg; } + public Result(T data) { + this.code = Error.SUCCESS.getCode(); + this.msg = Error.SUCCESS.getDesc(); + this.data = data; + } + public Result(String code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } - //TODO public static Result success() { - Result result = new Result<>(); - //result.setCode(HttpStatus.SUCCESS); - //result.setMsg("success"); - return result; + return new Result<>(Error.SUCCESS.getCode(), Error.SUCCESS.getDesc()); } + public static Result success(T data) { + return new Result<>(data); + } + + public static Result error(ErrorDefinition error) { + return new Result<>(error.getCode(), error.getDesc()); + } + + public static Result error(String code, String msg) { + return new Result<>(code, msg); + } } diff --git a/share-common/share-core/share-web/pom.xml b/share-common/share-core/share-web/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..3473be908dabadd85ecf994108f4c8fda257b181 --- /dev/null +++ b/share-common/share-core/share-web/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + com.shine + share-core + 0.0.4 + + + share-web + Share Common Core Web + Share Web应用依赖 + + + + org.springframework.boot + spring-boot-starter-web + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + org.postgresql + postgresql + runtime + + + com.shine + share-protocol + + + + diff --git a/share-common/share-core/share-web/src/main/java/com/shine/share/web/config/MybatisPlusConfiguration.java b/share-common/share-core/share-web/src/main/java/com/shine/share/web/config/MybatisPlusConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..beebcb5d81567e0a602a4b8ff188380cd9ac21f6 --- /dev/null +++ b/share-common/share-core/share-web/src/main/java/com/shine/share/web/config/MybatisPlusConfiguration.java @@ -0,0 +1,34 @@ +package com.shine.share.web.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * Mybatis Plus 配置 + * + * @author 辛凤文 + * @since 1.0 + */ +@Configuration +@EnableTransactionManagement +public class MybatisPlusConfiguration { + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); + paginationInnerInterceptor.setOptimizeJoin(true); + paginationInnerInterceptor.setDbType(DbType.POSTGRE_SQL); + paginationInnerInterceptor.setOverflow(true); + interceptor.addInnerInterceptor(paginationInnerInterceptor); + OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor = new OptimisticLockerInnerInterceptor(); + interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor); + return interceptor; + } + +} diff --git a/share-common/share-core/share-web/src/main/java/com/shine/share/web/controller/BaseController.java b/share-common/share-core/share-web/src/main/java/com/shine/share/web/controller/BaseController.java new file mode 100644 index 0000000000000000000000000000000000000000..d83c1356f3f4aa3303dfc32de1d94a2d8ce7b8db --- /dev/null +++ b/share-common/share-core/share-web/src/main/java/com/shine/share/web/controller/BaseController.java @@ -0,0 +1,27 @@ +package com.shine.share.web.controller; + + +import com.shine.share.protocol.constant.Error; +import com.shine.share.protocol.web.Result; + +/** + * 实现该接口使用success或error方法传递参数封装统一返回结果集 + * + * @author 辛凤文 + * @since 1.0 + */ +public class BaseController { + + public Result error(Error errorCode) { + return Result.error(errorCode); + } + + public Result success() { + return Result.success(); + } + + public Result success(T data) { + return Result.success(data); + } + +} diff --git a/share-common/share-core/share-web/src/main/java/com/shine/share/web/handler/GlobalExceptionHandler.java b/share-common/share-core/share-web/src/main/java/com/shine/share/web/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..f7cb8f220c6285f8aeff26338887180d3438e1f5 --- /dev/null +++ b/share-common/share-core/share-web/src/main/java/com/shine/share/web/handler/GlobalExceptionHandler.java @@ -0,0 +1,36 @@ +package com.shine.share.web.handler; + +import com.shine.share.protocol.constant.Error; +import com.shine.share.protocol.exception.BusinessException; +import com.shine.share.protocol.exception.ServiceException; +import com.shine.share.protocol.helper.ExceptionHelper; +import com.shine.share.protocol.web.Result; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常捕获 + * + * @author 辛凤文 + * @since 1.0 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + public Result errorResult(ServiceException e) { + log.error("[{}]-{}, error code : {}", e.getError(), e.getDesc(), e.getError(), e); + return ExceptionHelper.toResult(e); + } + + public Result errorResult(BusinessException e) { + log.error("[{}]-{}, error code : {}", e.getError(), e.getDesc(), e.getError(), e); + return ExceptionHelper.toResult(e); + } + + public Result errorResult(Exception e) { + log.error("error code : {}", Error.B0001, e); + return ExceptionHelper.toResult(); + } + +} diff --git a/share-common/share-core/share-web/src/main/java/com/shine/share/web/util/BeanUtils.java b/share-common/share-core/share-web/src/main/java/com/shine/share/web/util/BeanUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..86f4a7f2b2c501969e87c5e523781ae33600787e --- /dev/null +++ b/share-common/share-core/share-web/src/main/java/com/shine/share/web/util/BeanUtils.java @@ -0,0 +1,34 @@ +package com.shine.share.web.util; + +/** + * Bean转换、处理工具类 + * + * @author 辛凤文 + * @since 1.0 + */ +public class BeanUtils { + + public static T convert(Object sourceBean, Class targetClass) { + if (sourceBean == null || targetClass == null) { + return null; + } + try { + T instance = targetClass.getDeclaredConstructor().newInstance(); + org.springframework.beans.BeanUtils.copyProperties(sourceBean, instance); + return instance; + } catch (Exception e) { + return null; + } + } + + /** + * 将source的属性值赋给target + */ + public static void copy(Object source, Object target) { + if (source == null || target == null) { + return; + } + org.springframework.beans.BeanUtils.copyProperties(source, target); + } + +} diff --git a/share-common/share-manager/pom.xml b/share-common/share-manager/pom.xml index a4806d6b50fa2d5899432438491915d8148bae7e..6dd8d4c2a5a49ff05ca787363d93586a7bd748f7 100644 --- a/share-common/share-manager/pom.xml +++ b/share-common/share-manager/pom.xml @@ -6,7 +6,7 @@ com.shine share-common - 0.0.3 + 0.0.4 share-manager @@ -14,4 +14,8 @@ Share项目基础设施服务管理模块 pom + + redis-manager + + diff --git a/share-common/share-manager/redis-manager/pom.xml b/share-common/share-manager/redis-manager/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..f947713054fb43c24eed3f2f70f59fbfdbdb1599 --- /dev/null +++ b/share-common/share-manager/redis-manager/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + com.shine + share-manager + 0.0.4 + + + redis-manager + Share Common Manager Redis + Redis管理工具 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.alibaba.fastjson2 + fastjson2 + + + + diff --git a/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/config/FastJson2JsonRedisSerializer.java b/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..153cc9d28a8014de2d9bd4da9bed47cd6c819b26 --- /dev/null +++ b/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,51 @@ +package com.shine.share.redis.config; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.filter.Filter; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Redis使用FastJson序列化 + * + * @author 辛凤文 + * @since 1.0 + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer { + /** + * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) + */ + public static final String[] JSON_WHITELIST_STR = {"org.springframework", "com.shine"}; + + public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(JSON_WHITELIST_STR); + + private Class clazz; + + public FastJson2JsonRedisSerializer(Class clazz) { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException { + if (t == null) { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException { + if (bytes == null || bytes.length == 0) { + return null; + } + return JSON.parseObject(new String(bytes, DEFAULT_CHARSET), clazz, AUTO_TYPE_FILTER); + } +} diff --git a/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/config/RedisConfigurer.java b/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/config/RedisConfigurer.java new file mode 100644 index 0000000000000000000000000000000000000000..118601fe5d6e247b4cb5197e51b36fd25e7592f5 --- /dev/null +++ b/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/config/RedisConfigurer.java @@ -0,0 +1,39 @@ +package com.shine.share.redis.config; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.cache.annotation.CachingConfigurer; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author 辛凤文 + * @since 1.0 + */ +@Configuration +@EnableCaching +@AutoConfigureBefore(RedisAutoConfiguration.class) +public class RedisConfigurer implements CachingConfigurer { + + @Bean + @SuppressWarnings(value = {"unchecked", "rawtypes"}) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + template.afterPropertiesSet(); + return template; + } +} diff --git a/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/constant/CacheConstants.java b/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/constant/CacheConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..0cfa63a9d42ea9dbb67fe9daf7cf63a3f56b8172 --- /dev/null +++ b/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/constant/CacheConstants.java @@ -0,0 +1,61 @@ +package com.shine.share.redis.constant; + +/** + * 缓存常量信息 + * + * @author ruoyi + * @since 1.0 + */ +public class CacheConstants { + /** + * 缓存有效期,默认720(分钟) + */ + public final static long EXPIRATION = 720; + + /** + * 缓存刷新时间,默认120(分钟) + */ + public final static long REFRESH_TIME = 120; + + /** + * 密码最大错误次数 + */ + public final static int PASSWORD_MAX_RETRY_COUNT = 5; + + /** + * 密码锁定时间,默认10(分钟) + */ + public final static long PASSWORD_LOCK_TIME = 10; + + /** + * 权限缓存前缀 + */ + public final static String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; + + public static final String EMAIL_CAPTCHA_CODE_KEY = "email_captcha_codes:"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 登录账户密码错误次数 redis key + */ + public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; + + /** + * 登录IP黑名单 cache key + */ + public static final String SYS_LOGIN_BLACKIPLIST = SYS_CONFIG_KEY + "sys.login.blackIPList"; +} diff --git a/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/manager/RedisManager.java b/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/manager/RedisManager.java new file mode 100644 index 0000000000000000000000000000000000000000..499d388c0764362c52bb97a5b508211cf7db44d0 --- /dev/null +++ b/share-common/share-manager/redis-manager/src/main/java/com/shine/share/redis/manager/RedisManager.java @@ -0,0 +1,245 @@ +package com.shine.share.redis.manager; + +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.*; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * Spring redis 工具类 + * + * @author 辛凤文 + * @since 1.0 + **/ +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Component +public class RedisManager { + + @Resource + public RedisTemplate redisTemplate; + + @Resource + StringRedisTemplate stringRedisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) { + return Boolean.TRUE.equals(redisTemplate.delete(key)); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } +} diff --git a/share-common/share-manager/redis-manager/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/share-common/share-manager/redis-manager/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000000000000000000000000000000000..f0cd1a42fec77584d71752829ce145ffef1acb34 --- /dev/null +++ b/share-common/share-manager/redis-manager/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +com.shine.share.redis.config.RedisConfigurer +com.shine.share.redis.manager.RedisManager diff --git a/share-common/share-starter/oss-spring-boot-starter/pom.xml b/share-common/share-starter/oss-spring-boot-starter/pom.xml index 5ceb42e2dfeb82b9658403b649ab37bdf7c95a10..1723f7e16f77f42384ac95c976296405d1d6c332 100644 --- a/share-common/share-starter/oss-spring-boot-starter/pom.xml +++ b/share-common/share-starter/oss-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ com.shine share-starter - 0.0.3 + 0.0.4 oss-spring-boot-starter diff --git a/share-common/share-starter/oss-spring-boot-starter/src/main/java/com/shine/share/oss/OssAutoConfiguration.java b/share-common/share-starter/oss-spring-boot-starter/src/main/java/com/shine/share/oss/OssAutoConfiguration.java index 4dd0d44c352344418c67e9a37d9018067f9233a5..62cdad4ae947585a5207b6b53a3d39b7012d529f 100644 --- a/share-common/share-starter/oss-spring-boot-starter/src/main/java/com/shine/share/oss/OssAutoConfiguration.java +++ b/share-common/share-starter/oss-spring-boot-starter/src/main/java/com/shine/share/oss/OssAutoConfiguration.java @@ -26,4 +26,5 @@ public class OssAutoConfiguration { public OssClient ossClient() { return new OssClient(ossSettings); } + } diff --git a/share-common/share-starter/pom.xml b/share-common/share-starter/pom.xml index a3a93f1bd915ad4b6d280d172d147ba49d15a7cc..2398ae82bbc3fb216b20d1f42a1109f1ff5f105d 100644 --- a/share-common/share-starter/pom.xml +++ b/share-common/share-starter/pom.xml @@ -6,7 +6,7 @@ com.shine share-common - 0.0.3 + 0.0.4 share-starter diff --git a/share-domain/pom.xml b/share-domain/pom.xml index eeca6a3a25df6da50391ad954293ca0d8008aac1..c7ea22191ff017e657695b5c6d864418ec3bf5f8 100644 --- a/share-domain/pom.xml +++ b/share-domain/pom.xml @@ -6,7 +6,7 @@ com.shine share - 0.0.3 + 0.0.4 share-domain @@ -14,4 +14,11 @@ 领域模型模块 pom + + + com.shine + share-protocol + + + diff --git a/share-gateway/pom.xml b/share-gateway/pom.xml index 81a58faa810e8ea4f1f8db1adce256e377418fdc..41aee37996ddb043f209883669bcc180fe80f5bd 100644 --- a/share-gateway/pom.xml +++ b/share-gateway/pom.xml @@ -6,7 +6,7 @@ com.shine share - 0.0.3 + 0.0.4 share-gateway @@ -15,8 +15,29 @@ - org.springframework.boot - spring-boot-starter + org.springframework.cloud + spring-cloud-starter-gateway + + + + pro.fessional + kaptcha + + + org.apache.commons + commons-lang3 + + + com.shine + share-protocol + + + com.shine + share-cloud + + + com.shine + redis-manager @@ -35,6 +56,12 @@ + + + src/main/resources + true + + diff --git a/share-gateway/src/main/java/com/shine/share/gateway/GatewayApplication.java b/share-gateway/src/main/java/com/shine/share/gateway/GatewayApplication.java index 9b07d2078f363c8c6b79403f4bb6769f7949cb95..228cfc24be6e5f4369532505a8ec1a4999979a9f 100644 --- a/share-gateway/src/main/java/com/shine/share/gateway/GatewayApplication.java +++ b/share-gateway/src/main/java/com/shine/share/gateway/GatewayApplication.java @@ -2,16 +2,19 @@ package com.shine.share.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** - * TODO to describe GatewayApplication + * 网关服务 * * @author 辛凤文 * @since 1.0 */ +@EnableDiscoveryClient @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class,args); + System.out.println("The Gateway service has started successfully. "); } } diff --git a/share-gateway/src/main/java/com/shine/share/gateway/config/CaptchaConfig.java b/share-gateway/src/main/java/com/shine/share/gateway/config/CaptchaConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..fdaa62ea9899a6c5f8b2446a063d2ef6adf4ee68 --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/config/CaptchaConfig.java @@ -0,0 +1,84 @@ +package com.shine.share.gateway.config; + +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Properties; + +import static com.google.code.kaptcha.Constants.*; + +/** + * 验证码配置 + * + * @author 辛凤文 + * @since 1.0 + */ +@Configuration +public class CaptchaConfig { + + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.shine.share.gateway.config.CaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/share-gateway/src/main/java/com/shine/share/gateway/config/CaptchaTextCreator.java b/share-gateway/src/main/java/com/shine/share/gateway/config/CaptchaTextCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..5a30b2dbd66022560db22e43455a908e3c1e87dc --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/config/CaptchaTextCreator.java @@ -0,0 +1,58 @@ +package com.shine.share.gateway.config; + +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +import java.util.Random; + +/** + * 验证码文本生成器 + * + * @author 辛凤文 + * @since 1.0 + */ +public class CaptchaTextCreator extends DefaultTextCreator { + + private static final String[] NUMBERS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}; + + @Override + public String getText() { + int result; + Random random = new Random(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomOperands = random.nextInt(3); + if (randomOperands == 0) { + result = x * y; + suChinese.append(NUMBERS[x]); + suChinese.append("*"); + suChinese.append(NUMBERS[y]); + } else if (randomOperands == 1) { + if ((x != 0) && y % x == 0) { + result = y / x; + suChinese.append(NUMBERS[y]); + suChinese.append("/"); + suChinese.append(NUMBERS[x]); + } else { + result = x + y; + suChinese.append(NUMBERS[x]); + suChinese.append("+"); + suChinese.append(NUMBERS[y]); + } + } else { + if (x >= y) { + result = x - y; + suChinese.append(NUMBERS[x]); + suChinese.append("-"); + suChinese.append(NUMBERS[y]); + } else { + result = y - x; + suChinese.append(NUMBERS[y]); + suChinese.append("-"); + suChinese.append(NUMBERS[x]); + } + } + suChinese.append("=?@").append(result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/share-gateway/src/main/java/com/shine/share/gateway/config/RouterFunctionConfiguration.java b/share-gateway/src/main/java/com/shine/share/gateway/config/RouterFunctionConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..9644f39244d6a1f457386b10aa078f7b96fffbc9 --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/config/RouterFunctionConfiguration.java @@ -0,0 +1,31 @@ +package com.shine.share.gateway.config; + +import com.shine.share.gateway.handler.ValidateCodeHandler; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.server.RequestPredicates; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; + +/** + * 路由配置信息 + * + * @author 辛凤文 + * @since 1.0 + */ +@Configuration +public class RouterFunctionConfiguration { + + @Resource + private ValidateCodeHandler validateCodeHandler; + + @SuppressWarnings("rawtypes") + @Bean + public RouterFunction routerFunction() { + return RouterFunctions.route( + RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), + validateCodeHandler); + } +} diff --git a/share-gateway/src/main/java/com/shine/share/gateway/config/properties/CaptchaProperties.java b/share-gateway/src/main/java/com/shine/share/gateway/config/properties/CaptchaProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..38269cc1a8a9fab94febd675de5f50a8f0a810a9 --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/config/properties/CaptchaProperties.java @@ -0,0 +1,33 @@ +package com.shine.share.gateway.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * 验证码配置 + * + * @author 辛凤文 + * @since 1.0 + */ +@Data +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "security.captcha") +public class CaptchaProperties { + /** + * 验证码开关 + */ + private Boolean enabled; + + /** + * 验证码类型(math 数组计算 char 字符) + */ + private String type = "math"; + + /** + * 验证码有效期(分钟) + */ + private Long expiration = 2L; +} diff --git a/share-gateway/src/main/java/com/shine/share/gateway/config/properties/IgnoreWhiteProperties.java b/share-gateway/src/main/java/com/shine/share/gateway/config/properties/IgnoreWhiteProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..dafb47261f6c8d920b3b46c72bae9c0b1f8bdac7 --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/config/properties/IgnoreWhiteProperties.java @@ -0,0 +1,27 @@ +package com.shine.share.gateway.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +/** + * 放行白名单配置 + * + * @author 辛凤文 + * @since 1.0 + */ +@Data +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "security.ignore") +public class IgnoreWhiteProperties { + /** + * 放行白名单配置,网关不校验此处的白名单 + */ + private List whites = new ArrayList<>(); + +} diff --git a/share-gateway/src/main/java/com/shine/share/gateway/constant/ErrorCode.java b/share-gateway/src/main/java/com/shine/share/gateway/constant/ErrorCode.java new file mode 100644 index 0000000000000000000000000000000000000000..a6e88671e946751a37922456b2c764f055771c68 --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/constant/ErrorCode.java @@ -0,0 +1,46 @@ +package com.shine.share.gateway.constant; + +import com.shine.share.protocol.constant.ErrorDefinition; +import lombok.Getter; + +/** + * 错误码: + * 1. 五位组成 + * 2. A代表用户端错误 + * 3. B代表当前系统异常 + * 4. C代表第三方服务异常 + * 5. 若无法确定具体错误,选择宏观错误 + * 6. 大的错误类间的步长间距预留100 + * + * @author 辛凤文 + * @since 1.0 + */ +@Getter +public enum ErrorCode implements ErrorDefinition { + + A0240("A0240", "验证码不能为空"), + A0241("A0241", "验证码已失效"), + A0242("A0242", "验证码错误"), + A0243("A0243", "验证码尝试次数超限"), + + B0200("B0200", "网关服务出错"), + B0204("B0204", "服务未找到"), + B0205("B0205", "响应状态异常"), + + B0240("B0240", "不支持的验证码类型"); + + private final String code; + + private final String desc; + + ErrorCode(String code, String desc) { + this.code = code; + this.desc = desc; + } + + @Override + public String toString() { + return "[" + this.code + "]-" + this.desc; + } + +} diff --git a/share-gateway/src/main/java/com/shine/share/gateway/filter/ValidateCodeFilter.java b/share-gateway/src/main/java/com/shine/share/gateway/filter/ValidateCodeFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..8f6945e7b34f895971e5ab49a6364b76471c1b96 --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/filter/ValidateCodeFilter.java @@ -0,0 +1,78 @@ +package com.shine.share.gateway.filter; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.shine.share.gateway.config.properties.CaptchaProperties; +import com.shine.share.gateway.service.ValidateCodeService; +import com.shine.share.gateway.util.WebServerUtils; +import com.shine.share.protocol.exception.ServiceException; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; + +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicReference; + +/** + * 验证码过滤器 + * + * @author 辛凤文 + * @since 1.0 + */ +@Slf4j +@Component +public class ValidateCodeFilter extends AbstractGatewayFilterFactory { + + private final static String[] VALIDATE_URL = new String[]{"/auth/login", "/auth/register"}; + + @Resource + private ValidateCodeService validateCodeService; + + @Resource + private CaptchaProperties captchaProperties; + + private static final String CODE = "code"; + + private static final String UUID = "uuid"; + + @Override + public GatewayFilter apply(Object config) { + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + + // 非登录/注册请求或验证码关闭,不处理 + if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) { + return chain.filter(exchange); + } + + try { + String rspStr = resolveBodyFromRequest(request); + JSONObject obj = JSON.parseObject(rspStr); + validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID)); + } catch (ServiceException e) { + return WebServerUtils.webFluxResponseWriter(exchange.getResponse(), e.getError()); + } + return chain.filter(exchange); + }; + } + + private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) { + // 获取请求体 + Flux body = serverHttpRequest.getBody(); + AtomicReference bodyRef = new AtomicReference<>(); + body.subscribe(buffer -> { + CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); + DataBufferUtils.release(buffer); + bodyRef.set(charBuffer.toString()); + }); + return bodyRef.get(); + } +} diff --git a/share-gateway/src/main/java/com/shine/share/gateway/handler/GatewayExceptionHandler.java b/share-gateway/src/main/java/com/shine/share/gateway/handler/GatewayExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..c88d1512def22abb15da6915dee7aee419bada21 --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/handler/GatewayExceptionHandler.java @@ -0,0 +1,48 @@ +package com.shine.share.gateway.handler; + +import com.shine.share.gateway.constant.ErrorCode; +import com.shine.share.gateway.util.WebServerUtils; +import com.shine.share.protocol.constant.ErrorDefinition; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +import org.springframework.cloud.gateway.support.NotFoundException; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * 网关统一异常处理 + * + * @author 辛凤文 + * @since 1.0 + */ +@Slf4j +@Order(-1) +@Configuration +public class GatewayExceptionHandler implements ErrorWebExceptionHandler { + + @Override + public Mono handle(ServerWebExchange exchange, Throwable ex) { + ServerHttpResponse response = exchange.getResponse(); + + if (exchange.getResponse().isCommitted()) { + return Mono.error(ex); + } + + ErrorDefinition error; + + if (ex instanceof NotFoundException) { + error = ErrorCode.B0204; + } else if (ex instanceof ResponseStatusException responseStatusException) { + error = ErrorCode.B0205; + } else { + error = ErrorCode.B0200; + } + + log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage()); + return WebServerUtils.webFluxResponseWriter(response, error); + } +} \ No newline at end of file diff --git a/share-gateway/src/main/java/com/shine/share/gateway/handler/ValidateCodeHandler.java b/share-gateway/src/main/java/com/shine/share/gateway/handler/ValidateCodeHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..6d0a3b703f5c572dc678f6aff64d944f4614f62f --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/handler/ValidateCodeHandler.java @@ -0,0 +1,32 @@ +package com.shine.share.gateway.handler; + +import com.shine.share.gateway.service.ValidateCodeService; +import com.shine.share.protocol.web.Result; +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.server.HandlerFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +/** + * 验证码获取 + * + * @author 辛凤文 + * @since 1.0 + */ +@Component +public class ValidateCodeHandler implements HandlerFunction { + + @Resource + private ValidateCodeService validateCodeService; + + @Override + public Mono handle(ServerRequest serverRequest) { + Result res = validateCodeService.createCaptcha(); + return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(res)); + } +} diff --git a/share-gateway/src/main/java/com/shine/share/gateway/service/ValidateCodeService.java b/share-gateway/src/main/java/com/shine/share/gateway/service/ValidateCodeService.java new file mode 100644 index 0000000000000000000000000000000000000000..cc572a8be74563c918cd603c368d3fc93040e62e --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/service/ValidateCodeService.java @@ -0,0 +1,31 @@ +package com.shine.share.gateway.service; + +import com.shine.share.protocol.web.Result; + +import java.io.IOException; + +/** + * 验证码处理 + * + * @author 辛凤文 + * @since 1.0 + */ +public interface ValidateCodeService { + + /** + * 生成验证码 + * + * @return + * @throws IOException + */ + Result createCaptcha(); + + /** + * 校验验证码 + * + * @param key + * @param value + */ + void checkCaptcha(String key, String value); + +} diff --git a/share-gateway/src/main/java/com/shine/share/gateway/service/impl/ValidateCodeServiceImpl.java b/share-gateway/src/main/java/com/shine/share/gateway/service/impl/ValidateCodeServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..73eef274bb4f2757f3eab487453f6b7f6cea7f2e --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/service/impl/ValidateCodeServiceImpl.java @@ -0,0 +1,111 @@ +package com.shine.share.gateway.service.impl; + +import com.google.code.kaptcha.Producer; +import com.shine.share.gateway.config.properties.CaptchaProperties; +import com.shine.share.gateway.constant.ErrorCode; +import com.shine.share.gateway.service.ValidateCodeService; +import com.shine.share.protocol.exception.ServiceException; +import com.shine.share.protocol.web.Result; +import com.shine.share.redis.constant.CacheConstants; +import com.shine.share.redis.manager.RedisManager; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.FastByteArrayOutputStream; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * 验证码实现处理 + * + * @author 辛凤文 + * @since 1.0 + */ +@Slf4j +@Service +public class ValidateCodeServiceImpl implements ValidateCodeService { + + @Resource + private Producer captchaProducer; + + @Resource + private Producer captchaProducerMath; + + @Resource + private RedisManager redisManager; + + @Resource + private CaptchaProperties captchaProperties; + + @SneakyThrows + @Override + public Result createCaptcha() { + Map data = new HashMap<>(); + Result> result = new Result<>(data); + boolean captchaEnabled = captchaProperties.getEnabled(); + data.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) { + return result; + } + + // 保存验证码信息 + String uuid = UUID.randomUUID().toString(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr, code; + BufferedImage image; + + String captchaType = captchaProperties.getType(); + // 生成验证码 + if ("math".equals(captchaType)) { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } else if ("char".equals(captchaType)) { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } else { + log.error("验证码配置异常,请检查配置security.captcha.type,支持math/char选项,不支持{}", captchaType); + throw new ServiceException(ErrorCode.B0240); + } + + redisManager.setCacheObject(verifyKey, code, captchaProperties.getExpiration(), TimeUnit.MINUTES); + + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + ImageIO.write(image, "jpg", os); + data.put("expiration", captchaProperties.getExpiration()); + data.put("uuid", uuid); + data.put("img", Base64.getEncoder().encodeToString(os.toByteArray())); + return result; + } + + @Override + public void checkCaptcha(String code, String uuid) { + if (code == null || code.isBlank()) { + throw new ServiceException(ErrorCode.A0240); + } + if (uuid == null || uuid.isBlank()) { + throw new ServiceException(ErrorCode.A0241); + } + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + String captcha = redisManager.getCacheObject(verifyKey); + if (captcha == null) { + throw new ServiceException(ErrorCode.A0241); + } + redisManager.deleteObject(verifyKey); + + if (!code.equalsIgnoreCase(captcha)) { + throw new ServiceException(ErrorCode.A0242); + } + } + +} diff --git a/share-gateway/src/main/java/com/shine/share/gateway/util/WebServerUtils.java b/share-gateway/src/main/java/com/shine/share/gateway/util/WebServerUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..6b9a63e239191e6dd0d2aba6dfb89c35cce7dc57 --- /dev/null +++ b/share-gateway/src/main/java/com/shine/share/gateway/util/WebServerUtils.java @@ -0,0 +1,35 @@ +package com.shine.share.gateway.util; + +import com.alibaba.fastjson2.JSON; +import com.shine.share.protocol.constant.ErrorDefinition; +import com.shine.share.protocol.web.Result; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpResponse; +import reactor.core.publisher.Mono; + +/** + * web服务器工具类 + * + * @author 辛凤文 + * @since 1.0 + */ +public class WebServerUtils { + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param error 响应内容 + * @return Mono + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, ErrorDefinition error) { + response.setStatusCode(HttpStatus.OK); + response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + Result result = new Result<>(error); + DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes()); + return response.writeWith(Mono.just(dataBuffer)); + } +} diff --git a/share-gateway/src/main/resources/banner.txt b/share-gateway/src/main/resources/banner.txt new file mode 100644 index 0000000000000000000000000000000000000000..f991e894b449df0324460ee2d7523e713feab9e3 --- /dev/null +++ b/share-gateway/src/main/resources/banner.txt @@ -0,0 +1,3 @@ +Application Name: ${spring.application.name} +Application Version: ${project.version} +Spring Boot Version: ${spring-boot.version} \ No newline at end of file diff --git a/share-gateway/src/main/resources/bootstrap.yml b/share-gateway/src/main/resources/bootstrap.yml new file mode 100644 index 0000000000000000000000000000000000000000..5b08892d960ff8c4b4e3764a802394680e5e20aa --- /dev/null +++ b/share-gateway/src/main/resources/bootstrap.yml @@ -0,0 +1,41 @@ +env: + # 环境类型,可选dev/test/prod + application: + name: ${APPLICATION_NAME:gateway} + profile: ${APPLICATION_PROFILE:dev} + nacos: + username: ${NACOS_USERNAME:} + password: ${NACOS_PASSWORD:} + server-addr: ${NACOS_SERVER_ADDR:8.140.253.90:18848} + namespace: ${NACOS_NAMESPACE:b4c0713f-e17f-4099-91d2-02310e9ac279} + group: ${NACOS_GROUP:SHARE_GROUP} + host: + ip: ${HOST_IP:192.168.88.113} + port: ${HOST_PORT:8080} + +server: + port: 8080 + +spring: + application: + name: ${env.application.name} + config: + import: + - optional:nacos:${spring.application.name}-${env.application.profile}.${spring.cloud.nacos.config.file-extension} + cloud: + nacos: + username: ${env.nacos.username} + password: ${env.nacos.password} + discovery: + server-addr: ${env.nacos.server-addr} + namespace: ${env.nacos.namespace} + group: ${env.nacos.group} + ip: ${env.host.ip} + port: ${env.host.port} + config: + server-addr: ${env.nacos.server-addr} + namespace: ${env.nacos.namespace} + group: ${env.nacos.group} + file-extension: yml + refresh-enabled: true + enable-remote-sync-config: true \ No newline at end of file diff --git a/share-gateway/src/main/resources/logback-spring.xml b/share-gateway/src/main/resources/logback-spring.xml new file mode 100644 index 0000000000000000000000000000000000000000..bf47b5cc879b720a00575a715fc1fd734162a2fc --- /dev/null +++ b/share-gateway/src/main/resources/logback-spring.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/last.log + + + + ${log.path}/%d{yyyy-MM-dd}.%i.log + + 30 + + 20MB + + 500MB + + + ${log.pattern} + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/share-system/pom.xml b/share-system/pom.xml index d5f7b29232f5af5cbfd434d20403f79b1f6d30d1..4223c086bfa0ca9a96eb51bf8fea865ee125dd6e 100644 --- a/share-system/pom.xml +++ b/share-system/pom.xml @@ -6,7 +6,7 @@ com.shine share - 0.0.3 + 0.0.4 share-system @@ -35,6 +35,12 @@ + + + src/main/resources + true + +