This commit is contained in:
kk
2026-07-08 17:57:11 +08:00
commit f1e6599892
743 changed files with 159169 additions and 0 deletions

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
# http://editorconfig.org
root = true
# 空格替代Tab缩进在各种编辑工具下效果一致
[*]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.java]
indent_style = tab
[*.{json,yml,yaml}]
indent_size = 2
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

34
.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# maven #
target
logs
# windows #
Thumbs.db
# Mac #
.DS_Store
# eclipse #
.settings
.project
.classpath
.log
*.class
# idea #
.idea
*.iml
# Package Files #
*.jar
*.war
*.ear
/target
# Flattened pom
.flattened-pom.xml
/**/.flattened-pom.xml
# ai
.claude

324
CLAUDE.md Normal file
View File

@@ -0,0 +1,324 @@
# CLAUDE.md
本文件用于指导 Claude Code (claude.ai/code) 在此代码仓库中工作时的行为规范。
> 本规范适用于 BladeX 微服务平台的所有开发任务,为强制性条款。除非用户显式豁免,任何条目都不得忽视或删减。
>
> 作为 AI 助手参与本项目开发时,你必须:
> - 每次输出前深度理解 BladeX 微服务架构、Spring Cloud 全栈技术特征和多租户业务模型
> - 当回答依赖外部知识时,先查询 Spring Boot 3.x、Spring Cloud 2025、MyBatis-Plus 等官方文档
> - 若需求含糊,先复述已知信息并列出关键澄清问题
> - 面对复杂需求,先拆分为可管理的子任务
>
> 所有开发内容必须建立在深度思考过的基础之上,禁止机械生成与错误填充。
> 如果你已了解所有规范,请在用户第一次对话时说明:"我已充分了解 BladeX 微服务平台开发规范。"
---
## 1. 项目概述
BladeX 是基于 Spring Cloud 2025 + Spring Boot 3.x 构建的**企业级微服务开发平台**,提供 SaaS 多租户、OAuth2 认证、数据/接口权限、工作流、分布式事务等核心能力。
---
## 2. 项目架构
```
BladeX/
├── blade-auth/ # OAuth2 认证授权服务
├── blade-common/ # 公共模块(常量、工具类、启动配置)
├── blade-gateway/ # Spring Cloud Gateway 网关服务
├── blade-ops/ # 运维服务模块
│ ├── blade-admin/ # Spring Boot Admin 监控
│ ├── blade-develop/ # 代码生成服务
│ ├── blade-flow/ # Flowable 工作流服务
│ ├── blade-job/ # 分布式任务调度
│ ├── blade-log/ # 日志服务
│ ├── blade-report/ # 报表服务
│ └── blade-resource/ # 资源管理服务OSS、SMS
├── blade-ops-api/ # 运维服务 API 契约
├── blade-plugin/ / blade-plugin-api/ # 插件扩展模块(预留)
├── blade-service/ # 业务服务模块
│ ├── blade-desk/ # 工作台服务(通知、流程)
│ └── blade-system/ # 系统管理服务(用户、角色、菜单、租户等)
├── blade-service-api/ # 业务服务 API 契约
├── doc/ # Nacos 配置 & 多数据库建表脚本
├── script/ # Docker / FatJar 部署脚本
└── pom.xml # Maven 父工程配置
```
### 2.1 分层架构
每个业务模块遵循标准分层,**API 与 Service 分离**
| 层次 | 包路径 | 所在模块 |
| --- | --- | --- |
| Controller | `controller/` | Service 模块 |
| Service / ServiceImpl | `service/` + `service/impl/` | Service 模块 |
| Mapper接口 + XML 同包) | `mapper/` | Service 模块 |
| Wrapper | `wrapper/` | Service 模块Entity → VO 转换) |
| Entity / VO / DTO | `pojo/entity/` `pojo/vo/` `pojo/dto/` | **API 模块** |
| Feign Client | `feign/` | **API 模块**(含 Fallback 降级) |
| Cache | `cache/` | **API 模块** |
### 2.2 API 与 Service 分离原则
- **API 模块**`blade-*-api`):定义 Entity、VO、DTO、Feign 接口、Cache 工具,供其他服务依赖
- **Service 模块**`blade-service/*``blade-ops/*`):实现 Controller、ServiceImpl、Mapper、Wrapper
- 跨服务调用通过 Feign Client 完成,禁止直接依赖其他 Service 模块
> **与 Boot 版的关键区别**Cloud 采用 API/Service 模块分离,跨服务通过 Feign 调用Controller 路由无需 `AppConstant` 前缀(网关按服务名路由)。
---
## 3. 技术栈
| 类别 | 技术 |
| --- | --- |
| 基础框架 | Spring Boot 3.2.x / Spring Cloud 2025 / Maven |
| ORM | MyBatis-Plus禁止 JDBC 直连) |
| 微服务 | Nacos注册/配置、Sentinel熔断、Seata分布式事务、OpenFeign |
| 网关 | Spring Cloud GatewayWebFlux |
| 安全 | OAuth2 / JWT自研 Secure 框架)、数据权限、接口权限 |
| 缓存 | RedisProtostuff 序列化) |
| 工作流 / 任务调度 | Flowable / PowerJob |
| 数据库连接池 | Druid |
| 文档 | Knife4jOpenAPI 3.0 |
| 监控 | Spring Boot Admin / Prometheus / ELK / Zipkin |
| 数据库 | MySQL / PostgreSQL / Oracle / SQL Server / 达梦 / 人大金仓 / 崖山 |
---
## 4. 开发规范
### 4.1 编写新功能前
1. 先阅读目标模块中已有的类,理解其结构、命名和风格
2. 标准参考模块:`blade-service/blade-system`Service`blade-service-api/blade-system-api`API
3. 主动模仿现有代码风格包括缩进Java 用 TabYAML/JSON 用 2 空格、注解顺序、Javadoc 格式
4. 新建类必须包含 BladeX 商业许可证头部和 Javadoc 类注释(`@author Chill`
### 4.2 编写完成后
1. 使用 `mvn clean compile -DskipTests` 编译验证,编译不通过必须修复
2. 引入模块依赖前先检查循环依赖,若存在则采用更优方案
3. 编译通过后将测试交由用户执行,**不得自行执行任何测试**
4. 除非用户明确要求,不应撰写示例或额外文档
### 4.3 代码生成
当需要生成 CRUD 全套代码Entity、VO、Service、Controller、Wrapper、Mapper、建表语句等优先使用 **`/blade-design`** skill。该 skill 可根据模块名、实体名和字段列表,自动生成符合 BladeX 框架规范的后端代码和多数据库建表语句,确保生成结果与项目风格完全一致。
---
## 5. 命名规范
### 5.1 包名
统一前缀 `org.springblade`,按模块划分:`system``desk``auth``gateway``develop``flow``common`
### 5.2 类名
| 类型 | 规则 | 示例 |
| --- | --- | --- |
| Entity | 直接类名 | `Notice``Tenant` |
| VO / DTO | `XxxVO` / `XxxDTO` | `NoticeVO``DeptDTO` |
| Service 接口 | `IXxxService` | `INoticeService` |
| Service 实现 | `XxxServiceImpl` | `NoticeServiceImpl` |
| Controller | `XxxController` | `NoticeController` |
| Mapper | `XxxMapper` | `NoticeMapper` |
| Wrapper | `XxxWrapper` | `NoticeWrapper` |
| Feign 接口 / 降级 | `IXxxClient` / `IXxxClientFallback` | `INoticeClient` |
| 表名 | `blade_` + 下划线 | `blade_notice` |
### 5.3 变量命名
- 必须具有明确语义:`Exception exception`(✅)`Exception e`(❌);`List<Notice> noticeList`(✅)`List<Notice> list`(❌)
- 冲突时提升语义:`Cache cache; Cache noticeCache`(✅)`Cache cache1; Cache cache2`(❌)
---
## 6. 编码规范
### 6.1 注解顺序
- **Controller 类**:租户注解(`@TenantDS`/`@NonDS`) → `@RestController` → Lombok(`@AllArgsConstructor`) → 安全注解(`@PreAuth`) → `@RequestMapping``@Tag`
- **Controller 方法**HTTP 方法注解 → `@ApiOperationSupport(order=N)``@Operation`
- **Entity 类**`@Data``@EqualsAndHashCode``@TableName``@Schema`
- **VO 类**`@Data``@EqualsAndHashCode(callSuper = true)``@Schema`
### 6.2 Entity-Service 继承体系(核心)
Entity 基类的选择**直接决定** Service 和 ServiceImpl 的继承方式,三者必须配套:
| 场景 | Entity | Service 接口 | ServiceImpl | 示例 |
| --- | --- | --- | --- | --- |
| **多租户业务表** | `extends TenantEntity` | `extends BaseService<T>` | `extends BaseServiceImpl<M, T>` | Notice, Post, Dept |
| **非租户业务表** | `extends BaseEntity` | `extends BaseService<T>` | `extends BaseServiceImpl<M, T>` | Tenant |
| **轻量级/关系表** | `implements Serializable` | `extends IService<T>` | `extends ServiceImpl<M, T>` | RoleMenu, Dict, Role |
- `TenantEntity` 继承自 `BaseEntity` 并额外包含 `tenantId`
- `BaseEntity` 包含id, createUser, createDept, createTime, updateUser, updateTime, status, isDeleted
- `BaseService`/`BaseServiceImpl` 是 BladeX 增强版,提供 `deleteLogic()` 等方法,**要求 Entity 继承 BaseEntity 或 TenantEntity**
- `IService`/`ServiceImpl` 是 MyBatis-Plus 原生版,用于 `implements Serializable` 的轻量实体,删除用 `removeByIds()`
### 6.3 Controller 约定
- 继承 `BladeController`,使用 `@AllArgsConstructor` 注入
- **路由直接使用资源路径**(如 `@RequestMapping("notice")`),网关按服务名路由,无需 `AppConstant` 前缀Boot 版需要前缀)
- 统一返回 `R<T>` 响应体
- Entity → VO 转换通过 `XxxWrapper.build().entityVO()` / `.pageVO()`
### 6.4 Feign ClientCloud 特有)
- 定义在 API 模块的 `feign/` 包中
- `@FeignClient(value = AppConstant.APPLICATION_XXX_NAME)`
- 路径常量定义在接口内:`String API_PREFIX = "/feign/client/xxx"`
- 降级类命名:`IXxxClientFallback`
### 6.5 依赖注入
- Controller 使用 `@AllArgsConstructor` + `private final` 字段
- Service 需要额外依赖时使用 `@RequiredArgsConstructor` + `private final` 字段
### 6.6 Lombok
禁止手写 getter/setter统一使用 `@Data``@EqualsAndHashCode(callSuper = true)``@AllArgsConstructor``@RequiredArgsConstructor``@Slf4j`
### 6.7 Java 17 特性
- 可使用增强 switch、Text Blocks、Pattern Matching for instanceof、`@Serial`
- **禁止** `var` / `val` 类型推断,所有变量显式声明类型
- 优先使用 Stream APILambda 保持简洁
### 6.8 MyBatis-Plus
- 简单查询使用 `LambdaQueryWrapper`,复杂查询写在 Mapper XML 中
- Mapper XML 与接口**同包**放置(`src/main/java` 下)
- 分页统一使用 `Condition.getPage(query)` + `Condition.getQueryWrapper()`
- 禁止 JDBC 直连查询
### 6.9 日志
- 使用 `@Slf4j`,占位符传参(禁止字符串拼接)
- 包含关键业务标识,异常必须携带堆栈,禁止打印敏感信息
---
## 7. 数据库规范
- **表名前缀**`blade_`(如 `blade_notice``blade_user`
- **业务表必含字段**`id`(BIGINT)、`tenant_id`(VARCHAR)、`create_user``create_dept``create_time``update_user``update_time``status``is_deleted`
- **主键策略**:雪花算法(`IdType.ASSIGN_ID`Long 字段用 `@JsonSerialize(using = ToStringSerializer.class)` 防精度丢失
- **逻辑删除**`is_deleted` (INT)0 = 未删除1 = 已删除
- **索引命名**:唯一索引 `uk_` 前缀,普通索引 `idx_` 前缀
- **多数据库**:支持 7 种数据库,建表脚本位于 `doc/sql/`,修改表结构时需同步所有脚本
---
## 8. 多租户与安全
- **多租户**:默认字段隔离模式(`tenant_id`MP 内置方法自动注入;自定义 SQL 需手动 `AuthUtil.getTenantId()`
- **租户注解**`@TenantDS`(启用数据源切换)/ `@NonDS`(禁用切换)
- **权限注解**`@PreAuth(menu = "xxx")`(菜单权限)、`@PreAuth(AuthConstant.PERMIT_ALL)`(公开)
- **数据权限**DataScopeHandler 行级过滤
- **接口权限**ApiScopeHandler 端点级控制
---
## 9. 缓存规范
- `CacheUtil` 统一操作,常量在 `CacheConstant`
- 数据变更后清除缓存:`CacheUtil.clear(CACHE_NAME, Boolean.FALSE)`
- 字典翻译:`DictCache.getValue(DictEnum.XXX, value)`
---
## 10. 商业许可头部
所有新建的 Java 源文件必须在文件顶部包含以下许可头部:
```java
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
```
---
## 11. 构建与部署
```bash
mvn clean compile -DskipTests # 编译验证
mvn clean package -DskipTests # 打包
mvn clean package docker:build -DskipTests # Docker 镜像
```
- Nacos 配置:`doc/nacos/blade-{dev|test|prod}.yaml`
- Docker 部署:`script/docker/app/docker-compose.yml`
- 开发环境默认Nacos `localhost:8848`、Sentinel `localhost:8858`、Redis `127.0.0.1:6379`、MySQL `localhost:3306/bladex`
---
## 12. Git 提交规范
当需要提交代码时,优先使用 **`/blade-commit`** skill它会自动分析变更内容并生成符合项目规范的 Gitmoji 提交信息。
采用 **Gitmoji** 风格,中文描述。格式:`:<gitmoji>: 简要描述`
| Gitmoji | 场景 | 示例 |
| --- | --- |------------------------|
| `:sparkles:` | 新增功能 | `:sparkles: 新增角色授权功能` |
| `:zap:` | 优化重构 | `:zap: 优化字典查询排序逻辑` |
| `:bug:` | 修复缺陷 | `:bug: 修复用户查询未过滤已删除数据` |
| `:tada:` | 版本发布 | `:tada: x.x.x.RELEASE` |
| `:recycle:` | 代码重构 | `:recycle: 重构租户删除逻辑` |
---
## 13. 框架组件速查
| 组件 | 用途 |
| --- | --- |
| `R<T>` | 统一 API 响应 |
| `BladeController` | Controller 基类 |
| `TenantEntity` / `BaseEntity` | 实体基类(多租户/非租户) |
| `BaseService` / `BaseServiceImpl` | BladeX 增强 Service含 deleteLogic |
| `BaseEntityWrapper` | Entity→VO 转换基类 |
| `BladeUser` | 当前登录用户(含租户信息) |
| `Condition` / `Query` | 查询条件构建 / 分页参数 |
| `CacheUtil` / `DictCache` | 缓存工具 / 字典缓存 |
| `AuthUtil` | 获取当前用户/租户信息 |
| `Func` / `BeanUtil` / `StringUtil` | 通用工具类 |
| `ServiceException` | 业务异常 |
| `@PreAuth` / `@TenantDS` / `@NonDS` | 权限 / 租户数据源切换 |
| `@BladeView` / `@DataRecord` / `@XssIgnore` | 视图控制 / 数据审计 / XSS 跳过 |
---
## 14. 风格一致性
1. 风格不确定时,优先查阅现有代码并模仿当前模块的实现方式
2. 新模块参考 `blade-service/blade-system` 作为标准模板
3. 现有模块已满足需求时,禁止自写替代实现
4. 与用户交互全程使用**中文**,代码注释和 Javadoc 亦使用中文

35
LICENSE Normal file
View File

@@ -0,0 +1,35 @@
BladeX商业授权许可协议
一、 知识产权:
BladeX系列产品知识产权归上海布雷德科技有限公司独立所有
二、 许可:
1. 在您完全接受并遵守本协议的基础上本协议授予您使用BladeX的某些权利和非独占性许可。
2. 本协议中,将本产品使用用途分为"专业版用途"和"企业版用途"。
3. "专业版用途"定义:指个人在非团体机构中出于任何合法目的使用本产品(任何目的包括商业目的或非盈利目的)。
4. "企业版用途"定义:指拥有合法执照的团体机构(例如公司企业、政府、学校、军队、医院、社会团体等各类组织)(不包含集团,若集团使用则需为各个子公司分别购买企业授权)出于任何合法目的使用本产品(任何目的包括商业目的或非盈利目的)。
5. 若您不能以拥有合法执照的团体机构名义购买企业版,则视为个人名义购买,仅可行使专业版用途。在遵守此协议的前提下,后续有一次机会将企业版授权免费绑定至法人为购买人的新公司,并从专业版用途转为企业版用途。
三、 约束和限制:
1. 本产品只能由您为本协议许可的目的而使用,您不得透露给任何第三方;
2. 从本产品取得的任何信息、软件、产品或服务,您不得对其进行修改、改编或基于以上内容创建同种类别的衍生产品并售卖。
3. 您不得对本产品以及与之关联的商业授权进行发布、出租、销售、分销、抵押、转让、许可或发放子许可证。
4. 本产品商业授权版可能包含一些独立功能或特性,这些功能只有在您购买商业授权后才可以使用。在未取得商业授权的情况下,您不得使用、尝试使用或复制这些授权版独立功能。
5. 若您的客户要求以源码方式交付软件,需缴纳企业版授权费用,否则本产品部分不得提供源码。
四、 不得用于非法或禁止的用途:
您在使用本产品或服务时,不得将本产品产品或服务用于任何非法用途或本协议条款、条件和声明禁止的用途。
五、 免责说明:
1. 本产品按"现状"授予许可您须自行承担使用本产品的风险。BladeX团队不对此提供任何明示、暗示或任何其它形式的担保和表示。在任何情况下对于因使用或无法使用本软件而导致的任何损失包括但不仅限于商业利润损失、业务中断或业务信息丢失BladeX团队无需向您或任何第三方负责即使BladeX团队已被告知可能会造成此类损失。在任何情况下 BladeX团队均不就任何直接的、间接的、附带的、后果性的、特别的、惩戒性的和处罚性的损害赔偿承担任何责任无论该主张是基于保证、合同、侵权包括疏忽或是基于其他原因作出。
2. 本产品可能内置有第三方服务,您应自行评估使用这些第三方服务的风险,由使用此类第三方服务而产生的纠纷,全部责任由您自行承担。
3. BladeX团队不对使用本产品构建的网站中任何信息内容以及导致的任何版权纠纷、法律争议和后果承担任何责任全部责任由您自行承担。
4. BladeX团队可能会经常提供产品更新或升级但BladeX团队没有为根据本协议许可的产品提供维护或更新的责任。
5. BladeX团队可能会按照官方制定的答疑规则为您进行答疑但BladeX团队没有为根据本协议许可的产品提供技术支持的义务或责任。
六、 权利和所有权的保留:
BladeX团队保留所有未在本协议中明确授予您的所有权利。BladeX团队保留随时更新本协议的权利并只需公示于对应产品项目的LICENSE文件无需征得您的事先同意且无需另行通知更新后的内容应于公示即时生效。您可以随时访问产品地址并查阅最新版许可条款在更新生效后您继续使用本产品则被视作您已接受了新的条款。
七、 协议终止
1. 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款,在享有上述条款授予的许可权力同时,也受到相关的约束和限制,本协议许可范围以外的行为,将直接违反本协议并构成侵权。
2. 一旦您违反本协议的条款BladeX团队随时可能终止本协议、收回许可和授权并要求您承担相应法律和经济责任。

43
README.md Normal file
View File

@@ -0,0 +1,43 @@
## 版权声明
* BladeX是一个商业化软件系列产品知识产权归**上海布雷德科技有限公司**独立所有
* 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款
* 更多详情请看:[BladeX商业授权许可协议](https://license.bladex.cn)
## 答疑流程
>1. 遇到问题或Bug
>2. 业务型问题打断点调试尝试找出问题所在
>3. 系统型问题通过百度、谷歌、社区查找解决方案
>4. 未解决问题则进入技术社区进行发帖提问:[https://sns.bladex.cn](https://sns.bladex.cn)
>5. 将帖子地址发至商业群,特别简单三言两语就能描述清楚的也可在答疑时间内发至商业群提问
>6. 发帖的时候一定要描述清楚,详细描述遇到问题的**重现步骤**、**报错详细信息**、**相关代码与逻辑**、**使用软件版本**以及**操作系统版本**,否则随意发帖提问将会提高我们的答疑难度。
## 答疑时间
* 工作日9:00 ~ 17:00 提供答疑,周末、节假日休息,暂停答疑
* 请勿**私聊提问**,以免被其他用户的消息覆盖从而无法获得答疑
* 答疑时间外遇到问题可以将问题发帖至[技术社区](https://sns.bladex.cn),我们后续会逐个回复
## 授权范围
* 专业版:只可用于**个人学习**及**个人私活**项目,不可用于公司或团队,不可泄露给任何第三方
* 企业版:可用于**企业名下**的任何项目,企业版员工在**未购买**专业版授权前,只授权开发**所在授权企业名下**的项目,**不得将BladeX用于个人私活**
* 共同遵守若甲方需要您提供项目源码则需代为甲方购买BladeX企业授权甲方购买后续的所有项目都无需再次购买授权
## 商用权益
* ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下将BladeX系列产品用于授权范围内的商用项目并上线运营
* ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,不限制项目数,不限制服务器数
* ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,将自行编写的业务代码申请软件著作权
## 何为侵权
* ❌ 不遵守商业协议,私自销售商业源码
* ❌ 以任何理由将BladeX源码用于申请软件著作权
* ❌ 将商业源码以任何途径任何理由泄露给未授权的单位或个人
* ❌ 开发完毕项目没有为甲方购买企业授权向甲方提供了BladeX代码
* ❌ 基于BladeX拓展研发与BladeX有竞争关系的衍生框架并将其开源或销售
## 侵权后果
* 情节较轻:第一次发现警告处理
* 情节较重:封禁账号,踢出商业群,并保留追究法律责任的权利
* 情节严重:与本地律师事务所合作,以公司名义起诉侵犯计算机软件著作权
## 举报有奖
* 向官方提供有用线索并成功捣毁盗版个人或窝点,将会看成果给予 50010000 不等的现金奖励
* 官方唯一指定QQ1272154962

17
blade-auth/Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
# 网络问题无法下载可以改为阿里云镜像
# FROM registry.cn-hangzhou.aliyuncs.com/bladex-repo/alpine-java:openjdk17_cn_slim
FROM bladex/alpine-java:openjdk17_cn_slim
LABEL maintainer="bladejava@qq.com"
RUN mkdir -p /blade/auth
WORKDIR /blade/auth
EXPOSE 8100
COPY ./target/blade-auth.jar ./app.jar
ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
CMD ["--spring.profiles.active=test"]

32
blade-auth/README.md Normal file
View File

@@ -0,0 +1,32 @@
## 目前主要支持的oauth协议
一、 授权码模式
授权码模式(authorization_code)主要针对第三方应用,是最为复杂也最为安全的一种模式,操作步骤如下
1. 访问地址http://localhost:8100/oauth/authorize?client_id=blade&redirect_uri=http://example.com&code=233333&response_type=code
2. 获取跳转后的code值(http://example.com/?code=VhYNLR)之后,调用 http://localhost/blade-auth/oauth/token 传入对应的参数
请求头:
Authorization Basic YmxhZGU6YmxhZGU= "YmxhZGU6YmxhZGU="为clientId:clientSecret串转换为的base64编码
表单:
grant_typeauthorization_code
scopeall
codeVhYNLR
redirect_uri http://example.com
二、 密码模式
密码模式(password)主要针对自家应用,可信度较高,所以可以使用简便安全共存的模式,操作步骤如下
1. 直接调用 http://localhost/blade-auth/oauth/token 传入对应的参数
请求头:
Authorization Basic YmxhZGU6YmxhZGU= "YmxhZGU6YmxhZGU="为clientId:clientSecret串转换为的base64编码
表单:
grant_typepassword
scopeall
usernameadmin
password123456
## 获取到token后如何获取用户信息
1. 拼接请求头
Authorization bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU1MzE2MTA5NSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjE0YmMyYjAyLTgxY2UtNDFiNC04ZTI3LTA5YWE0ZmU4ZWMwYyIsImNsaWVudF9pZCI6ImJsYWRlIn0.jTmioQDq-fSNNn7YCwl3wP0JE-etSWtzLDe545mDbP4
2. 调用 http://localhost/blade-auth/oauth/user-info 既可获得对应用户信息

103
blade-auth/pom.xml Normal file
View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-auth</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-common</artifactId>
<exclusions>
<exclusion>
<groupId>org.springblade</groupId>
<artifactId>blade-scope-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-db</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-cloud</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-metrics</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-swagger</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-log</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-social</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-user-api</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-system-api</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-resource-api</artifactId>
</dependency>
<!--安全模块-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-oauth2</artifactId>
</dependency>
<!-- 验证码 -->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
</dependency>
<!-- 链路追踪、服务监控 -->
<!--<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-trace</artifactId>
</dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<skip>${docker.fabric.skip}</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,47 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
/**
* 用户认证服务器
*
* @author Chill
*/
@EnableRedisHttpSession
@BladeCloudApplication
public class AuthApplication {
public static void main(String[] args) {
BladeApplication.run(AppConstant.APPLICATION_AUTH_NAME, AuthApplication.class, args);
}
}

View File

@@ -0,0 +1,101 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.config;
import org.springblade.auth.handler.BladeAuthorizationHandler;
import org.springblade.auth.handler.BladeLockHandler;
import org.springblade.auth.handler.BladeLogHandler;
import org.springblade.auth.handler.BladePasswordHandler;
import org.springblade.auth.handler.BladeTokenHandler;
import org.springblade.auth.service.BladeClientDetailService;
import org.springblade.auth.service.BladeUserDetailService;
import org.springblade.core.jwt.props.JwtProperties;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.launch.server.ServerInfo;
import org.springblade.core.oauth2.config.OAuth2AutoConfiguration;
import org.springblade.core.oauth2.handler.AuthorizationHandler;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.handler.TokenHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
import org.springblade.core.oauth2.service.OAuth2ClientService;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.system.feign.IAuthLockClient;
import org.springblade.system.feign.IAuthLogClient;
import org.springblade.core.tenant.BladeTenantProperties;
import org.springblade.system.feign.IUserClient;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* BladeAuthConfiguration
*
* @author Chill
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(OAuth2AutoConfiguration.class)
public class BladeAuthConfiguration {
@Bean
public AuthorizationHandler authorizationHandler(BladeProperties bladeProperties,
BladeTenantProperties tenantProperties,
OAuth2Properties oAuth2Properties,
BladeLockHandler lockHandler,
BladeLogHandler logHandler) {
return new BladeAuthorizationHandler(bladeProperties, tenantProperties, oAuth2Properties, lockHandler, logHandler);
}
@Bean
public BladeLockHandler lockHandler(IAuthLockClient authLockClient) {
return new BladeLockHandler(authLockClient);
}
@Bean
public BladeLogHandler logHandler(IAuthLogClient authLogClient, BladeProperties bladeProperties, ServerInfo serverInfo) {
return new BladeLogHandler(authLogClient, bladeProperties, serverInfo);
}
@Bean
public PasswordHandler passwordHandler(OAuth2Properties properties) {
return new BladePasswordHandler(properties);
}
@Bean
public TokenHandler tokenHandler(JwtProperties jwtProperties) {
return new BladeTokenHandler(jwtProperties);
}
@Bean
public OAuth2ClientService oAuth2ClientService(JdbcTemplate jdbcTemplate) {
return new BladeClientDetailService(jdbcTemplate);
}
@Bean
public OAuth2UserService oAuth2UserService(IUserClient userClient) {
return new BladeUserDetailService(userClient);
}
}

View File

@@ -0,0 +1,35 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.constant;
/**
* AuthorizationConstant
*
* @author Chill
*/
public interface BladeAuthConstant {
}

View File

@@ -0,0 +1,98 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.endpoint;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springblade.core.oauth2.props.OAuth2Properties;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.resource.feign.ISmsClient;
import org.springblade.resource.utils.SmsUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static org.springblade.core.oauth2.constant.OAuth2TokenConstant.USER_PHONE_NOT_FOUND;
/**
* Oauth2SmsEndpoint
*
* @author Chill
*/
@RestController
@AllArgsConstructor
@Tag(name = "用户短信认证", description = "4 - OAuth2短信认证端点")
public class Oauth2SmsEndpoint {
/**
* 短信服务构建类
*/
private final ISmsClient smsClient;
/**
* 用户服务类
*/
private final OAuth2UserService userService;
/**
* OAuth2配置类
*/
private final OAuth2Properties properties;
/**
* 短信验证码发送
*
* @param tenantId 租户ID
* @param phone 手机号
*/
@SneakyThrows
@PostMapping("/oauth/sms/send-validate")
public R sendValidate(@RequestParam String tenantId, @RequestParam String phone) {
// 校验手机加密认证,防止恶意发送验证码
String decryptedPhone = SM2Util.decrypt(phone, properties.getPublicKey(), properties.getPrivateKey());
if (StringUtil.isBlank(decryptedPhone)) {
return R.fail(USER_PHONE_NOT_FOUND);
}
// 校验手机是否已注册,防止恶意发送验证码
OAuth2Request request = OAuth2Request.create();
request.setTenantId(tenantId);
OAuth2User oAuth2User = userService.loadByPhone(decryptedPhone, request);
if (oAuth2User == null) {
return R.fail(USER_PHONE_NOT_FOUND);
}
// 用户存在则发送验证码
R result = smsClient.sendValidate(tenantId, StringPool.EMPTY, decryptedPhone);
return result.isSuccess() ? R.data(result.getData(), SmsUtil.SEND_SUCCESS) : R.fail(SmsUtil.SEND_FAIL);
}
}

View File

@@ -0,0 +1,76 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.granter;
import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.granter.PasswordTokenGranter;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2ClientService;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
/**
* BehaviorTokenGranter
*
* <p>行为验证码授权模式。行为验证(点选/滑块/旋转)在专用端点完成并换取一次性通行票据,
* 登录请求经由验证码请求头携带票据,本授权器核销票据后进入密码认证链路。</p>
*
* @author Chill
*/
@Component
public class BehaviorTokenGranter extends PasswordTokenGranter {
private final BladeRedis bladeRedis;
public BehaviorTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, BladeRedis bladeRedis) {
super(clientService, userService, passwordHandler);
this.bladeRedis = bladeRedis;
}
@Override
public String type() {
return BEHAVIOR;
}
@Override
public OAuth2User user(OAuth2Request request) {
// 获取行为验证票据信息
String key = request.getCaptchaKey();
String ticket = request.getCaptchaCode();
// 票据一次性核销,防止同一票据重复登录
String cacheTicket = bladeRedis.getAndDel(OAuth2TokenConstant.BEHAVIOR_TICKET_KEY + key);
// 判断票据有效性
if (StringUtil.isBlank(ticket) || !StringUtil.equals(cacheTicket, ticket)) {
throw new UserInvalidException(OAuth2TokenConstant.BEHAVIOR_NOT_CORRECT);
}
return super.user(request);
}
}

View File

@@ -0,0 +1,73 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.granter;
import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.granter.PasswordTokenGranter;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2ClientService;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
/**
* CaptchaTokenGranter
*
* @author BladeX
*/
@Component
public class CaptchaTokenGranter extends PasswordTokenGranter {
private final BladeRedis bladeRedis;
public CaptchaTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, BladeRedis bladeRedis) {
super(clientService, userService, passwordHandler);
this.bladeRedis = bladeRedis;
}
@Override
public String type() {
return CAPTCHA;
}
@Override
public OAuth2User user(OAuth2Request request) {
// 获取验证码信息
String key = request.getCaptchaKey();
String code = request.getCaptchaCode();
// 获取验证码
String redisCode = bladeRedis.getAndDel(OAuth2TokenConstant.CAPTCHA_CACHE_KEY + key);
// 判断验证码
if (code == null || !StringUtil.equalsIgnoreCase(redisCode, code)) {
throw new UserInvalidException(OAuth2TokenConstant.CAPTCHA_NOT_CORRECT);
}
return super.user(request);
}
}

View File

@@ -0,0 +1,161 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.granter;
import org.jetbrains.annotations.NotNull;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.provider.OAuth2Token;
import org.springblade.core.oauth2.service.OAuth2Client;
import org.springblade.core.oauth2.service.OAuth2ClientService;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.NumberUtil;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.system.cache.ParamCache;
import org.springblade.system.feign.IUserClient;
import org.springblade.system.pojo.entity.User;
import org.springblade.system.pojo.enums.UserType;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.function.Predicate;
import static org.springblade.common.constant.ParamConstant.REGISTER_USER_VALUE;
/**
* RegisterTokenGranter
*
* @author BladeX
*/
@Component
public class RegisterTokenGranter extends AbstractTokenGranter {
private final IUserClient userClient;
private final OAuth2Properties properties;
public RegisterTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, IUserClient userClient, OAuth2Properties properties) {
super(clientService, userService, passwordHandler);
this.userClient = userClient;
this.properties = properties;
}
@Override
public String type() {
return REGISTER;
}
@Override
public OAuth2User user(OAuth2Request request) {
// 校验注册功能是否开启
Boolean registerOpen = Func.toBoolean(ParamCache.getValue(REGISTER_USER_VALUE), false);
if (!registerOpen) {
throw new UserInvalidException("注册功能暂未开启,请联系管理员");
}
// 用户注册信息
User user = new User();
user.setUserType(UserType.of(request.getUserType()).getCategory());
user.setTenantId(request.getTenantId());
user.setAccount(request.getUsername());
user.setPassword(SM2Util.decrypt(request.getPassword(), properties.getPublicKey(), properties.getPrivateKey()));
user.setName(request.getName());
user.setRealName(request.getName());
user.setPhone(request.getPhone());
user.setEmail(request.getEmail());
// 校验用户格式
validateUser(user);
R<String> result = userClient.registerUser(user);
// 执行用户注册
if (result.isSuccess()) {
// 构建oauth2所需用户信息
user.setId(NumberUtil.toLong(result.getData()));
return convertOAuth2UserDetail(user, client(request));
}
throw new UserInvalidException(result.getMsg());
}
@Override
public OAuth2Token token(OAuth2User user, OAuth2Request request) {
// 移除注册后返回的令牌与刷新令牌,防止外部攻击采用注册接口获取令牌并调用低权接口
// 注意:
// 1. 框架已默认开启严格模式blade.secure.strict-token=true不移除令牌则不受影响注册令牌会被框架校验并拒绝
// 2. 若自行关闭严格模式blade.secure.strict-token=false必须将令牌移除否则注册获取令牌后可调用低权接口
OAuth2Token token = super.token(user, request);
token.getArgs().remove(TokenConstant.ACCESS_TOKEN);
token.getArgs().remove(TokenConstant.REFRESH_TOKEN);
return token;
}
private void validateUser(User user) {
Predicate<String> isNameValid = name -> name.matches("^([\\u4e00-\\u9fa5]{2,20}|[a-zA-Z]{2,10})$");
Predicate<String> isUsernameValid = username -> username.matches("^(?=.*[a-zA-Z])[a-zA-Z0-9_\\-@]{3,20}$");
Predicate<String> isPasswordValid = password -> password.matches("^(?=.*[0-9])(?=.*[a-zA-Z])[\\w@-]{6,20}$");
Predicate<String> isPhoneValid = phone -> phone.matches("^1[3-9]\\d{9}$");
Predicate<String> isEmailValid = email -> email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
if (!isNameValid.test(user.getName())) {
throw new UserInvalidException("用户姓名长度必须在2-10之间且仅能设置纯中文或纯英文");
}
if (!isUsernameValid.test(user.getAccount())) {
throw new UserInvalidException("用户账号长度必须在3-20之间且需要包含英文可额外携带数字、下划线、横杠、@");
}
if (!isPasswordValid.test(user.getPassword())) {
throw new UserInvalidException("用户密码长度必须在6-20之间且需要包含英文与数字可额外携带下划线、横杠、@");
}
if (!isPhoneValid.test(user.getPhone())) {
throw new UserInvalidException("手机号格式不正确");
}
if (!isEmailValid.test(user.getEmail())) {
throw new UserInvalidException("邮箱格式不正确");
}
}
@NotNull
private OAuth2UserDetail convertOAuth2UserDetail(User user, OAuth2Client client) {
OAuth2UserDetail userDetail = new OAuth2UserDetail();
userDetail.setUserId(String.valueOf(user.getId()));
userDetail.setTenantId(user.getTenantId());
userDetail.setName(user.getName());
userDetail.setRealName(user.getName());
userDetail.setAccount(user.getAccount());
userDetail.setPassword(user.getPassword());
userDetail.setPhone(user.getPhone());
userDetail.setEmail(user.getEmail());
userDetail.setAuthorities(Collections.singletonList(REGISTER));
userDetail.setClient(client);
return userDetail;
}
}

View File

@@ -0,0 +1,103 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.granter;
import jakarta.servlet.http.HttpServletRequest;
import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2ClientService;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.core.sms.model.SmsCode;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.resource.feign.ISmsClient;
import org.springframework.stereotype.Component;
/**
* SmsTokenGranter
*
* @author BladeX
*/
@Component
public class SmsTokenGranter extends AbstractTokenGranter {
private final OAuth2UserService userService;
private final ISmsClient smsClient;
private final OAuth2Properties properties;
public SmsTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, ISmsClient smsClient, OAuth2Properties properties) {
super(clientService, userService, passwordHandler);
this.userService = userService;
this.smsClient = smsClient;
this.properties = properties;
}
@Override
public String type() {
return SMS_CODE;
}
@Override
public OAuth2User user(OAuth2Request request) {
// 获取基础信息
String tenantId = request.getTenantId();
SmsCode smsCode = buildSmsCode();
// 校验手机加密认证
String decryptedPhone = SM2Util.decrypt(smsCode.getPhone(), properties.getPublicKey(), properties.getPrivateKey());
if (StringUtil.isBlank(decryptedPhone)) {
throw new UserInvalidException(OAuth2TokenConstant.USER_PHONE_NOT_FOUND);
}
// 获取短信验证信息
R result = smsClient.validateMessage(tenantId, StringPool.EMPTY, smsCode.getId(), smsCode.getValue(), decryptedPhone);
if (!result.isSuccess()) {
throw new UserInvalidException(OAuth2TokenConstant.CAPTCHA_NOT_CORRECT);
}
// 获取用户信息
OAuth2User user = userService.loadByPhone(decryptedPhone, request);
// 校验用户信息
if (!userService.validateUser(user)) {
throw new UserInvalidException(OAuth2TokenConstant.TOKEN_NOT_CORRECT);
}
// 设置客户端信息
user.setClient(client(request));
return user;
}
private SmsCode buildSmsCode() {
HttpServletRequest request = WebUtil.getRequest();
return new SmsCode().setId(request.getParameter("id"))
.setPhone(request.getParameter("phone"))
.setValue(request.getParameter("value"));
}
}

View File

@@ -0,0 +1,120 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.granter;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import org.springblade.auth.utils.TokenUtil;
import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2ClientService;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
import org.springblade.core.social.props.SocialProperties;
import org.springblade.core.social.utils.SocialUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.system.pojo.entity.UserInfo;
import org.springblade.system.pojo.entity.UserOauth;
import org.springblade.system.feign.IUserClient;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* SocialTokenGranter
*
* @author Chill
*/
@Component
public class SocialTokenGranter extends AbstractTokenGranter {
private static final Integer AUTH_SUCCESS_CODE = 2000;
private final IUserClient userClient;
private final SocialProperties socialProperties;
public SocialTokenGranter(OAuth2ClientService clientService, OAuth2UserService oAuth2UserService, PasswordHandler passwordHandler, IUserClient userClient, SocialProperties socialProperties) {
super(clientService, oAuth2UserService, passwordHandler);
this.userClient = userClient;
this.socialProperties = socialProperties;
}
@Override
public String type() {
return SOCIAL;
}
@Override
public OAuth2User user(OAuth2Request request) {
String tenantId = request.getTenantId();
// 开放平台来源
String sourceParameter = request.getSource();
// 匹配是否有别名定义
String source = socialProperties.getAlias().getOrDefault(sourceParameter, sourceParameter);
// 开放平台授权码
String code = request.getCode();
// 开放平台状态吗
String state = request.getState();
// 获取开放平台授权数据
AuthRequest authRequest = SocialUtil.getAuthRequest(source, socialProperties);
AuthCallback authCallback = new AuthCallback();
authCallback.setCode(code);
authCallback.setState(state);
AuthResponse<?> authResponse = authRequest.login(authCallback);
AuthUser authUser = null;
if (authResponse.getCode() == AUTH_SUCCESS_CODE) {
authUser = (AuthUser) authResponse.getData();
} else {
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
}
// 组装数据
UserOauth userOauth = Objects.requireNonNull(BeanUtil.copyProperties(authUser, UserOauth.class));
userOauth.setSource(authUser.getSource());
userOauth.setTenantId(tenantId);
userOauth.setUuid(authUser.getUuid());
R<UserInfo> result = userClient.userAuthInfo(userOauth);
if (!result.isSuccess()) {
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
}
// 设置Oauth2用户信息
OAuth2User user = TokenUtil.convertUser(result.getData(), request);
// 设置客户端信息
user.setClient(client(request));
return user;
}
}

View File

@@ -0,0 +1,200 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.constant.TenantConstant;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.oauth2.exception.ExceptionCode;
import org.springblade.core.oauth2.handler.AbstractAuthorizationHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.provider.OAuth2Validation;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.tenant.BladeTenantProperties;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.DesUtil;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.system.cache.SysCache;
import org.springblade.system.pojo.entity.Tenant;
import java.util.Date;
import java.util.List;
/**
* BladeAuthorizationHandler
*
* @author BladeX
*/
@Slf4j
@RequiredArgsConstructor
public class BladeAuthorizationHandler extends AbstractAuthorizationHandler {
private final BladeProperties bladeProperties;
private final BladeTenantProperties tenantProperties;
private final OAuth2Properties oAuth2Properties;
private final BladeLockHandler lockHandler;
private final BladeLogHandler logHandler;
/**
* 自定义弱密码列表
*/
private static final List<String> WEAK_PASSWORDS = List.of("admin", "hr", "manager", "boss");
/**
* 认证前校验
*
* @param request 请求信息
* @return boolean
*/
@Override
public OAuth2Validation preValidation(OAuth2Request request) {
if (request.isPassword() || request.isCaptchaCode()) {
// 生产环境弱密码校验
if (bladeProperties.isProd() && isWeakPassword(request.getPassword())) {
return buildValidationFailure(ExceptionCode.INVALID_USER_PASSWORD);
}
// 判断账号是否锁定
OAuth2Validation accountValidation = lockHandler.validateAccountLock(request.getTenantId(), request.getUsername());
if (!accountValidation.isSuccess()) {
return accountValidation;
}
// 判断IP是否锁定
OAuth2Validation ipValidation = lockHandler.validateIpLock(request.getTenantId());
if (!ipValidation.isSuccess()) {
return ipValidation;
}
}
return super.preValidation(request);
}
/**
* 认证前失败回调
*
* @param validation 失败信息
*/
@Override
public void preFailure(OAuth2Request request, OAuth2Validation validation) {
// 处理认证失败,增加错误次数
lockHandler.handleAuthFailure(request.getTenantId(), request.getUsername());
log.error("用户:{},认证失败,失败原因:{}", request.getUsername(), validation.getMessage());
}
/**
* 认证校验
*
* @param user 用户信息
* @param request 请求信息
* @return boolean
*/
@Override
public OAuth2Validation authValidation(OAuth2User user, OAuth2Request request) {
// 密码模式、刷新token模式、验证码模式需要校验租户状态
if (request.isPassword() || request.isRefreshToken() || request.isCaptchaCode()) {
// 租户校验
OAuth2Validation tenantValidation = validateTenant(user.getTenantId());
if (!tenantValidation.isSuccess()) {
return tenantValidation;
}
}
return super.authValidation(user, request);
}
/**
* 认证成功回调
*
* @param user 用户信息
*/
@Override
public void authSuccessful(OAuth2User user, OAuth2Request request) {
// 处理认证成功,清空错误次数
lockHandler.handleAuthSuccess(user.getTenantId(), user.getAccount());
// 记录认证成功日志
logHandler.handleAuthLog(user, request);
log.info("用户:{},认证成功", user.getAccount());
}
/**
* 认证失败回调
*
* @param user 用户信息
* @param validation 失败信息
*/
@Override
public void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation) {
// 自定义认证失败回调
}
/**
* 判断是否为弱密码
*
* @param rawPassword 加密密码
* @return boolean
*/
private boolean isWeakPassword(String rawPassword) {
// 获取公钥
String publicKey = oAuth2Properties.getPublicKey();
// 获取私钥
String privateKey = oAuth2Properties.getPrivateKey();
// 解密密码
String decryptPassword = SM2Util.decrypt(rawPassword, publicKey, privateKey);
return WEAK_PASSWORDS.stream()
.anyMatch(weakPass -> weakPass.equalsIgnoreCase(decryptPassword));
}
/**
* 租户授权校验
*
* @param tenantId 租户id
* @return OAuth2Validation
*/
private OAuth2Validation validateTenant(String tenantId) {
// 租户校验
Tenant tenant = SysCache.getTenant(tenantId);
if (tenant == null) {
return buildValidationFailure(ExceptionCode.USER_TENANT_NOT_FOUND);
}
// 租户授权时间校验
Date expireTime = tenant.getExpireTime();
if (tenantProperties.getLicense()) {
String licenseKey = tenant.getLicenseKey();
String decrypt = DesUtil.decryptFormHex(licenseKey, TenantConstant.DES_KEY);
Tenant license = JsonUtil.parse(decrypt, Tenant.class);
if (license == null || !license.getId().equals(tenant.getId())) {
return buildValidationFailure(ExceptionCode.UNAUTHORIZED_USER_TENANT);
}
expireTime = license.getExpireTime();
}
if (expireTime != null && expireTime.before(DateUtil.now())) {
return buildValidationFailure(ExceptionCode.UNAUTHORIZED_USER_TENANT);
}
return new OAuth2Validation();
}
}

View File

@@ -0,0 +1,155 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.oauth2.exception.ExceptionCode;
import org.springblade.core.oauth2.provider.OAuth2Validation;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.system.feign.IAuthLockClient;
import java.util.concurrent.CompletableFuture;
/**
* 失败锁定处理器
* 统一管理账号锁定和IP锁定逻辑
*
* @author BladeX
*/
@Slf4j
@RequiredArgsConstructor
public class BladeLockHandler {
private final IAuthLockClient authLockClient;
/**
* 校验账号是否锁定
*
* @param tenantId 租户id
* @param account 账号
* @return OAuth2Validation
*/
public OAuth2Validation validateAccountLock(String tenantId, String account) {
try {
R<Boolean> result = authLockClient.isAccountLocked(tenantId, account);
if (result.isSuccess() && Boolean.TRUE.equals(result.getData())) {
log.error("用户:{}已锁定请求ip{}", account, WebUtil.getIP());
return buildValidationFailure();
}
} catch (Exception e) {
log.warn("账号锁定校验异常: {}", e.getMessage());
}
return new OAuth2Validation();
}
/**
* 校验IP是否锁定
*
* @param tenantId 租户id
* @return OAuth2Validation
*/
public OAuth2Validation validateIpLock(String tenantId) {
String clientIp = WebUtil.getIP();
try {
R<Boolean> result = authLockClient.isIpLocked(tenantId, clientIp);
if (result.isSuccess() && Boolean.TRUE.equals(result.getData())) {
log.error("IP{},已锁定", clientIp);
return buildValidationFailure();
}
} catch (Exception e) {
log.warn("IP锁定校验异常: {}", e.getMessage());
}
return new OAuth2Validation();
}
/**
* 处理认证失败
* 同时增加账号和IP错误次数
*
* @param tenantId 租户id
* @param account 账号
* @param userId 用户ID
*/
public void handleAuthFailure(String tenantId, String account, Long userId) {
String ip = WebUtil.getIP();
String userAgent = WebUtil.getUserAgent();
CompletableFuture.runAsync(() -> {
try {
authLockClient.addAccountFailCount(tenantId, account, userId, ip, userAgent);
authLockClient.addIpFailCount(tenantId, ip, userAgent);
} catch (Exception e) {
log.warn("认证失败计数异常: {}", e.getMessage());
}
});
}
/**
* 处理认证失败
* 同时增加账号和IP错误次数
*
* @param tenantId 租户id
* @param account 账号
*/
public void handleAuthFailure(String tenantId, String account) {
handleAuthFailure(tenantId, account, null);
}
/**
* 处理认证成功
* 释放系统自动锁定
*
* @param tenantId 租户id
* @param account 账号
*/
public void handleAuthSuccess(String tenantId, String account) {
String ip = WebUtil.getIP();
CompletableFuture.runAsync(() -> {
try {
authLockClient.releaseSystemLock(tenantId, account);
authLockClient.releaseSystemIpLock(tenantId, ip);
} catch (Exception e) {
log.warn("认证成功释放锁定异常: {}", e.getMessage());
}
});
}
/**
* 构建校验失败结果
*
* @return OAuth2Validation
*/
private OAuth2Validation buildValidationFailure() {
OAuth2Validation validation = new OAuth2Validation();
validation.setSuccess(false);
validation.setCode(ExceptionCode.USER_TOO_MANY_FAILS.getCode());
validation.setMessage(ExceptionCode.USER_TOO_MANY_FAILS.getMessage());
return validation;
}
}

View File

@@ -0,0 +1,99 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.handler;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.launch.server.ServerInfo;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.system.pojo.entity.AuthLog;
import org.springblade.system.feign.IAuthLogClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;
/**
* 认证日志处理器
* 在用户认证成功时异步记录登录日志
*
* @author BladeX
*/
@Slf4j
@RequiredArgsConstructor
public class BladeLogHandler {
private final IAuthLogClient authLogClient;
private final BladeProperties bladeProperties;
private final ServerInfo serverInfo;
/**
* 记录认证成功日志
*
* @param user 用户信息
* @param request 请求信息
*/
public void handleAuthLog(OAuth2User user, OAuth2Request request) {
// 异步记录日志,避免影响认证性能
CompletableFuture.runAsync(() -> {
try {
AuthLog authLog = buildAuthLog(user, request);
authLogClient.saveAuthLog(authLog);
} catch (Exception exception) {
log.error("记录认证日志异常:{}", exception.getMessage(), exception);
}
});
}
/**
* 构建认证日志实体
*
* @param user 用户信息
* @param request 请求信息
* @return AuthLog
*/
private AuthLog buildAuthLog(OAuth2User user, OAuth2Request request) {
AuthLog authLog = new AuthLog();
authLog.setUserId(Func.toLong(user.getUserId()));
authLog.setTenantId(user.getTenantId());
authLog.setServiceId(bladeProperties.getName());
authLog.setServerIp(serverInfo.getIpWithPort());
authLog.setServerHost(serverInfo.getHostName());
authLog.setEnv(bladeProperties.getEnv());
authLog.setAccount(user.getAccount());
authLog.setRealName(user.getRealName());
authLog.setGrantType(request.getGrantType());
authLog.setRemoteIp(WebUtil.getIP(request.getHttpRequest()));
authLog.setUserAgent(WebUtil.getUserAgent(request.getHttpRequest()));
authLog.setLoginTime(DateUtil.now());
return authLog;
}
}

View File

@@ -0,0 +1,64 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.handler;
import org.springblade.core.oauth2.handler.OAuth2PasswordHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
/**
* BladePasswordHandler
*
* @author BladeX
*/
public class BladePasswordHandler extends OAuth2PasswordHandler {
public BladePasswordHandler(OAuth2Properties properties) {
super(properties);
}
/**
* 判断密码是否匹配
*
* @param rawPassword 请求时提交的原密码
* @param encodedPassword 数据库加密后的密码
* @return boolean
*/
@Override
public boolean matches(String rawPassword, String encodedPassword) {
return super.matches(rawPassword, encodedPassword);
}
/**
* 加密密码规则
*
* @param rawPassword 密码
* @return 加密后的密码
*/
@Override
public String encode(String rawPassword) {
return super.encode(rawPassword);
}
}

View File

@@ -0,0 +1,59 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.handler;
import org.springblade.core.jwt.props.JwtProperties;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.oauth2.handler.OAuth2TokenHandler;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.provider.OAuth2Token;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.tool.support.Kv;
/**
* BladeTokenHandler
*
* @author BladeX
*/
public class BladeTokenHandler extends OAuth2TokenHandler {
public BladeTokenHandler(JwtProperties properties) {
super(properties);
}
@Override
public OAuth2Token enhance(OAuth2User user, OAuth2Token token, OAuth2Request request) {
// 父类令牌状态配置
OAuth2Token enhanceToken = super.enhance(user, token, request);
// 令牌统一处理,增加或删减字段
Kv args = enhanceToken.getArgs();
args.set(TokenConstant.USER_NAME, user.getAccount());
// 返回令牌
return enhanceToken;
}
}

View File

@@ -0,0 +1,62 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.service;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2Client;
import org.springblade.core.oauth2.service.impl.OAuth2ClientDetailService;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* BladeClientDetailService
*
* @author Chill
*/
public class BladeClientDetailService extends OAuth2ClientDetailService {
public BladeClientDetailService(JdbcTemplate jdbcTemplate) {
super(jdbcTemplate);
}
@Override
public OAuth2Client loadByClientId(String clientId) {
return super.loadByClientId(clientId);
}
@Override
public OAuth2Client loadByClientId(String clientId, OAuth2Request request) {
return super.loadByClientId(clientId, request);
}
@Override
public boolean validateClient(OAuth2Client client, String clientId, String clientSecret) {
return super.validateClient(client, clientId, clientSecret);
}
@Override
public boolean validateGranter(OAuth2Client client, String grantType) {
return super.validateGranter(client, grantType);
}
}

View File

@@ -0,0 +1,111 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.service;
import lombok.RequiredArgsConstructor;
import org.springblade.auth.utils.TokenUtil;
import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.system.feign.IUserClient;
import org.springblade.system.pojo.entity.UserInfo;
import org.springblade.system.pojo.enums.UserType;
import java.util.Optional;
/**
* BladeUserDetailService
*
* @author Chill
*/
@RequiredArgsConstructor
public class BladeUserDetailService implements OAuth2UserService {
private final IUserClient userClient;
@Override
public OAuth2User loadByUserId(String userId, OAuth2Request request) {
// 获取用户参数
String userType = Optional.ofNullable(request.getUserType())
.filter(s -> !StringUtil.isBlank(s))
.orElse(UserType.WEB.getName());
// 获取用户信息
R<UserInfo> result = userClient.userInfo(Func.toLong(userId), userType);
if (!result.isSuccess()) {
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
}
// 构建oauth2用户信息
return TokenUtil.convertUser(result.getData(), request);
}
@Override
public OAuth2User loadByUsername(String username, OAuth2Request request) {
// 获取用户参数
String userType = Optional.ofNullable(request.getUserType())
.filter(s -> !StringUtil.isBlank(s))
.orElse(UserType.WEB.getName());
String tenantId = request.getTenantId();
// 获取用户信息
R<UserInfo> result = userClient.userInfo(tenantId, username, userType);
if (!result.isSuccess()) {
return null;
}
// 构建oauth2用户信息
return TokenUtil.convertUser(result.getData(), request);
}
@Override
public OAuth2User loadByPhone(String phone, OAuth2Request request) {
// 获取用户参数
String userType = Optional.ofNullable(request.getUserType())
.filter(s -> !StringUtil.isBlank(s))
.orElse(UserType.WEB.getName());
String tenantId = request.getTenantId();
// 获取用户信息
R<UserInfo> result = userClient.userInfoByPhone(tenantId, phone, userType);
if (!result.isSuccess()) {
return null;
}
// 构建oauth2用户信息
return TokenUtil.convertUser(result.getData(), request);
}
@Override
public boolean validateUser(OAuth2User user) {
return Optional.ofNullable(user)
.filter(u -> u.getUserId() != null && !u.getUserId().isEmpty()) // 检查userId不为空
.filter(u -> u.getAuthorities() != null && !u.getAuthorities().isEmpty()) // 检查authorities不为空
.isPresent(); // 如果上述条件都满足则返回true否则返回false
}
}

View File

@@ -0,0 +1,88 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.auth.utils;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.SysCache;
import org.springblade.system.pojo.entity.User;
import org.springblade.system.pojo.entity.UserInfo;
import java.util.Collections;
/**
* 认证工具类
*
* @author Chill
*/
public class TokenUtil {
/**
* 系统用户转换为OAuth2标准用户
*
* @param userInfo 用户信息
* @param request 请求信息
* @return OAuth2User
*/
public static OAuth2User convertUser(UserInfo userInfo, OAuth2Request request) {
// 为空则返回null
if (userInfo == null || userInfo.getUser() == null) {
return null;
}
User user = userInfo.getUser();
String userDept = request.getUserDept();
String userRole = request.getUserRole();
// 单独指定部门
if (Func.isNotEmpty(userDept) && user.getDeptId().contains(userDept)) {
user.setDeptId(userDept);
}
// 单独指定角色
if (Func.isNotEmpty(userRole) && user.getRoleId().contains(userRole)) {
user.setRoleId(userRole);
userInfo.setRoles(SysCache.getRoleAliases(userRole));
}
// 构建oauth2所需用户信息
OAuth2UserDetail userDetail = new OAuth2UserDetail();
userDetail.setUserId(String.valueOf(user.getId()));
userDetail.setOauthId(userInfo.getOauthId());
userDetail.setTenantId(user.getTenantId());
userDetail.setName(user.getName());
userDetail.setRealName(user.getRealName());
userDetail.setAccount(user.getAccount());
userDetail.setPassword(user.getPassword());
userDetail.setDeptId(user.getDeptId());
userDetail.setPostId(user.getPostId());
userDetail.setRoleId(user.getRoleId());
userDetail.setRoleName(Func.join(userInfo.getRoles()));
userDetail.setAvatar(user.getAvatar());
userDetail.setAuthorities(userInfo.getRoles());
userDetail.setDetail(userInfo.getDetail());
return userDetail;
}
}

View File

@@ -0,0 +1,15 @@
#服务器端口
server:
port: 8100
#数据源配置
spring:
datasource:
url: ${blade.datasource.dev.url}
username: ${blade.datasource.dev.username}
password: ${blade.datasource.dev.password}
#第三方登陆
social:
enabled: true
domain: http://127.0.0.1:2888

View File

@@ -0,0 +1,15 @@
#服务器端口
server:
port: 8100
#数据源配置
spring:
datasource:
url: ${blade.datasource.prod.url}
username: ${blade.datasource.prod.username}
password: ${blade.datasource.prod.password}
#第三方登陆
social:
enabled: true
domain: http://127.0.0.1:2888

View File

@@ -0,0 +1,15 @@
#服务器端口
server:
port: 8100
#数据源配置
spring:
datasource:
url: ${blade.datasource.test.url}
username: ${blade.datasource.test.username}
password: ${blade.datasource.test.password}
#第三方登陆
social:
enabled: true
domain: http://127.0.0.1:2888

View File

@@ -0,0 +1,65 @@
# 在使用Spring默认数据源Hikari的情况下配置以下配置项
spring:
datasource:
hikari:
# 自动提交从池中返回的连接
auto-commit: true
# 连接池中维护的最小空闲连接数
minimum-idle: 10
# 连接池中允许的最大连接数。缺省值10推荐的公式((core_count * 2) + effective_spindle_count)
maximum-pool-size: 60
# 空闲连接超时时间默认值60000010分钟大于等于max-lifetime且max-lifetime>0会被重置为0不等于0且小于10秒会被重置为10秒。
# 只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放
idle-timeout: 30000
# 连接最大存活时间.不等于0且小于30秒会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
max-lifetime: 1800000
# 等待连接池分配连接的最大时长毫秒超过这个时长还没可用的连接则发生SQLException 缺省:30秒
connection-timeout: 30000
# 连接测试查询
connection-test-query: select 1
#connection-test-query: select 1 from dual
freemarker:
# 模板后缀名
suffix: .ftl
# 文档类型
content-type: text/html
# 页面编码
charset: UTF-8
# 页面缓存
cache: false
# 模板路径
template-loader-path: classpath:/templates/
web:
# 资源路径
resources:
static-locations: classpath:/static/
#swagger文档
swagger:
base-packages:
- org.springblade
- org.springframework.security.oauth2.provider.endpoint
#第三方登陆
social:
oauth:
GITHUB:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/github
GITEE:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/gitee
WECHAT_OPEN:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/wechat
QQ:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/qq
DINGTALK:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/dingtalk

View File

@@ -0,0 +1,47 @@
package org.springblade.test;
import org.springblade.core.tool.utils.AesUtil;
import org.springblade.core.tool.utils.DesUtil;
import org.springblade.core.tool.utils.StringPool;
/**
* Key生成器
*
* @author Chill
*/
public class CryptoKeyGenerator {
public static void main(String[] args) {
String cryptoKey = AesUtil.genAesKey();
String aesKey = AesUtil.genAesKey();
String desKey = DesUtil.genDesKey();
System.out.println("=========== blade.token.crypto-key 配置如下 ============");
System.out.println("#blade配置\n" +
"blade:\n" +
" token:\n" +
" crypto-key: " + cryptoKey);
System.out.println("=======================================================");
System.out.println(StringPool.EMPTY);
System.out.println("============== blade.api.crypto 配置如下 ===============");
System.out.println("#blade配置\n" +
"blade:\n" +
" api:\n" +
" crypto:\n" +
" enabled: true\n" +
" aes-key: " + aesKey + "\n" +
" des-key: " + desKey);
System.out.println("=======================================================");
System.out.println(StringPool.EMPTY);
System.out.println("============== saber crypto.js 配置如下 ===============");
System.out.println("export default class crypto {\n" +
" static cryptoKey = '" + cryptoKey + "';\n" +
" static aesKey = '" + aesKey + "';\n" +
" static desKey = '" + desKey + "';\n" +
"}");
System.out.println("=======================================================");
}
}

View File

@@ -0,0 +1,45 @@
package org.springblade.test;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.springblade.core.tool.utils.AesUtil;
import org.springblade.core.tool.utils.DesUtil;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.SM4Util;
/**
* 加密密钥生成器
*
* @author BladeX
*/
public class EncryptKeyGenerator {
public static void main(String[] args) {
// 生成对称加密密钥
String aesKey = AesUtil.genAesKey();
String desKey = DesUtil.genDesKey();
String sm4Key = SM4Util.generateKey();
// 生成SM2非对称加密密钥对
AsymmetricCipherKeyPair sm2KeyPair = SM2Util.generateKeyPair();
String sm2PublicKey = SM2Util.getPublicKeyString(sm2KeyPair);
String sm2PrivateKey = SM2Util.getPrivateKeyString(sm2KeyPair);
System.out.println("\n================== Encryption Keys Generated ==================\n");
System.out.println("[Symmetric Keys / 对称加密密钥]");
System.out.println();
System.out.println("AES-256-Key: " + aesKey);
System.out.println("DES-Key: " + desKey);
System.out.println("SM4-Key(国密): " + sm4Key);
System.out.println("\n----------------------------------------------------------------\n");
System.out.println("[Asymmetric Keys / 非对称加密密钥]");
System.out.println();
System.out.println("SM2-PublicKey(国密公钥): " + sm2PublicKey);
System.out.println("SM2-PrivateKey(国密私钥): " + sm2PrivateKey);
System.out.println("\n================================================================\n");
}
}

View File

@@ -0,0 +1,22 @@
package org.springblade.test;
import org.springblade.core.tool.utils.RandomType;
import org.springblade.core.tool.utils.StringUtil;
/**
* signKey生成器
*
* @author Chill
*/
public class SignKeyGenerator {
public static void main(String[] args) {
System.out.println("=========== blade.token.sign-key 配置如下 ==============");
System.out.println("#blade配置\n" +
"blade:\n" +
" token:\n" +
" sign-key: " + StringUtil.random(32, RandomType.ALL) );
System.out.println("=======================================================");
}
}

View File

@@ -0,0 +1,46 @@
package org.springblade.test;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.util.encoders.Hex;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.StringPool;
/**
* 国密算法生成器
*
* @author Chill
*/
public class Sm2KeyGenerator {
public static void main(String[] args) {
System.out.println("================ blade.oauth2 配置如下 =================");
AsymmetricCipherKeyPair keyPair = SM2Util.generateKeyPair();
String publicKey = SM2Util.getPublicKeyString(keyPair);
String privateKey = SM2Util.getPrivateKeyString(keyPair);
System.out.println("#blade配置 \n" +
"blade:\n" +
" oauth2:\n" +
" enabled: true\n" +
" public-key: " + publicKey + "\n" +
" private-key: " + privateKey);
System.out.println("=======================================================");
System.out.println(StringPool.EMPTY);
System.out.println("============== saber website.js 配置如下 ===============");
System.out.println("//saber配置\n" +
"oauth2: {\n" +
" publicKey: '" + publicKey + "',\n" +
"}");
System.out.println("=======================================================");
System.out.println(StringPool.EMPTY);
System.out.println("============== 密码:[admin] 加密流程如下 ================");
String password = "admin";
byte[] encryptedData = SM2Util.encrypt(password, publicKey);
String decryptedText = SM2Util.decrypt(encryptedData, privateKey);
System.out.println("加密前: " + password);
System.out.println("加密后: " + Hex.toHexString(encryptedData));
System.out.println("解密后: " + decryptedText);
System.out.println("请注意: 此密文为前端加密后调用token接口的密码参数");
System.out.println("=======================================================");
}
}

View File

@@ -0,0 +1,120 @@
package org.springblade.test;
import lombok.SneakyThrows;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.util.encoders.Hex;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.StringUtil;
/**
* SM2Test
*
* @author Chill
*/
public class Sm2Test {
@SneakyThrows
public static void main(String[] args) {
System.out.println("================== SM2 加解密与签名测试 ==================");
// 1. 生成SM2密钥对
AsymmetricCipherKeyPair keyPair = SM2Util.generateKeyPair();
String publicKeyString = SM2Util.getPublicKeyString(keyPair);
String privateKeyString = SM2Util.getPrivateKeyString(keyPair);
System.out.println("1. 生成SM2密钥对:");
System.out.println(" 公钥 (16进制): " + publicKeyString);
System.out.println(" 公钥长度: " + publicKeyString.length());
System.out.println(" 私钥 (16进制): " + privateKeyString);
System.out.println(" 私钥长度: " + privateKeyString.length());
System.out.println("--------------------------------------------------");
// 2. 从字符串恢复密钥
System.out.println("2. 从字符串恢复密钥...");
ECPublicKeyParameters publicKey = SM2Util.stringToPublicKey(publicKeyString);
ECPrivateKeyParameters privateKey = SM2Util.stringToPrivateKey(privateKeyString);
System.out.println(" 密钥恢复成功!");
System.out.println("--------------------------------------------------");
// 3. 准备待加密的原文
String originalText = "Hello BladeX! 这是一段需要被国密SM2算法加密和签名的敏感信息。1234567890";
System.out.println("3. 待加密的原文: " + originalText);
System.out.println("--------------------------------------------------");
// 4. 执行加密
System.out.println("4. 正在执行加密操作...");
byte[] encryptedData = SM2Util.encrypt(originalText, publicKey);
if (encryptedData != null && encryptedData.length > 0) {
System.out.println(" 加密成功!");
System.out.println(" 加密后的密文 (16进制): " + Hex.toHexString(encryptedData));
System.out.println(" 密文长度: " + encryptedData.length + " 字节");
} else {
System.out.println(" 加密失败!");
return;
}
System.out.println("--------------------------------------------------");
// 5. 执行解密
System.out.println("5. 正在执行解密操作...");
String decryptedText = SM2Util.decrypt(encryptedData, privateKey);
if (StringUtil.isNotBlank(decryptedText)) {
System.out.println(" 解密成功!");
System.out.println(" 解密后的明文: " + decryptedText);
} else {
System.out.println(" 解密失败!");
return;
}
System.out.println("--------------------------------------------------");
// 6. 执行数字签名
System.out.println("6. 正在执行数字签名...");
byte[] signature = SM2Util.sign(originalText, privateKey);
if (signature != null && signature.length > 0) {
System.out.println(" 签名成功!");
System.out.println(" 数字签名 (16进制): " + Hex.toHexString(signature));
System.out.println(" 签名长度: " + signature.length + " 字节");
} else {
System.out.println(" 签名失败!");
return;
}
System.out.println("--------------------------------------------------");
// 7. 验证签名
System.out.println("7. 正在验证数字签名...");
boolean isVerified = SM2Util.verify(originalText, signature, publicKey);
System.out.println(" 签名验证结果: " + (isVerified ? "有效" : "无效"));
System.out.println("--------------------------------------------------");
// 8. 校验加解密结果
System.out.println("8. 正在校验原文与解密后的明文是否一致...");
boolean isDecryptMatch = originalText.equals(decryptedText);
System.out.println(" 加解密校验结果: " + (isDecryptMatch ? "一致" : "不一致"));
System.out.println("==================================================");
// 9. 最终结果汇总
if (isDecryptMatch && isVerified) {
System.out.println("\n[成功] SM2加解密与签名流程验证通过");
System.out.println(" ✓ 密钥对生成成功");
System.out.println(" ✓ 加解密功能正常");
System.out.println(" ✓ 数字签名验证通过");
} else {
System.out.println("\n[失败] SM2加解密与签名流程验证失败");
if (!isDecryptMatch) {
System.out.println(" ✗ 加解密校验失败");
}
if (!isVerified) {
System.out.println(" ✗ 数字签名验证失败");
}
}
}
}

View File

@@ -0,0 +1,71 @@
package org.springblade.test;
import lombok.SneakyThrows;
import org.springblade.core.tool.utils.SM4Util;
import org.springblade.core.tool.utils.StringUtil;
/**
* Sm4Test
*
* @author BladeX
*/
public class Sm4Test {
@SneakyThrows
public static void main(String[] args) {
System.out.println("================== SM4 加解密测试 ==================");
// 1. 生成SM4密钥
String sm4Key = SM4Util.generateKey();
System.out.println("1. 生成SM4密钥 (16进制): " + sm4Key);
System.out.println(" 密钥长度 (Hex): " + sm4Key.length());
System.out.println("--------------------------------------------------");
// 2. 准备待加密的原文
String originalText = "Hello BladeX! 这是一段需要被国密SM4算法加密的敏感信息。1234567890";
System.out.println("2. 待加密的原文: " + originalText);
System.out.println("--------------------------------------------------");
// 3. 执行加密
System.out.println("3. 正在执行加密操作...");
String encryptedText = SM4Util.encrypt(originalText, sm4Key);
if (StringUtil.isNotBlank(encryptedText)) {
System.out.println(" 加密成功!");
System.out.println(" 加密后的密文 (16进制): " + encryptedText);
} else {
System.out.println(" 加密失败!");
return;
}
System.out.println("--------------------------------------------------");
// 4. 执行解密
System.out.println("4. 正在执行解密操作...");
String decryptedText = SM4Util.decrypt(encryptedText, sm4Key);
if (StringUtil.isNotBlank(decryptedText)) {
System.out.println(" 解密成功!");
System.out.println(" 解密后的明文: " + decryptedText);
} else {
System.out.println(" 解密失败!");
return;
}
System.out.println("--------------------------------------------------");
// 5. 校验结果
System.out.println("5. 正在校验原文与解密后的明文是否一致...");
boolean isMatch = originalText.equals(decryptedText);
System.out.println(" 校验结果: " + (isMatch ? "一致" : "不一致"));
System.out.println("==================================================");
if (isMatch) {
System.out.println("\n[成功] SM4加解密流程验证通过");
} else {
System.out.println("\n[失败] SM4加解密流程验证失败");
}
}
}

45
blade-common/pom.xml Normal file
View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>BladeX</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-common</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-launch</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
<finalName>${project.name}</finalName>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,63 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.common.cache;
/**
* 缓存名
*
* @author Chill
*/
public interface CacheNames {
/**
* 返回拼接后的key
*
* @param cacheKey 缓存key
* @param cacheKeyValue 缓存key值
* @return tenantKey
*/
static String cacheKey(String cacheKey, String cacheKeyValue) {
return cacheKey.concat(cacheKeyValue);
}
/**
* 返回租户格式的key
*
* @param tenantId 租户编号
* @param cacheKey 缓存key
* @param cacheKeyValue 缓存key值
* @return tenantKey
*/
static String tenantKey(String tenantId, String cacheKey, String cacheKeyValue) {
return tenantId.concat(":").concat(cacheKey).concat(cacheKeyValue);
}
/**
* 验证码key
*/
String CAPTCHA_KEY = "blade:auth::blade:captcha:";
}

View File

@@ -0,0 +1,41 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.common.config;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
/**
* 公共封装包配置类
*
* @author Chill
*/
@Configuration(proxyBeanMethods = false)
@AllArgsConstructor
public class BladeCommonConfiguration {
}

View File

@@ -0,0 +1,75 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.common.constant;
/**
* 通用常量
*
* @author Chill
*/
public interface CommonConstant {
/**
* sword 系统名
*/
String SWORD_NAME = "sword";
/**
* saber 系统名
*/
String SABER_NAME = "saber";
/**
* 顶级父节点id
*/
Long TOP_PARENT_ID = 0L;
/**
* 顶级父节点名称
*/
String TOP_PARENT_NAME = "顶级";
/**
* 未封存状态值
*/
Integer NOT_SEALED_ID = 0;
/**
* 默认排序字段
*/
String SORT_FIELD = "sort";
/**
* 数据权限类型
*/
Integer DATA_SCOPE_CATEGORY = 1;
/**
* 接口权限类型
*/
Integer API_SCOPE_CATEGORY = 2;
}

View File

@@ -0,0 +1,222 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.common.constant;
import org.springblade.core.launch.constant.AppConstant;
/**
* 启动常量
*
* @author Chill
*/
public interface LauncherConstant {
/**
* nacos 用户名
*/
String NACOS_USERNAME = "nacos";
/**
* nacos 密码
*/
String NACOS_PASSWORD = "nacos";
/**
* nacos dev 地址
*/
String NACOS_DEV_ADDR = "127.0.0.1:8848";
/**
* nacos prod 地址
*/
String NACOS_PROD_ADDR = "172.30.0.48:8848";
/**
* nacos test 地址
*/
String NACOS_TEST_ADDR = "172.30.0.48:8848";
/**
* sentinel dev 地址
*/
String SENTINEL_DEV_ADDR = "127.0.0.1:8858";
/**
* sentinel prod 地址
*/
String SENTINEL_PROD_ADDR = "172.30.0.58:8858";
/**
* sentinel test 地址
*/
String SENTINEL_TEST_ADDR = "172.30.0.58:8858";
/**
* seata dev 地址
*/
String SEATA_DEV_ADDR = "127.0.0.1:8091";
/**
* seata prod 地址
*/
String SEATA_PROD_ADDR = "172.30.0.68:8091";
/**
* seata test 地址
*/
String SEATA_TEST_ADDR = "172.30.0.68:8091";
/**
* zipkin dev 地址
*/
String ZIPKIN_DEV_ADDR = "http://127.0.0.1:9411";
/**
* zipkin prod 地址
*/
String ZIPKIN_PROD_ADDR = "http://172.30.0.71:9411";
/**
* zipkin test 地址
*/
String ZIPKIN_TEST_ADDR = "http://172.30.0.71:9411";
/**
* elk dev 地址
*/
String ELK_DEV_ADDR = "127.0.0.1:9000";
/**
* elk prod 地址
*/
String ELK_PROD_ADDR = "172.30.0.72:9000";
/**
* elk test 地址
*/
String ELK_TEST_ADDR = "172.30.0.72:9000";
/**
* seata file模式
*/
String FILE_MODE = "file";
/**
* seata nacos模式
*/
String NACOS_MODE = "nacos";
/**
* seata default模式
*/
String DEFAULT_MODE = "default";
/**
* seata group后缀
*/
String GROUP_NAME = "-group";
/**
* seata 服务组格式
*
* @param appName 服务名
* @return group
*/
static String seataServiceGroup(String appName) {
return appName.concat(GROUP_NAME);
}
/**
* 动态获取nacos地址
*
* @param profile 环境变量
* @return addr
*/
static String nacosAddr(String profile) {
return switch (profile) {
case (AppConstant.PROD_CODE) -> NACOS_PROD_ADDR;
case (AppConstant.TEST_CODE) -> NACOS_TEST_ADDR;
default -> NACOS_DEV_ADDR;
};
}
/**
* 动态获取sentinel地址
*
* @param profile 环境变量
* @return addr
*/
static String sentinelAddr(String profile) {
return switch (profile) {
case (AppConstant.PROD_CODE) -> SENTINEL_PROD_ADDR;
case (AppConstant.TEST_CODE) -> SENTINEL_TEST_ADDR;
default -> SENTINEL_DEV_ADDR;
};
}
/**
* 动态获取seata地址
*
* @param profile 环境变量
* @return addr
*/
static String seataAddr(String profile) {
return switch (profile) {
case (AppConstant.PROD_CODE) -> SEATA_PROD_ADDR;
case (AppConstant.TEST_CODE) -> SEATA_TEST_ADDR;
default -> SEATA_DEV_ADDR;
};
}
/**
* 动态获取zipkin地址
*
* @param profile 环境变量
* @return addr
*/
static String zipkinAddr(String profile) {
return switch (profile) {
case (AppConstant.PROD_CODE) -> ZIPKIN_PROD_ADDR;
case (AppConstant.TEST_CODE) -> ZIPKIN_TEST_ADDR;
default -> ZIPKIN_DEV_ADDR;
};
}
/**
* 动态获取elk地址
*
* @param profile 环境变量
* @return addr
*/
static String elkAddr(String profile) {
return switch (profile) {
case (AppConstant.PROD_CODE) -> ELK_PROD_ADDR;
case (AppConstant.TEST_CODE) -> ELK_TEST_ADDR;
default -> ELK_DEV_ADDR;
};
}
}

View File

@@ -0,0 +1,99 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.common.constant;
import java.util.Arrays;
import java.util.List;
/**
* 系统参数常量
* 统一管理所有通过 ParamCache 读取的参数key及其默认值
*
* @author BladeX
*/
public interface ParamConstant {
// ==================== 账号相关 ====================
/**
* 是否开启注册参数key
*/
String REGISTER_USER_VALUE = "account.registerUser";
/**
* 账号默认密码参数key
*/
String DEFAULT_PARAM_PASSWORD = "account.initPassword";
/**
* 账号默认密码
*/
String DEFAULT_PASSWORD = "123456";
/**
* 账号锁定错误次数参数key
*/
String FAIL_COUNT_VALUE = "account.failCount";
/**
* 账号锁定默认错误次数
*/
Integer FAIL_COUNT = 5;
/**
* 账号锁定时长参数key单位分钟
*/
String LOCK_DURATION_VALUE = "account.lockDuration";
/**
* 账号锁定默认时长(单位:分钟)
*/
Integer LOCK_DURATION = 30;
// ==================== 租户相关 ====================
/**
* 租户默认密码参数key
*/
String TENANT_PASSWORD_KEY = "tenant.default.password";
/**
* 租户默认密码
*/
String TENANT_DEFAULT_PASSWORD = "123456";
/**
* 租户默认账号额度参数key
*/
String TENANT_ACCOUNT_NUMBER_KEY = "tenant.default.accountNumber";
/**
* 租户默认账号额度
*/
Integer TENANT_DEFAULT_ACCOUNT_NUMBER = -1;
/**
* 租户默认菜单集合参数key
*/
String TENANT_MENU_CODE_KEY = "tenant.default.menuCode";
/**
* 租户默认菜单集合
*/
List<String> TENANT_MENU_CODES = Arrays.asList(
"desk", "flow", "work", "monitor", "resource", "role", "user", "dept", "dictbiz", "topmenu"
);
}

View File

@@ -0,0 +1,40 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.common.constant;
/**
* 租户常量
*
* @author Chill
*/
public interface TenantConstant {
/**
* 租户授权码默认16位密钥
*/
String DES_KEY = "0000000000000000";
}

View File

@@ -0,0 +1,77 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.common.launch;
import org.springblade.common.constant.LauncherConstant;
import org.springblade.core.auto.service.AutoService;
import org.springblade.core.launch.service.LauncherService;
import org.springblade.core.launch.utils.PropsUtil;
import org.springframework.boot.builder.SpringApplicationBuilder;
import java.util.Properties;
/**
* 启动参数拓展
*
* @author smallchil
*/
@AutoService(LauncherService.class)
public class LauncherServiceImpl implements LauncherService {
@Override
public void launcher(SpringApplicationBuilder builder, String appName, String profile, boolean isLocalDev) {
Properties props = System.getProperties();
// nacos注册中心配置
PropsUtil.setProperty(props, "spring.cloud.nacos.discovery.username", LauncherConstant.NACOS_USERNAME);
PropsUtil.setProperty(props, "spring.cloud.nacos.discovery.password", LauncherConstant.NACOS_PASSWORD);
PropsUtil.setProperty(props, "spring.cloud.nacos.discovery.server-addr", LauncherConstant.nacosAddr(profile));
// nacos配置中心配置
PropsUtil.setProperty(props, "spring.cloud.nacos.config.username", LauncherConstant.NACOS_USERNAME);
PropsUtil.setProperty(props, "spring.cloud.nacos.config.password", LauncherConstant.NACOS_PASSWORD);
PropsUtil.setProperty(props, "spring.cloud.nacos.config.server-addr", LauncherConstant.nacosAddr(profile));
// sentinel配置
PropsUtil.setProperty(props, "spring.cloud.sentinel.transport.dashboard", LauncherConstant.sentinelAddr(profile));
// 多数据源配置
PropsUtil.setProperty(props, "spring.datasource.dynamic.enabled", "false");
// 开启elk日志
// PropsUtil.setProperty(props, "blade.log.elk.destination", LauncherConstant.elkAddr(profile));
// seata注册地址
// PropsUtil.setProperty(props, "seata.service.grouplist.default", LauncherConstant.seataAddr(profile));
// seata注册group格式
// PropsUtil.setProperty(props, "seata.tx-service-group", LauncherConstant.seataServiceGroup(appName));
// seata配置服务group
// PropsUtil.setProperty(props, "seata.service.vgroup-mapping.".concat(LauncherConstant.seataServiceGroup(appName)), LauncherConstant.DEFAULT_MODE);
// seata注册模式配置
// PropsUtil.setProperty(props, "seata.registry.type", LauncherConstant.NACOS_MODE);
// PropsUtil.setProperty(props, "seata.registry.nacos.server-addr", LauncherConstant.nacosAddr(profile));
// PropsUtil.setProperty(props, "seata.config.type", LauncherConstant.NACOS_MODE);
// PropsUtil.setProperty(props, "seata.config.nacos.server-addr", LauncherConstant.nacosAddr(profile));
}
}

View File

@@ -0,0 +1,35 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.common.utils;
/**
* 通用工具类
*
* @author Chill
*/
public class CommonUtil {
}

View File

@@ -0,0 +1,8 @@
${AnsiColor.BLUE} ______ _ _ ___ ___
${AnsiColor.BLUE} | ___ \| | | | \ \ / /
${AnsiColor.BLUE} | |_/ /| | __ _ __| | ___ \ V /
${AnsiColor.BLUE} | ___ \| | / _` | / _` | / _ \ > <
${AnsiColor.BLUE} | |_/ /| || (_| || (_| || __/ / . \
${AnsiColor.BLUE} \____/ |_| \__,_| \__,_| \___|/__/ \__\
${AnsiColor.BLUE}:: BladeX ${blade.service.version} :: ${spring.application.name}:${AnsiColor.RED}${blade.env}${AnsiColor.BLUE} :: Running SpringBoot ${spring-boot.version} :: ${AnsiColor.BRIGHT_BLACK}

View File

@@ -0,0 +1,24 @@
# 验证错误
error.validation.default=参数校验失败。
user.name.required=请输入姓名。
user.name.size=姓名长度需在{min}到{max}个字符之间。
user.email.invalid=请输入有效的邮箱地址。
user.description.size=描述不能超过{max}个字符。
# 业务错误
error.biz.denied=不允许的操作:{0}。
error.auth.unauthorized=请先登录。
error.permission.denied=您没有访问{0}的权限。
error.resource.notfound={0} ID {1} 不存在。
error.user.delete.admin=不能删除管理员用户。
error.unknown=未知错误类型:{0}
# 服务器错误
error.server.internal=服务器内部错误,请稍后重试。
error.server.busy=服务器繁忙,请稍后重试。
error.server.maintenance=系统维护中。
# 数据错误
error.data.duplicate=数据已存在。
error.data.invalid=数据格式无效。
error.data.notfound=数据不存在。

View File

@@ -0,0 +1,24 @@
# Validierungsfehler
error.validation.default=Parametervalidierung fehlgeschlagen.
user.name.required=Bitte geben Sie einen Namen ein.
user.name.size=Der Name muss zwischen {min} und {max} Zeichen lang sein.
user.email.invalid=Bitte geben Sie eine gültige E-Mail-Adresse ein.
user.description.size=Die Beschreibung darf {max} Zeichen nicht überschreiten.
# Geschäftsfehler
error.biz.denied=Operation nicht erlaubt: {0}.
error.auth.unauthorized=Bitte melden Sie sich zuerst an.
error.permission.denied=Sie haben keine Berechtigung für den Zugriff auf {0}.
error.resource.notfound={0} mit ID {1} existiert nicht.
error.user.delete.admin=Administrator-Benutzer kann nicht gelöscht werden.
error.unknown=Unbekannter Fehlertyp: {0}
# Serverfehler
error.server.internal=Interner Serverfehler. Bitte versuchen Sie es später erneut.
error.server.busy=Server ist ausgelastet. Bitte versuchen Sie es später erneut.
error.server.maintenance=System wird gewartet.
# Datenfehler
error.data.duplicate=Daten existieren bereits.
error.data.invalid=Ungültiges Datenformat.
error.data.notfound=Daten nicht gefunden.

View File

@@ -0,0 +1,24 @@
# Validation Errors
error.validation.default=Parameter validation failed.
user.name.required=Please enter a name.
user.name.size=Name must be between {min} and {max} characters.
user.email.invalid=Please enter a valid email address.
user.description.size=Description cannot exceed {max} characters.
# Business Errors
error.biz.denied=Operation not permitted: {0}.
error.auth.unauthorized=Please log in first.
error.permission.denied=You do not have permission to access {0}.
error.resource.notfound={0} with ID {1} does not exist.
error.user.delete.admin=Cannot delete administrator user.
error.unknown=Unknown error type: {0}
# Server Errors
error.server.internal=Internal server error. Please try again later.
error.server.busy=Server is busy. Please try again later.
error.server.maintenance=System is under maintenance.
# Data Errors
error.data.duplicate=Data already exists.
error.data.invalid=Invalid data format.
error.data.notfound=Data not found.

View File

@@ -0,0 +1,24 @@
# Validation errors
error.validation.default=Validation failed.
user.name.required=Name is required.
user.name.size=Name length must be between {min} and {max} characters.
user.email.invalid=Please provide a valid email address.
user.description.size=Description cannot exceed {max} characters.
# Business errors
error.biz.denied=Operation {0} is not allowed.
error.auth.unauthorized=Authentication required.
error.permission.denied=You don't have permission to access {0}.
error.resource.notfound={0} with ID {1} not found.
error.user.delete.admin=Cannot delete admin user.
error.unknown=Unknown error type: {0}
# Server errors
error.server.internal=Internal server error. Please try again later.
error.server.busy=Server is busy. Please try again later.
error.server.maintenance=System is under maintenance.
# Data errors
error.data.duplicate=Duplicate data found.
error.data.invalid=Invalid data format.
error.data.notfound=Data not found.

View File

@@ -0,0 +1,24 @@
# Erreurs de validation
error.validation.default=Échec de la validation des paramètres.
user.name.required=Veuillez saisir un nom.
user.name.size=Le nom doit contenir entre {min} et {max} caractères.
user.email.invalid=Veuillez saisir une adresse e-mail valide.
user.description.size=La description ne peut pas dépasser {max} caractères.
# Erreurs métier
error.biz.denied=Opération non autorisée : {0}.
error.auth.unauthorized=Veuillez vous connecter d'abord.
error.permission.denied=Vous n'avez pas l'autorisation d'accéder à {0}.
error.resource.notfound={0} avec l'ID {1} n'existe pas.
error.user.delete.admin=Impossible de supprimer l'utilisateur administrateur.
error.unknown=Type d'erreur inconnu : {0}
# Erreurs serveur
error.server.internal=Erreur interne du serveur. Veuillez réessayer plus tard.
error.server.busy=Le serveur est occupé. Veuillez réessayer plus tard.
error.server.maintenance=Système en maintenance.
# Erreurs de données
error.data.duplicate=Les données existent déjà.
error.data.invalid=Format de données invalide.
error.data.notfound=Données introuvables.

View File

@@ -0,0 +1,24 @@
# Errori di validazione
error.validation.default=Validazione dei parametri fallita.
user.name.required=Inserisci il nome.
user.name.size=La lunghezza del nome deve essere tra {min} e {max} caratteri.
user.email.invalid=Inserisci un indirizzo email valido.
user.description.size=La descrizione non può superare {max} caratteri.
# Errori di business
error.biz.denied=Operazione non consentita: {0}.
error.auth.unauthorized=Effettua il login prima di procedere.
error.permission.denied=Non hai i permessi per accedere a {0}.
error.resource.notfound={0} con ID {1} non esiste.
error.user.delete.admin=Non è possibile eliminare l'utente amministratore.
error.unknown=Tipo di errore sconosciuto: {0}
# Errori del server
error.server.internal=Errore interno del server, riprova più tardi.
error.server.busy=Il server è occupato, riprova più tardi.
error.server.maintenance=Sistema in manutenzione.
# Errori di dati
error.data.duplicate=I dati esistono già.
error.data.invalid=Formato dati non valido.
error.data.notfound=I dati non esistono.

View File

@@ -0,0 +1,24 @@
# Ошибки валидации
error.validation.default=Ошибка проверки параметров.
user.name.required=Пожалуйста, введите имя.
user.name.size=Имя должно быть от {min} до {max} символов.
user.email.invalid=Пожалуйста, введите действительный адрес электронной почты.
user.description.size=Описание не может превышать {max} символов.
# Бизнес-ошибки
error.biz.denied=Операция не разрешена: {0}.
error.auth.unauthorized=Пожалуйста, сначала войдите в систему.
error.permission.denied=У вас нет разрешения на доступ к {0}.
error.resource.notfound={0} с ID {1} не существует.
error.user.delete.admin=Невозможно удалить пользователя-администратора.
error.unknown=Неизвестный тип ошибки: {0}
# Ошибки сервера
error.server.internal=Внутренняя ошибка сервера. Пожалуйста, попробуйте позже.
error.server.busy=Сервер занят. Пожалуйста, попробуйте позже.
error.server.maintenance=Система на обслуживании.
# Ошибки данных
error.data.duplicate=Данные уже существуют.
error.data.invalid=Недопустимый формат данных.
error.data.notfound=Данные не найдены.

View File

@@ -0,0 +1,24 @@
# 验证错误
error.validation.default=参数校验失败。
user.name.required=请输入姓名。
user.name.size=姓名长度需在{min}到{max}个字符之间。
user.email.invalid=请输入有效的邮箱地址。
user.description.size=描述不能超过{max}个字符。
# 业务错误
error.biz.denied=不允许的操作:{0}。
error.auth.unauthorized=请先登录。
error.permission.denied=您没有访问{0}的权限。
error.resource.notfound={0} ID {1} 不存在。
error.user.delete.admin=不能删除管理员用户。
error.unknown=未知错误类型:{0}
# 服务器错误
error.server.internal=服务器内部错误,请稍后重试。
error.server.busy=服务器繁忙,请稍后重试。
error.server.maintenance=系统维护中。
# 数据错误
error.data.duplicate=数据已存在。
error.data.invalid=数据格式无效。
error.data.notfound=数据不存在。

View File

@@ -0,0 +1,24 @@
# 驗證錯誤
error.validation.default=參數校驗失敗。
user.name.required=請輸入姓名。
user.name.size=姓名長度需在{min}到{max}個字元之間。
user.email.invalid=請輸入有效的電子郵件地址。
user.description.size=描述不能超過{max}個字元。
# 業務錯誤
error.biz.denied=不允許的操作:{0}。
error.auth.unauthorized=請先登入。
error.permission.denied=您沒有存取{0}的權限。
error.resource.notfound={0} ID {1} 不存在。
error.user.delete.admin=不能刪除管理員使用者。
error.unknown=未知錯誤類型:{0}
# 伺服器錯誤
error.server.internal=伺服器內部錯誤,請稍後重試。
error.server.busy=伺服器忙碌中,請稍後重試。
error.server.maintenance=系統維護中。
# 資料錯誤
error.data.duplicate=資料已存在。
error.data.invalid=資料格式無效。
error.data.notfound=資料不存在。

View File

@@ -0,0 +1,14 @@
# 问候消息
greeting.message=你好,{0}
greeting.welcome=欢迎使用我们的应用
# 用户消息
user.create.success=用户 {0} 创建成功。
user.update.success=ID为 {1} 的用户 {0} 更新成功。
user.delete.success=ID为 {0} 的用户删除成功。
user.notfound=用户未找到
# 成功消息
operation.success=操作成功完成
save.success=数据保存成功
delete.success=数据删除成功

View File

@@ -0,0 +1,14 @@
# Begrüßungsnachrichten
greeting.message=Hallo, {0}!
greeting.welcome=Willkommen in unserer Anwendung
# Benutzernachrichten
user.create.success=Benutzer {0} wurde erfolgreich erstellt.
user.update.success=Benutzer {0} mit ID {1} wurde erfolgreich aktualisiert.
user.delete.success=Benutzer mit ID {0} wurde erfolgreich gelöscht.
user.notfound=Benutzer nicht gefunden
# Erfolgsmeldungen
operation.success=Vorgang erfolgreich abgeschlossen
save.success=Daten erfolgreich gespeichert
delete.success=Daten erfolgreich gelöscht

View File

@@ -0,0 +1,14 @@
# Greeting Messages
greeting.message=Hello, {0}!
greeting.welcome=Welcome to our application
# User Messages
user.create.success=User {0} has been successfully created.
user.update.success=User {0} with ID {1} has been successfully updated.
user.delete.success=User with ID {0} has been successfully deleted.
user.notfound=User not found
# Success Messages
operation.success=Operation completed successfully
save.success=Data saved successfully
delete.success=Data deleted successfully

View File

@@ -0,0 +1,14 @@
# Greeting messages
greeting.message=Hello, {0}!
greeting.welcome=Welcome to our application
# User messages
user.create.success=User {0} created successfully.
user.update.success=User {0} with ID {1} updated successfully.
user.delete.success=User with ID {0} deleted successfully.
user.notfound=User not found
# Success messages
operation.success=Operation completed successfully
save.success=Data saved successfully
delete.success=Data deleted successfully

View File

@@ -0,0 +1,14 @@
# Messages de salutation
greeting.message=Bonjour, {0} !
greeting.welcome=Bienvenue dans notre application
# Messages utilisateur
user.create.success=L'utilisateur {0} a été créé avec succès.
user.update.success=L'utilisateur {0} avec l'ID {1} a été mis à jour avec succès.
user.delete.success=L'utilisateur avec l'ID {0} a été supprimé avec succès.
user.notfound=Utilisateur introuvable
# Messages de succès
operation.success=Opération terminée avec succès
save.success=Données enregistrées avec succès
delete.success=Données supprimées avec succès

View File

@@ -0,0 +1,14 @@
# Messaggi di saluto
greeting.message=Ciao, {0}!
greeting.welcome=Benvenuto nella nostra applicazione
# Messaggi utente
user.create.success=L'utente {0} è stato creato con successo.
user.update.success=L'utente {0} con ID {1} è stato aggiornato con successo.
user.delete.success=L'utente con ID {0} è stato eliminato con successo.
user.notfound=Utente non trovato
# Messaggi di successo
operation.success=Operazione completata con successo
save.success=Dati salvati con successo
delete.success=Dati eliminati con successo

View File

@@ -0,0 +1,14 @@
# Приветственные сообщения
greeting.message=Здравствуйте, {0}!
greeting.welcome=Добро пожаловать в наше приложение
# Сообщения пользователя
user.create.success=Пользователь {0} успешно создан.
user.update.success=Пользователь {0} с ID {1} успешно обновлен.
user.delete.success=Пользователь с ID {0} успешно удален.
user.notfound=Пользователь не найден
# Сообщения об успехе
operation.success=Операция успешно завершена
save.success=Данные успешно сохранены
delete.success=Данные успешно удалены

View File

@@ -0,0 +1,14 @@
# 问候消息
greeting.message=你好,{0}
greeting.welcome=欢迎使用我们的应用
# 用户消息
user.create.success=用户 {0} 创建成功。
user.update.success=ID为 {1} 的用户 {0} 更新成功。
user.delete.success=ID为 {0} 的用户删除成功。
user.notfound=用户未找到
# 成功消息
operation.success=操作成功完成
save.success=数据保存成功
delete.success=数据删除成功

View File

@@ -0,0 +1,14 @@
# 問候訊息
greeting.message=你好,{0}
greeting.welcome=歡迎使用我們的應用
# 使用者訊息
user.create.success=使用者 {0} 建立成功。
user.update.success=ID為 {1} 的使用者 {0} 更新成功。
user.delete.success=ID為 {0} 的使用者刪除成功。
user.notfound=使用者未找到
# 成功訊息
operation.success=操作成功完成
save.success=資料儲存成功
delete.success=資料刪除成功

15
blade-gateway/Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM bladex/alpine-java:openjdk17_cn_slim
LABEL maintainer="bladejava@qq.com"
RUN mkdir -p /blade/gateway
WORKDIR /blade/gateway
EXPOSE 80
COPY ./target/blade-gateway.jar ./app.jar
ENTRYPOINT ["java", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
CMD ["--spring.profiles.active=test"]

103
blade-gateway/pom.xml Normal file
View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>BladeX</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-gateway</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-launch</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-common</artifactId>
<exclusions>
<exclusion>
<groupId>org.springblade</groupId>
<artifactId>blade-core-launch</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-metrics</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!--Spring-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!-- 开启knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-gateway-spring-boot-starter</artifactId>
</dependency>
<!-- 开启swagger-ui -->
<!--<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
</dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<skip>${docker.fabric.skip}</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,48 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 项目启动
*
* @author Chill
*/
@EnableScheduling
@EnableDiscoveryClient
@SpringBootApplication
public class GateWayApplication {
public static void main(String[] args) {
BladeApplication.run(AppConstant.APPLICATION_GATEWAY_NAME, GateWayApplication.class, args);
}
}

View File

@@ -0,0 +1,53 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springblade.gateway.handler.ErrorExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 异常处理配置类
*
* @author Chill
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(ErrorWebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class})
public class ErrorHandlerConfiguration {
@Bean
public ErrorExceptionHandler globalExceptionHandler(ObjectMapper objectMapper) {
return new ErrorExceptionHandler(objectMapper);
}
}

View File

@@ -0,0 +1,50 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.config;
import com.github.xiaoymin.knife4j.spring.gateway.Knife4jGatewayProperties;
import com.github.xiaoymin.knife4j.spring.gateway.discover.router.DiscoverClientRouteServiceConvert;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* knife4j自动聚合配置
*
* @author BladeX
*/
@Configuration(proxyBeanMethods = false)
public class ReactiveDiscoveryConfiguration {
@Bean
@ConditionalOnProperty(name = {"spring.cloud.gateway.server.webflux.discovery.locator.enabled"})
public DiscoverClientRouteServiceConvert discoverClientRouteServiceConvert(DiscoveryClientRouteDefinitionLocator discoveryClient,
Knife4jGatewayProperties knife4jGatewayProperties) {
return new DiscoverClientRouteServiceConvert(discoveryClient, knife4jGatewayProperties);
}
}

View File

@@ -0,0 +1,57 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.config;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.gateway.filter.GatewayFilter;
import org.springblade.gateway.props.AuthProperties;
import org.springblade.gateway.props.RequestProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.WebFilter;
/**
* 路由配置信息
*
* @author Chill
*/
@Slf4j
@Configuration(proxyBeanMethods = false)
@AllArgsConstructor
@EnableConfigurationProperties({AuthProperties.class, RequestProperties.class})
public class RouterFunctionConfiguration {
/**
* 全局配置
*/
@Bean
public WebFilter gatewayFilter(RequestProperties requestProperties) {
return new GatewayFilter(requestProperties);
}
}

View File

@@ -0,0 +1,117 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.dynamic;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 动态路由业务类
*
* @author Chill
*/
@Service
public class DynamicRouteService implements ApplicationEventPublisherAware {
private final RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter) {
this.routeDefinitionWriter = routeDefinitionWriter;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* 增加路由
*/
public String save(RouteDefinition definition) {
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "save success";
} catch (Exception e) {
e.printStackTrace();
return "save failure";
}
}
/**
* 更新路由
*/
public String update(RouteDefinition definition) {
try {
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "update success";
} catch (Exception e) {
e.printStackTrace();
return "update failure";
}
}
/**
* 更新路由
*/
public String updateList(List<RouteDefinition> routeDefinitions) {
try {
if (!routeDefinitions.isEmpty()) {
routeDefinitions.forEach(this::update);
}
return "update done";
} catch (Exception e) {
e.printStackTrace();
return "update failure";
}
}
/**
* 删除路由
*/
public String delete(String id) {
try {
this.routeDefinitionWriter.delete(Mono.just(id));
return "delete success";
} catch (Exception e) {
e.printStackTrace();
return "delete failure";
}
}
}

View File

@@ -0,0 +1,109 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.dynamic;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.launch.constant.NacosConstant;
import org.springblade.core.launch.props.BladeProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 动态路由监听器
*
* @author Chill
*/
@Order
@Slf4j
@Component
@RefreshScope
public class DynamicRouteServiceListener {
private final DynamicRouteService dynamicRouteService;
private final NacosDiscoveryProperties nacosDiscoveryProperties;
private final NacosConfigProperties nacosConfigProperties;
private final BladeProperties bladeProperties;
public DynamicRouteServiceListener(DynamicRouteService dynamicRouteService, NacosDiscoveryProperties nacosDiscoveryProperties, NacosConfigProperties nacosConfigProperties, BladeProperties bladeProperties) {
this.dynamicRouteService = dynamicRouteService;
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.nacosConfigProperties = nacosConfigProperties;
this.bladeProperties = bladeProperties;
dynamicRouteServiceListener();
}
/**
* 监听Nacos下发的动态路由配置
*/
private void dynamicRouteServiceListener() {
try {
String dataId = bladeProperties.getName() + "-" + bladeProperties.getEnv() + "." + NacosConstant.NACOS_CONFIG_JSON_FORMAT;
String group = nacosConfigProperties.getGroup();
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDiscoveryProperties.getNamespace());
properties.setProperty(PropertyKeyConst.USERNAME, nacosDiscoveryProperties.getUsername());
properties.setProperty(PropertyKeyConst.PASSWORD, nacosDiscoveryProperties.getPassword());
ConfigService configService = NacosFactory.createConfigService(properties);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
if (!routeDefinitions.isEmpty()) {
dynamicRouteService.updateList(routeDefinitions);
}
}
@Override
public Executor getExecutor() {
return null;
}
});
String configInfo = configService.getConfig(dataId, group, 5000);
if (configInfo != null) {
List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
dynamicRouteService.updateList(routeDefinitions);
}
} catch (NacosException ignored) {
ignored.printStackTrace();
}
}
}

View File

@@ -0,0 +1,50 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.dynamic;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 过滤器定义模型
*
* @author Chill
*/
@Data
public class GatewayFilter {
/**
* 过滤器对应的Name
*/
private String name;
/**
* 对应的路由规则
*/
private Map<String, String> args = new LinkedHashMap<>();
}

View File

@@ -0,0 +1,50 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.dynamic;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 路由断言定义模型
*
* @author Chill
*/
@Data
public class GatewayPredicate {
/**
* 断言对应的Name
*/
private String name;
/**
* 配置的断言规则
*/
private Map<String, String> args = new LinkedHashMap<>();
}

View File

@@ -0,0 +1,66 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.dynamic;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* Gateway的路由定义模型
*
* @author Chill
*/
@Data
public class GatewayRoute {
/**
* 路由的id
*/
private String id;
/**
* 路由断言集合配置
*/
private List<GatewayPredicate> predicates = new ArrayList<>();
/**
* 路由过滤器集合配置
*/
private List<GatewayFilter> filters = new ArrayList<>();
/**
* 路由规则转发的目标uri
*/
private String uri;
/**
* 路由执行的顺序
*/
private int order = 0;
}

View File

@@ -0,0 +1,156 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.filter;
import com.alibaba.nacos.common.utils.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.jwt.JwtCrypto;
import org.springblade.core.jwt.JwtUtil;
import org.springblade.core.jwt.props.JwtProperties;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.gateway.props.AuthProperties;
import org.springblade.gateway.provider.AuthProvider;
import org.springblade.gateway.provider.KeyProvider;
import org.springblade.gateway.provider.RequestProvider;
import org.springblade.gateway.provider.ResponseProvider;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import static org.springblade.core.jwt.JwtCrypto.BLADE_TOKEN_CRYPTO_KEY;
/**
* 鉴权认证
*
* @author Chill
*/
@Slf4j
@Component
@AllArgsConstructor
public class AuthFilter implements GlobalFilter, Ordered {
private static final String MSG_TOKEN_MISSING = "缺失令牌,鉴权失败";
private static final String MSG_REQUEST_UNAUTHORIZED = "请求未授权";
private static final String MSG_TOKEN_EXPIRED = "令牌已失效";
private final AuthProperties authProperties;
private final ObjectMapper objectMapper;
private final JwtProperties jwtProperties;
private final BladeProperties bladeProperties;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//校验 Token 放行
String originalRequestUrl = RequestProvider.getOriginalRequestUrl(exchange);
String path = exchange.getRequest().getURI().getPath();
if (isSkip(path) || isSkip(originalRequestUrl)) {
return chain.filter(exchange);
}
//校验 Token 合法性
ServerHttpResponse resp = exchange.getResponse();
String headerToken = exchange.getRequest().getHeaders().getFirst(AuthProvider.AUTH_KEY);
String paramToken = exchange.getRequest().getQueryParams().getFirst(AuthProvider.AUTH_KEY);
if (StringUtils.isBlank(headerToken) && StringUtils.isBlank(paramToken)) {
return unAuth(resp, MSG_TOKEN_MISSING);
}
String auth = StringUtils.isBlank(headerToken) ? paramToken : headerToken;
// 判断 auth 是否为空
if (StringUtils.isBlank(auth)) {
return unAuth(resp, MSG_REQUEST_UNAUTHORIZED);
}
// 校验 超级密钥 合法性,剩余属性交由微服务内部自行鉴权
if (KeyProvider.isApiKey(auth, bladeProperties)) {
return chain.filter(exchange);
}
String token = JwtUtil.getToken(auth);
// 判断 token 是否为空
if (StringUtils.isBlank(token)) {
return unAuth(resp, MSG_REQUEST_UNAUTHORIZED);
}
// 校验 加密Token 合法性
if (JwtUtil.isCrypto(auth)) {
token = JwtCrypto.decryptToString(token, bladeProperties.getEnvironment().getProperty(BLADE_TOKEN_CRYPTO_KEY));
}
Claims claims = JwtUtil.parseJWT(token);
if (token == null || claims == null) {
return unAuth(resp, MSG_REQUEST_UNAUTHORIZED);
}
// 判断 Token 状态
if (jwtProperties.getState()) {
String tenantId = String.valueOf(claims.get(TokenConstant.TENANT_ID));
String clientId = String.valueOf(claims.get(TokenConstant.CLIENT_ID));
String userId = String.valueOf(claims.get(TokenConstant.USER_ID));
String accessToken = JwtUtil.getAccessToken(tenantId, clientId, userId, token);
if (!token.equalsIgnoreCase(accessToken)) {
return unAuth(resp, MSG_TOKEN_EXPIRED);
}
}
return chain.filter(exchange);
}
private boolean isSkip(String path) {
return AuthProvider.getDefaultSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path))
|| authProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path))
|| authProperties.getAuth().stream().anyMatch(auth -> antPathMatcher.match(auth.getPattern(), path))
|| authProperties.getBasic().stream().anyMatch(basic -> antPathMatcher.match(basic.getPattern(), path))
|| authProperties.getSign().stream().anyMatch(sign -> antPathMatcher.match(sign.getPattern(), path));
}
private Mono<Void> unAuth(ServerHttpResponse resp, String msg) {
resp.setStatusCode(HttpStatus.UNAUTHORIZED);
resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String result = "";
try {
result = objectMapper.writeValueAsString(ResponseProvider.unAuth(msg));
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
}
DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Flux.just(buffer));
}
@Override
public int getOrder() {
return -100;
}
}

View File

@@ -0,0 +1,171 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.filter;
import lombok.RequiredArgsConstructor;
import org.springblade.gateway.props.RequestProperties;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Objects;
/**
* 全局拦截器
*
* @author Chill
*/
@RequiredArgsConstructor
public class GatewayFilter implements WebFilter, Ordered {
/**
* 请求配置
*/
private final RequestProperties requestProperties;
/**
* 路径匹配
*/
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
/**
* 默认拦截地址
*/
private final List<String> defaultBlockUrl = List.of("/**/actuator/**", "/health/**");
/**
* 默认白名单
*/
private final List<String> defaultWhiteList = List.of("127.0.0.1", "172.30.*.*", "192.168.*.*", "10.*.*.*", "0:0:0:0:0:0:0:1");
/**
* 默认提示信息
*/
private final static String DEFAULT_MESSAGE = "当前请求被拒绝,请联系管理员!";
/**
* 这里为支持的请求头如果有自定义的header字段请自己添加
*/
private static final String ALLOWED_HEADERS = "X-Requested-With, Tenant-Id, Blade-Auth, Content-Type, Authorization, credential, X-XSRF-TOKEN, token, username, client, knfie4j-gateway-request, knife4j-gateway-code, request-origion";
private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD";
private static final String ALLOWED_ORIGIN = "*";
private static final String ALLOWED_EXPOSE = "*";
private static final String MAX_AGE = "18000L";
@NonNull
@Override
public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 处理跨域请求
if (CorsUtils.isCorsRequest(request)) {
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE);
headers.add("Access-Control-Max-Age", MAX_AGE);
headers.add("Access-Control-Allow-Credentials", "true");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
// 处理黑白名单与拦截请求
if (requestProperties.getEnabled()) {
String path = request.getPath().value();
String ip = Objects.requireNonNull(request.getRemoteAddress()).getHostString();
if (isRequestBlock(path, ip)) {
throw new RuntimeException(DEFAULT_MESSAGE);
}
}
return chain.filter(exchange);
}
/**
* 是否白名单
*
* @param ip ip地址
* @return boolean
*/
private boolean isWhiteList(String ip) {
List<String> whiteList = requestProperties.getWhiteList();
String[] defaultWhiteIps = defaultWhiteList.toArray(new String[0]);
String[] whiteIps = whiteList.toArray(new String[0]);
return PatternMatchUtils.simpleMatch(defaultWhiteIps, ip) || PatternMatchUtils.simpleMatch(whiteIps, ip);
}
/**
* 是否黑名单
*
* @param ip ip地址
* @return boolean
*/
private boolean isBlackList(String ip) {
List<String> blackList = requestProperties.getBlackList();
String[] blackIps = blackList.toArray(new String[0]);
return PatternMatchUtils.simpleMatch(blackIps, ip);
}
/**
* 是否禁用请求访问
*
* @param path 请求路径
* @return boolean
*/
private boolean isRequestBlock(String path) {
List<String> blockUrl = requestProperties.getBlockUrl();
return defaultBlockUrl.stream().anyMatch(pattern -> antPathMatcher.match(pattern, path)) ||
blockUrl.stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
}
/**
* 是否拦截请求
*
* @param path 请求路径
* @param ip ip地址
* @return boolean
*/
private boolean isRequestBlock(String path, String ip) {
return (isRequestBlock(path) && !isWhiteList(ip)) || isBlackList(ip);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@@ -0,0 +1,63 @@
package org.springblade.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.stream.Collectors;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
/**
* <p>
* 全局拦截器,作用所有的微服务
* <p>
* 1. 对请求头中参数进行处理 from 参数进行清洗
* 2. 重写StripPrefix = 1,支持全局
*
* @author lengleng
*/
@Component
public class RequestFilter implements GlobalFilter, Ordered {
/**
* Process the Web request and (optionally) delegate to the next
* {@code WebFilter} through the given {@link GatewayFilterChain}.
*
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 清洗请求头中from 参数
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> httpHeaders.remove("X"))
.build();
// 2. 重写StripPrefix
addOriginalRequestUrl(exchange, request.getURI());
String rawPath = request.getURI().getRawPath();
String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/"))
.skip(1L).collect(Collectors.joining("/"));
ServerHttpRequest newRequest = request.mutate()
.path(newPath)
.build();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
return chain.filter(exchange.mutate().request(newRequest.mutate().build()).build());
}
@Override
public int getOrder() {
return -1000;
}
}

View File

@@ -0,0 +1,121 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.gateway.filter;
import com.alibaba.nacos.common.utils.StringUtils;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.jwt.JwtUtil;
import org.springblade.gateway.provider.AuthProvider;
import org.springblade.gateway.provider.RequestProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
/**
* webflux 日志请求记录,方便开发调试。请求日志过滤器排序尽量低。
*
* <p>
* 注意:暂时不支持结构体打印,想实现,请看下面的链接。
* https://stackoverflow.com/questions/45240005/how-to-log-request-and-response-bodies-in-spring-webflux
* https://github.com/Silvmike/webflux-demo/blob/master/tests/src/test/java/ru/hardcoders/demo/webflux/web_handler/filters/logging
* </p>
*
* @author dream.lu
*/
@Slf4j
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
@ConditionalOnProperty(value = "blade.log.request.enabled", havingValue = "true", matchIfMissing = true)
public class RequestLogFilter implements GlobalFilter, Ordered {
private final WebEndpointProperties endpointProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 打印请求路径
String path = request.getPath().pathWithinApplication().value();
// 忽略 endpoint 请求
String endpointBasePath = endpointProperties.getBasePath();
if (StringUtils.isNotBlank(endpointBasePath) && path.startsWith(endpointBasePath)) {
return chain.filter(exchange);
}
String requestUrl = RequestProvider.getOriginalRequestUrl(exchange);
// 构建成一条长 日志,避免并发下日志错乱
StringBuilder beforeReqLog = new StringBuilder(300);
// 日志参数
List<Object> beforeReqArgs = new ArrayList<>();
beforeReqLog.append("\n\n================ Gateway Request Start ================\n");
// 打印路由
beforeReqLog.append("===> {}: {}\n");
// 参数
String requestMethod = request.getMethod().name();
beforeReqArgs.add(requestMethod);
beforeReqArgs.add(requestUrl);
// 打印请求头
HttpHeaders headers = request.getHeaders();
headers.forEach((headerName, headerValue) -> {
beforeReqLog.append("===Headers=== {}: {}\n");
beforeReqArgs.add(headerName);
if (AuthProvider.AUTH_KEY.toLowerCase().equals(headerName)) {
String value = headerValue.get(0);
String token = JwtUtil.getToken(value);
Claims claims = JwtUtil.parseJWT(token);
beforeReqArgs.add((claims == null) ? "" : claims.toString());
beforeReqLog.append("===Headers=== {}: {}\n");
beforeReqArgs.add(headerName.concat("-original"));
beforeReqArgs.add(headerValue.toArray());
} else {
beforeReqArgs.add(headerValue.toArray());
}
});
beforeReqLog.append("================ Gateway Request End =================\n");
// 打印执行时间
log.info(beforeReqLog.toString(), beforeReqArgs.toArray());
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}

View File

@@ -0,0 +1,108 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.gateway.filter;
import com.alibaba.nacos.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
/**
* webflux 相应日志,方便开发调试,注意排序要优先。
*
* @author dream.lu
*/
@Slf4j
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
@ConditionalOnProperty(value = "blade.log.request.enabled", havingValue = "true", matchIfMissing = true)
public class ResponseLogFilter implements GlobalFilter, Ordered {
private final WebEndpointProperties endpointProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 打印请求路径
String path = request.getPath().pathWithinApplication().value();
// 忽略 endpoint 请求
String endpointBasePath = endpointProperties.getBasePath();
if (StringUtils.isNotBlank(endpointBasePath) && path.startsWith(endpointBasePath)) {
return chain.filter(exchange);
}
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
MultiValueMap<String, String> queryParams = request.getQueryParams();
String requestUrl = UriComponentsBuilder.fromPath(path).queryParams(queryParams).build().toUriString();
// 构建成一条长 日志,避免并发下日志错乱
StringBuilder responseLog = new StringBuilder(300);
// 日志参数
List<Object> responseArgs = new ArrayList<>();
responseLog.append("\n\n================ Gateway Response Start ================\n");
ServerHttpResponse response = exchange.getResponse();
// 打印路由 200 get: /api/xxx/xxx
responseLog.append("<=== {} {}: {}\n");
// 参数
String requestMethod = request.getMethod().name();
responseArgs.add(response.getStatusCode().value());
responseArgs.add(requestMethod);
responseArgs.add(requestUrl);
// 打印请求头
HttpHeaders headers = response.getHeaders();
headers.forEach((headerName, headerValue) -> {
responseLog.append("===Headers=== {}: {}\n");
responseArgs.add(headerName);
responseArgs.add(headerValue.toArray());
});
responseLog.append("================ Gateway Response End =================\n");
// 打印执行时间
log.info(responseLog.toString(), responseArgs.toArray());
})
);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@@ -0,0 +1,101 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.handler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springblade.gateway.provider.ResponseProvider;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Map;
/**
* 异常处理
*
* @author Chill
*/
@Order(-1)
@RequiredArgsConstructor
public class ErrorExceptionHandler implements ErrorWebExceptionHandler {
private final ObjectMapper objectMapper;
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
if (response.isCommitted()) {
return Mono.error(ex);
}
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
if (ex instanceof ResponseStatusException) {
response.setStatusCode(((ResponseStatusException) ex).getStatusCode());
}
return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
try {
int status = 500;
if (response.getStatusCode() != null) {
status = response.getStatusCode().value();
}
Map<String, Object> result = ResponseProvider.response(status, this.buildMessage(request, ex));
return bufferFactory.wrap(objectMapper.writeValueAsBytes(result));
} catch (JsonProcessingException e) {
return bufferFactory.wrap(new byte[0]);
}
}));
}
/**
* 构建异常信息
*/
private String buildMessage(ServerHttpRequest request, Throwable ex) {
StringBuilder message = new StringBuilder("Failed to handle request [");
message.append(request.getMethod().name());
message.append(" ");
message.append(request.getURI());
message.append("]");
if (ex != null) {
message.append(": ");
message.append(ex.getMessage());
}
return message.toString();
}
}

View File

@@ -0,0 +1,68 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.props;
import lombok.Data;
import org.springblade.gateway.provider.AuthSecure;
import org.springblade.gateway.provider.BasicSecure;
import org.springblade.gateway.provider.SignSecure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import java.util.ArrayList;
import java.util.List;
/**
* 权限过滤
*
* @author Chill
*/
@Data
@RefreshScope
@ConfigurationProperties("blade.secure")
public class AuthProperties {
/**
* 放行API集合
*/
private final List<String> skipUrl = new ArrayList<>();
/**
* 自定义授权配置
*/
private final List<AuthSecure> auth = new ArrayList<>();
/**
* 基础认证配置
*/
private final List<BasicSecure> basic = new ArrayList<>();
/**
* 签名认证配置
*/
private final List<SignSecure> sign = new ArrayList<>();
}

View File

@@ -0,0 +1,68 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* Request配置类
*
* @author Chill
*/
@Data
@ConfigurationProperties("blade.request")
public class RequestProperties {
/**
* 开启自定义request
*/
private Boolean enabled = true;
/**
* 放行url
*/
private List<String> skipUrl = new ArrayList<>();
/**
* 禁用url
*/
private List<String> blockUrl = new ArrayList<>();
/**
* 白名单支持通配符例如10.20.0.8*、10.20.0.*
*/
private List<String> whiteList = new ArrayList<>();
/**
* 黑名单支持通配符例如10.20.0.8*、10.20.0.*
*/
private List<String> blackList = new ArrayList<>();
}

View File

@@ -0,0 +1,72 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.provider;
import org.springblade.core.launch.constant.TokenConstant;
import java.util.ArrayList;
import java.util.List;
/**
* 鉴权配置
*
* @author Chill
*/
public class AuthProvider {
public static final String AUTH_KEY = TokenConstant.AUTH_HEADER;
private static final List<String> DEFAULT_SKIP_URL = new ArrayList<>();
static {
DEFAULT_SKIP_URL.add("/example");
DEFAULT_SKIP_URL.add("/oauth/token/**");
DEFAULT_SKIP_URL.add("/oauth/behavior/**");
DEFAULT_SKIP_URL.add("/oauth/captcha/**");
DEFAULT_SKIP_URL.add("/oauth/sms/**");
DEFAULT_SKIP_URL.add("/oauth/clear-cache/**");
DEFAULT_SKIP_URL.add("/oauth/user-info");
DEFAULT_SKIP_URL.add("/oauth/render/**");
DEFAULT_SKIP_URL.add("/oauth/callback/**");
DEFAULT_SKIP_URL.add("/oauth/revoke/**");
DEFAULT_SKIP_URL.add("/oauth/refresh/**");
DEFAULT_SKIP_URL.add("/token/**");
DEFAULT_SKIP_URL.add("/actuator/**");
DEFAULT_SKIP_URL.add("/v3/api-docs/**");
DEFAULT_SKIP_URL.add("/tenant/info");
DEFAULT_SKIP_URL.add("/process/resource-view");
DEFAULT_SKIP_URL.add("/process/diagram-view");
DEFAULT_SKIP_URL.add("/manager/check-upload");
DEFAULT_SKIP_URL.add("/assets/**");
}
/**
* 默认无需鉴权的API
*/
public static List<String> getDefaultSkipUrl() {
return DEFAULT_SKIP_URL;
}
}

View File

@@ -0,0 +1,46 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.provider;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 自定义授权规则
*
* @author Chill
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AuthSecure {
/**
* 请求路径
*/
private String pattern;
}

View File

@@ -0,0 +1,46 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.provider;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 基础授权规则
*
* @author Chill
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BasicSecure {
/**
* 请求路径
*/
private String pattern;
}

View File

@@ -0,0 +1,303 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.provider;
import org.springblade.core.launch.props.BladeProperties;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
/**
* 超级密钥加解密工具类
*
* @author BladeX
*/
public class KeyProvider {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
/**
* API Key 前缀
*/
private static final String API_KEY_PREFIX = "ak-";
/**
* API Key 配置项
*/
private static final String BLADE_KEY_ENABLED = "blade.key.enabled";
/**
* API Key 配置项
*/
private static final String BLADE_KEY_CRYPTO_KEY = "blade.key.crypto-key";
/**
* 随机字符串因子
*/
private static final String RANDOM_FACTOR = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/**
* 安全随机数生成器
*/
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
/**
* PKCS7 块大小
*/
private static final int BLOCK_SIZE = 16;
/**
* Hex 编码字符
*/
private static final byte[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* 判断是否为合法 API Key
*
* @param auth 认证字符串
* @return 是否为 API Key
*/
public static boolean isApiKey(@Nullable String auth, BladeProperties bladeProperties) {
// 检查功能是否启用
String enabled = bladeProperties.getEnvironment().getProperty(BLADE_KEY_ENABLED);
if (!Boolean.parseBoolean(enabled)) {
return false;
}
// 检查前缀并解析
if (auth != null && auth.startsWith(API_KEY_PREFIX)) {
String cryptoKey = bladeProperties.getEnvironment().getProperty(BLADE_KEY_CRYPTO_KEY);
String parsed = parseKey(auth, cryptoKey);
return parsed != null;
}
return false;
}
/**
* 生成 API Key
*
* @param cryptoKey 加密密钥
* @return 带前缀的加密 Key
*/
public static String generateKey(String cryptoKey) {
return API_KEY_PREFIX + encryptToHex(randomUUID(), cryptoKey);
}
/**
* 解析 API Key
*
* @param key 带前缀的加密 Key
* @param cryptoKey 加密密钥
* @return 解密后的原始值
*/
@Nullable
public static String parseKey(@Nullable String key, String cryptoKey) {
if (isBlank(key) || !key.startsWith(API_KEY_PREFIX)) {
return null;
}
try {
String encryptedPart = key.substring(API_KEY_PREFIX.length());
return decryptFormHexToString(encryptedPart, cryptoKey);
} catch (Exception e) {
return null;
}
}
/**
* 加密并转换为十六进制字符串
*
* @param content 明文内容
* @param aesTextKey AES 密钥字符串
* @return 十六进制加密字符串
*/
public static String encryptToHex(String content, String aesTextKey) {
return hexEncode(encrypt(content.getBytes(DEFAULT_CHARSET), aesTextKey));
}
/**
* 从十六进制字符串解密并返回明文字符串
*
* @param content 十六进制加密字符串
* @param aesTextKey AES 密钥字符串
* @return 解密后的明文字符串
*/
@Nullable
public static String decryptFormHexToString(@Nullable String content, String aesTextKey) {
byte[] hexBytes = decryptFormHex(content, aesTextKey);
if (hexBytes == null) {
return null;
}
return new String(hexBytes, DEFAULT_CHARSET);
}
/**
* 从十六进制字符串解密并返回字节数组
*
* @param content 十六进制加密字符串
* @param aesTextKey AES 密钥字符串
* @return 解密后的字节数组
*/
@Nullable
public static byte[] decryptFormHex(@Nullable String content, String aesTextKey) {
if (isBlank(content)) {
return null;
}
return decrypt(hexDecode(content.getBytes(DEFAULT_CHARSET)), aesTextKey);
}
/**
* 加密字节数组
*
* @param content 明文字节数组
* @param aesTextKey AES 密钥字符串
* @return 加密后的字节数组
*/
public static byte[] encrypt(byte[] content, String aesTextKey) {
return aes(pkcs7Encode(content), Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET), Cipher.ENCRYPT_MODE);
}
/**
* 解密字节数组
*
* @param content 加密的字节数组
* @param aesTextKey AES 密钥字符串
* @return 解密后的字节数组
*/
public static byte[] decrypt(byte[] content, String aesTextKey) {
return pkcs7Decode(aes(content, Objects.requireNonNull(aesTextKey).getBytes(DEFAULT_CHARSET), Cipher.DECRYPT_MODE));
}
/**
* AES 加解密核心方法
*/
private static byte[] aes(byte[] data, byte[] aesKey, int mode) {
Assert.isTrue(aesKey.length == 32, "IllegalAesKey, aesKey's length must be 32");
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
cipher.init(mode, keySpec, iv);
return cipher.doFinal(data);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// ==================== 内联工具方法 ====================
/**
* 生成指定长度的随机字符串
*/
public static String randomKey(int count) {
char[] buffer = new char[count];
for (int i = 0; i < count; i++) {
buffer[i] = RANDOM_FACTOR.charAt(SECURE_RANDOM.nextInt(RANDOM_FACTOR.length()));
}
return new String(buffer);
}
/**
* 生成无横线的 UUID
*/
private static String randomUUID() {
ThreadLocalRandom random = ThreadLocalRandom.current();
return new UUID(random.nextLong(), random.nextLong()).toString().replace("-", "");
}
/**
* 检查字符串是否为空或空白
*/
private static boolean isBlank(@Nullable String str) {
if (str == null || str.isEmpty()) {
return true;
}
for (int i = 0; i < str.length(); i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
}
/**
* 字节数组转十六进制字符串
*/
private static String hexEncode(byte[] data) {
int len = data.length;
byte[] out = new byte[len << 1];
for (int i = 0, j = 0; i < len; i++) {
out[j++] = HEX_DIGITS[(0xF0 & data[i]) >>> 4];
out[j++] = HEX_DIGITS[0x0F & data[i]];
}
return new String(out, DEFAULT_CHARSET);
}
/**
* 十六进制字节数组解码为原始字节数组
*/
private static byte[] hexDecode(byte[] data) {
int len = data.length;
if ((len & 0x01) != 0) {
throw new IllegalArgumentException("hexBinary needs to be even-length: " + len);
}
byte[] out = new byte[len >> 1];
for (int i = 0, j = 0; j < len; i++) {
int f = Character.digit(data[j++], 16) << 4;
f |= Character.digit(data[j++], 16);
out[i] = (byte) (f & 0xFF);
}
return out;
}
/**
* PKCS7 编码(填充)
*/
private static byte[] pkcs7Encode(byte[] src) {
int count = src.length;
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
byte pad = (byte) (amountToPad & 0xFF);
byte[] dest = new byte[count + amountToPad];
System.arraycopy(src, 0, dest, 0, count);
Arrays.fill(dest, count, dest.length, pad);
return dest;
}
/**
* PKCS7 解码(去填充)
*/
private static byte[] pkcs7Decode(byte[] decrypted) {
int pad = decrypted[decrypted.length - 1];
if (pad < 1 || pad > BLOCK_SIZE) {
pad = 0;
}
if (pad > 0) {
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
}
return decrypted;
}
}

View File

@@ -0,0 +1,58 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.provider;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.LinkedHashSet;
/**
* RequestProvider
*
* @author Chill
*/
public class RequestProvider {
/**
* 获取原始url
*
* @param exchange
* @return
*/
public static String getOriginalRequestUrl(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
LinkedHashSet<URI> uris = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
URI requestUri = uris.stream().findFirst().orElse(request.getURI());
MultiValueMap<String, String> queryParams = request.getQueryParams();
return UriComponentsBuilder.fromPath(requestUri.getRawPath()).queryParams(queryParams).build().toUriString();
}
}

View File

@@ -0,0 +1,93 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.provider;
import java.util.HashMap;
import java.util.Map;
/**
* 请求响应返回
*
* @author Chill
*/
public class ResponseProvider {
/**
* 成功
*
* @param message 信息
* @return
*/
public static Map<String, Object> success(String message) {
return response(200, message);
}
/**
* 失败
*
* @param message 信息
* @return
*/
public static Map<String, Object> fail(String message) {
return response(400, message);
}
/**
* 未授权
*
* @param message 信息
* @return
*/
public static Map<String, Object> unAuth(String message) {
return response(401, message);
}
/**
* 服务器异常
*
* @param message 信息
* @return
*/
public static Map<String, Object> error(String message) {
return response(500, message);
}
/**
* 构建返回的JSON数据格式
*
* @param status 状态码
* @param message 信息
* @return
*/
public static Map<String, Object> response(int status, String message) {
Map<String, Object> map = new HashMap<>(16);
map.put("code", status);
map.put("msg", message);
map.put("data", null);
return map;
}
}

View File

@@ -0,0 +1,46 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.gateway.provider;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 签名授权规则
*
* @author Chill
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SignSecure {
/**
* 请求路径
*/
private String pattern;
}

View File

@@ -0,0 +1,11 @@
blade:
#多团队协作服务配置
loadbalancer:
#开启配置
enabled: true
#灰度版本
#version: 3.0.0
#负载均衡优先调用的ip段
prior-ip-pattern:
- 192.168.0.*
- 127.0.0.1

View File

@@ -0,0 +1,11 @@
blade:
#多团队协作服务配置
loadbalancer:
#开启配置
enabled: true
#灰度版本
#version: 3.0.0
#负载均衡优先调用的ip段
prior-ip-pattern:
- 192.168.0.*
- 127.0.0.1

View File

@@ -0,0 +1,11 @@
blade:
#多团队协作服务配置
loadbalancer:
#开启配置
enabled: true
#灰度版本
#version: 3.0.0
#负载均衡优先调用的ip段
prior-ip-pattern:
- 192.168.0.*
- 127.0.0.1

View File

@@ -0,0 +1,16 @@
knife4j:
gateway:
enabled: true
tags-sorter: order
operations-sorter: order
# 指定服务发现的模式聚合微服务文档,并且是默认`default`分组
strategy: discover
discover:
enabled: true
# 指定版本号(Swagger2|OpenAPI3)
version : openapi3
# 需要排除的微服务(eg:网关服务)
excluded-services:
- blade-admin
- blade-gateway
- blade-log

View File

@@ -0,0 +1,23 @@
server:
port: 80
spring:
cloud:
gateway:
server:
webflux:
discovery:
locator:
enabled: true
loadbalancer:
retry:
enabled: true
# 兼容性说明新版gateway配置需要最后启动才能发现服务否则会出现404的问题
# 此问题可等spring官方修复或临时处理方案采用下方旧版配置即可自动发现服务
#spring:
# cloud:
# gateway:
# discovery:
# locator:
# enabled: true

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>blade-ops-api</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-develop-api</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
</project>

View File

@@ -0,0 +1,68 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.develop.feign;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.tool.api.R;
import org.springblade.develop.pojo.entity.Datasource;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* IDatasourceClient
*
* @author Chill
*/
@FeignClient(
value = AppConstant.APPLICATION_DEVELOP_NAME,
fallback = IDatasourceClientFallback.class
)
public interface IDatasourceClient {
String API_PREFIX = "/feign/client/datasource";
String GET_DETAIL = API_PREFIX + "/detail";
String GET_LIST = API_PREFIX + "/list";
/**
* 获取数据源详情
*
* @param id 数据源ID
* @return R
*/
@GetMapping(GET_DETAIL)
R<Datasource> detail(@RequestParam("id") Long id);
/**
* 获取所有数据源列表
*
* @return R
*/
@GetMapping(GET_LIST)
R<List<Datasource>> list();
}

View File

@@ -0,0 +1,52 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.develop.feign;
import org.springblade.core.tool.api.R;
import org.springblade.develop.pojo.entity.Datasource;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 数据源远程调用失败处理类
*
* @author Chill
*/
@Component
public class IDatasourceClientFallback implements IDatasourceClient {
@Override
public R<Datasource> detail(Long id) {
return R.fail("远程调用失败");
}
@Override
public R<List<Datasource>> list() {
return R.fail("远程调用失败");
}
}

View File

@@ -0,0 +1,153 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.develop.pojo.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 代码生成DTO
*
* @author Chill
*/
@Data
public class GeneratorDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 上级菜单主键
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "上级菜单主键")
private Long menuId;
/**
* 数据源主键
*/
@Schema(description = "数据源主键")
@JsonSerialize(using = ToStringSerializer.class)
private Long datasourceId;
/**
* 模型编号
*/
@Schema(description = "模型编号")
private String modelCode;
/**
* 物理表名
*/
@Schema(description = "物理表名")
private String modelTable;
/**
* 表单设计
*/
@Schema(description = "表单设计")
private String modelForm;
/**
* 模型类名
*/
@Schema(description = "模型类名")
private String modelClass;
/**
* 模块名称
*/
@Schema(description = "服务名称")
private String serviceName;
/**
* 模块名称
*/
@Schema(description = "模块名称")
private String codeName;
/**
* 表名
*/
@Schema(description = "表名")
private String tableName;
/**
* 实体名
*/
@Schema(description = "表前缀")
private String tablePrefix;
/**
* 主键名
*/
@Schema(description = "主键名")
private String pkName;
/**
* 后端包名
*/
@Schema(description = "后端包名")
private String packageName;
/**
* 基础业务模式
*/
@Schema(description = "基础业务模式")
private Integer baseMode;
/**
* 包装器模式
*/
@Schema(description = "包装器模式")
private Integer wrapMode;
/**
* 远程调用模式
*/
@Schema(description = "远程调用模式")
private Integer feignMode;
/**
* 代码风格
*/
@Schema(description = "代码风格")
private String codeStyle;
/**
* 后端路径
*/
@Schema(description = "后端路径")
private String apiPath;
/**
* 前端路径
*/
@Schema(description = "前端路径")
private String webPath;
}

View File

@@ -0,0 +1,53 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.develop.pojo.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.develop.pojo.entity.Model;
import org.springblade.develop.pojo.entity.ModelPrototype;
import java.io.Serial;
import java.util.List;
/**
* 代码模型DTO
*
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ModelDTO extends Model {
@Serial
private static final long serialVersionUID = 1L;
/**
* 代码建模原型
*/
private List<ModelPrototype> prototypes;
}

View File

@@ -0,0 +1,197 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.develop.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 实体类
*
* @author Chill
*/
@Data
@TableName("blade_code")
@Schema(description = "Code对象")
public class Code implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 数据模型主键
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "数据模型主键")
private Long modelId;
/**
* 上级菜单主键
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "上级菜单主键")
private Long menuId;
/**
* 模块名称
*/
@Schema(description = "服务名称")
private String serviceName;
/**
* 模块名称
*/
@Schema(description = "模块名称")
private String codeName;
/**
* 表名
*/
@Schema(description = "表名")
private String tableName;
/**
* 实体名
*/
@Schema(description = "表前缀")
private String tablePrefix;
/**
* 主键名
*/
@Schema(description = "主键名")
private String pkName;
/**
* 后端包名
*/
@Schema(description = "后端包名")
private String packageName;
/**
* 模版类型
*/
@Schema(description = "模版类型")
private String templateType;
/**
* 作者信息
*/
@Schema(description = "作者信息")
private String author;
/**
* 子表模型主键
*/
@Schema(description = "子表模型主键")
private String subModelId;
/**
* 子表绑定外键
*/
@Schema(description = "子表绑定外键")
private String subFkId;
/**
* 树主键字段
*/
@Schema(description = "树主键字段")
private String treeId;
/**
* 树父主键字段
*/
@Schema(description = "树父主键字段")
private String treePid;
/**
* 树名称字段
*/
@Schema(description = "树名称字段")
private String treeName;
/**
* 基础业务模式
*/
@Schema(description = "基础业务模式")
private Integer baseMode;
/**
* 包装器模式
*/
@Schema(description = "包装器模式")
private Integer wrapMode;
/**
* 远程调用模式
*/
@Schema(description = "远程调用模式")
private Integer feignMode;
/**
* 代码风格
*/
@Schema(description = "代码风格")
private String codeStyle;
/**
* 后端路径
*/
@Schema(description = "后端路径")
private String apiPath;
/**
* 前端路径
*/
@Schema(description = "前端路径")
private String webPath;
/**
* 是否已删除
*/
@TableLogic
@Schema(description = "是否已删除")
private Integer isDeleted;
}

Some files were not shown because too many files have changed in this diff Show More