This commit is contained in:
zhaohu 2026-06-12 16:14:58 +08:00
parent cf71897528
commit a7ba0f21e5
235 changed files with 33574 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团队随时可能终止本协议、收回许可和授权并要求您承担相应法律和经济责任。

45
blade-biz-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-Biz</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-biz-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,37 @@
/**
* 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 {
String NOTICE_ONE = "blade:notice:one";
}

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,61 @@
/**
* 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
*/
Integer TOP_PARENT_ID = 0;
/**
* 顶级父节点名称
*/
String TOP_PARENT_NAME = "顶级";
/**
* 默认密码
*/
String DEFAULT_PASSWORD = "123456";
}

View File

@ -0,0 +1,216 @@
/**
* 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;
import static org.springblade.core.launch.constant.AppConstant.APPLICATION_NAME_PREFIX;
/**
* 通用常量
*
* @author Chill
*/
public interface LauncherConstant {
/**
* nacos 用户名
*/
String NACOS_USERNAME = "nacos";
/**
* nacos 密码
*/
String NACOS_PASSWORD = "nacos";
/**
* xxljob
*/
String APPLICATION_XXLJOB_NAME = APPLICATION_NAME_PREFIX + "xxljob";
/**
* xxljob
*/
String APPLICATION_XXLJOB_ADMIN_NAME = APPLICATION_NAME_PREFIX + "xxljob-admin";
/**
* nacos namespace id
*/
String NACOS_NAMESPACE = "f447a694-519a-4255-95f9-bcbb5a5d6369";
/**
* 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.19.2.126:30848";
/**
* 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";
/**
* sharding
*/
String APPLICATION_SHARDING_NAME = APPLICATION_NAME_PREFIX + "sharding";
/**
* seata订单
*/
String APPLICATION_SEATA_ORDER_NAME = APPLICATION_NAME_PREFIX + "seata-order";
/**
* seata库存
*/
String APPLICATION_SEATA_STORAGE_NAME = APPLICATION_NAME_PREFIX + "seata-storage";
/**
* mqtt-broker
*/
String APPLICATION_MQTT_BROKER_NAME = APPLICATION_NAME_PREFIX + "mqtt-broker";
/**
* mqtt-client
*/
String APPLICATION_MQTT_CLIENT_NAME = APPLICATION_NAME_PREFIX + "mqtt-client";
/**
* rabbit-listener
*/
String APPLICATION_RABBIT_LISTENER_NAME = APPLICATION_NAME_PREFIX + "rabbit-listener";
/**
* rabbit-publisher
*/
String APPLICATION_RABBIT_PUBLISHER_NAME = APPLICATION_NAME_PREFIX + "rabbit-publisher";
/**
* 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;
};
}
}

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.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");
// 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 :: ${spring.application.name}:${AnsiColor.RED}${blade.env}${AnsiColor.BLUE} :: Running SpringBoot ${spring-boot.version} :: ${AnsiColor.BRIGHT_BLACK}

View File

@ -0,0 +1,3 @@
#自定义配置
apollo:
name: apollo-name

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-example</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-apollo</artifactId>
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-biz-common</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-cloud</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-launch</artifactId>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>2.2.0</version>
</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,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;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
/**
* Apollo启动器
*
* @author Chill
*/
@Slf4j
@BladeCloudApplication
public class ApolloApplication {
public static void main(String[] args) {
BladeApplication.run("blade-apollo", ApolloApplication.class, args);
}
}

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.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* ApolloListener
*
* @author Chill
*/
@Slf4j
@Component
public class ApolloListener {
@Value("${apollo.name:undefined}")
private String name;
@EventListener(ApplicationReadyEvent.class)
public void handleApplicationReady() {
log.info("====================ApolloListener ready to work====================");
log.info("apollo.name:{}", name);
log.info("====================ApolloListener ready to work====================");
}
}

View File

@ -0,0 +1,13 @@
#服务器端口
server:
port: 8508
app:
id: blade-apollo
apollo:
#apollo官方文档https://www.apolloconfig.com/
#apollo官方测试地址http://81.68.181.139 ,账号/密码:apollo/admin
meta: http://81.68.181.139:8080
bootstrap:
enabled: true
namespaces: application,common

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-example</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-mqtt-broker</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-mqttx-server-spring-boot-starter</artifactId>
<version>3.1.12</version>
</dependency>
</dependencies>
</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.mqtt.broker;
import org.springblade.core.launch.BladeApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import static org.springblade.common.constant.LauncherConstant.APPLICATION_MQTT_BROKER_NAME;
/**
* MQTT Broker 启动器
*
* @author Chill
*/
@EnableScheduling
@SpringBootApplication
public class MqttBrokerApplication {
public static void main(String[] args) {
BladeApplication.run(APPLICATION_MQTT_BROKER_NAME, MqttBrokerApplication.class, args);
}
}

View File

@ -0,0 +1,107 @@
/**
* 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.mqtt.broker.listener;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.iot.mqtt.spring.server.event.MqttClientOfflineEvent;
import net.dreamlu.iot.mqtt.spring.server.event.MqttClientOnlineEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
/**
* 客户端上下线监听器
*
* @author Chill
*/
@Slf4j
@Service
public class MqttConnectStatusListener {
@EventListener
public void online(MqttClientOnlineEvent event) {
log.info("MQTT客户端上线: {}", event);
// 客户端上线处理逻辑
String clientId = event.getClientId();
log.info("客户端上线 - clientId: {}", clientId);
// 可以在这里添加上线处理逻辑例如
// 1. 记录上线日志
// 2. 更新设备状态
// 3. 发送上线通知
// 4. 统计在线数量等
handleClientOnline(clientId);
}
@EventListener
public void offline(MqttClientOfflineEvent event) {
log.info("MQTT客户端下线: {}", event);
// 客户端下线处理逻辑
String clientId = event.getClientId();
log.info("客户端下线 - clientId: {}", clientId);
// 可以在这里添加下线处理逻辑例如
// 1. 记录下线日志
// 2. 更新设备状态
// 3. 发送下线通知
// 4. 清理资源等
handleClientOffline(clientId);
}
/**
* 处理客户端上线
*
* @param clientId 客户端ID
*/
private void handleClientOnline(String clientId) {
try {
// 添加具体的上线处理逻辑
log.debug("执行客户端上线处理: clientId={}", clientId);
} catch (Exception e) {
log.error("处理客户端上线异常: clientId={}", clientId, e);
}
}
/**
* 处理客户端下线
*
* @param clientId 客户端ID
*/
private void handleClientOffline(String clientId) {
try {
// 添加具体的下线处理逻辑
log.debug("执行客户端下线处理: clientId={}", clientId);
} catch (Exception e) {
log.error("处理客户端下线异常: clientId={}", clientId, e);
}
}
}

View File

@ -0,0 +1,91 @@
/**
* 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.mqtt.broker.listener;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.iot.mqtt.codec.MqttPublishMessage;
import net.dreamlu.iot.mqtt.codec.MqttQoS;
import net.dreamlu.iot.mqtt.core.server.event.IMqttMessageListener;
import org.springframework.stereotype.Service;
import org.tio.core.ChannelContext;
import org.tio.utils.buffer.ByteBufferUtil;
/**
* 服务端接收消息监听器
*
* @author Chill
*/
@Slf4j
@Service
public class MqttServerMessageListener implements IMqttMessageListener {
@Override
public void onMessage(ChannelContext context, String clientId, String topic, MqttQoS qoS, MqttPublishMessage message) {
log.info("收到MQTT消息 - clientId: {}, topic: {}, qoS: {}, payload: {}",
clientId, topic, qoS, ByteBufferUtil.toString(message.getPayload()));
// 自定义数据流转逻辑
// 可以在这里添加消息处理逻辑例如
// 1. 数据解析和格式化
// 2. 业务规则处理
// 3. 数据存储
// 4. 消息转发
// 5. 告警处理等
handleMessage(clientId, topic, message);
}
/**
* 处理接收到的消息
*
* @param clientId 客户端ID
* @param topic 主题
* @param message 消息
*/
private void handleMessage(String clientId, String topic, MqttPublishMessage message) {
try {
String payload = ByteBufferUtil.toString(message.getPayload());
// 根据主题进行不同的处理
if (topic.contains("/property/")) {
log.info("处理属性消息: {}", payload);
// 处理设备属性上报
} else if (topic.contains("/event/")) {
log.info("处理事件消息: {}", payload);
// 处理设备事件上报
} else if (topic.contains("/command/")) {
log.info("处理命令消息: {}", payload);
// 处理设备命令响应
} else {
log.info("处理通用消息: {}", payload);
// 处理其他类型消息
}
} catch (Exception e) {
log.error("处理MQTT消息异常: clientId={}, topic={}", clientId, topic, e);
}
}
}

View File

@ -0,0 +1,49 @@
/**
* 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.mqtt.broker.service;
import org.tio.core.ChannelContext;
/**
* MQTT 认证服务
*
* @author Chill
*/
public interface IMqttAuthService {
/**
* 设备连接认证
*
* @param context ChannelContext
* @param clientId clientId
* @param userName userName
* @param password password
* @param clientNodeIp clientNodeIp
* @return 是否成功
*/
boolean hasAuth(ChannelContext context, String clientId, String userName, String password, String clientNodeIp);
}

View File

@ -0,0 +1,67 @@
/**
* 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.mqtt.broker.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springblade.mqtt.broker.service.IMqttAuthService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.tio.core.ChannelContext;
/**
* MQTT 认证处理服务
*
* @author Chill
*/
@Slf4j
@Service
public class MqttAuthServiceImpl implements IMqttAuthService {
/**
* MQTT 认证最大间隔时间默认 5 分钟
*/
@Value("${mqtt.auth.timeSecondDiff:300}")
private int timeSecondDiff;
@Override
public boolean hasAuth(ChannelContext context, String clientId, String userName, String password, String clientNodeIp) {
log.info("MQTT 客户端认证: clientId={}, userName={}, clientNodeIp={}", clientId, userName, clientNodeIp);
// 自定义账号认证逻辑
// 这里可以根据实际需求添加具体的认证逻辑
// 例如查询数据库验证用户名密码检查设备白名单等
// 简单的demo认证逻辑允许所有连接
if (userName != null && password != null) {
log.info("MQTT 客户端认证成功: clientId={}", clientId);
return true;
}
log.warn("MQTT 客户端认证失败: clientId={}", clientId);
return false;
}
}

View File

@ -0,0 +1,45 @@
# 服务器配置
server:
port: 8290
undertow:
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
buffer-size: 1024
# 是否分配的直接内存
direct-buffers: true
# 线程配置
threads:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
io: 16
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 400
servlet:
# 编码配置
encoding:
charset: UTF-8
force: true
# MQTT服务端配置
mqtt:
server:
enabled: true # 是否开启服务端默认true
port: 11883 # 端口默认1883
name: Blade-MQTT-Broker # 名称默认Mica-Mqtt-Server
heartbeat-timeout: 120000 # 心跳超时,单位毫秒,默认: 1000 * 120
read-buffer-size: 8KB # 接收数据的 buffer size默认8k
max-bytes-in-message: 10MB # 消息解析最大 bytes 长度默认10M
auth:
enable: false # 是否开启 mqtt 认证
debug: true # 如果开启 prometheus 指标收集建议关闭
stat-enable: true # 开启指标收集debug 和 prometheus 开启时需要打开,默认开启,关闭节省内存
web-port: 28083 # http、websocket 端口默认8083
websocket-enable: true # 是否开启 websocket默认 true
http-enable: false # 是否开启 http api默认 false
ssl: # mqtt tcp ssl 认证
enabled: false # 是否开启 ssl 认证2.1.0 开始支持双向认证
keystore-path: # 必须参数ssl keystore 目录,支持 classpath:/ 路径。
keystore-pass: # 必选参数ssl keystore 密码
truststore-path: # 可选参数ssl 双向认证 truststore 目录,支持 classpath:/ 路径。
truststore-pass: # 可选参数ssl 双向认证 truststore 密码
client-auth: none # 是否需要客户端认证双向认证默认NONE不需要
auth:
timeSecondDiff: 300 # 认证时间差默认5分钟

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-example</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-mqtt-client</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>net.dreamlu</groupId>
<artifactId>mica-mqttx-client</artifactId>
<version>3.1.12</version>
</dependency>
</dependencies>
</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.mqtt.client;
import org.springblade.core.launch.BladeApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import static org.springblade.common.constant.LauncherConstant.APPLICATION_MQTT_CLIENT_NAME;
/**
* MQTT Client 启动器
*
* @author Chill
*/
@EnableScheduling
@SpringBootApplication
public class MqttClientApplication {
public static void main(String[] args) {
BladeApplication.run(APPLICATION_MQTT_CLIENT_NAME, MqttClientApplication.class, args);
}
}

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.mqtt.client.listener;
import lombok.extern.slf4j.Slf4j;
import org.springblade.mqtt.client.simulator.DeviceSimulator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 设备应用监听器
*
* @author Chill
*/
@Slf4j
@Component
public class DeviceListener {
@Value("${mqtt.client.productKey:DEMO_PRODUCT}")
private String productKey;
@Value("${mqtt.client.deviceName:DEMO_DEVICE}")
private String deviceName;
@Value("${mqtt.client.deviceSecret:demo_secret_123}")
private String deviceSecret;
@Value("${mqtt.client.serverHost:localhost}")
private String serverHost;
@Value("${mqtt.client.serverPort:1883}")
private int serverPort;
@Value("${mqtt.client.enabled:true}")
private boolean enabled;
@EventListener(ApplicationReadyEvent.class)
public void handleApplicationReady() {
if (!enabled) {
log.info("MQTT客户端已禁用跳过启动");
return;
}
log.info("应用启动完成开始初始化MQTT客户端");
log.info("配置信息: productKey={}, deviceName={}, serverHost={}:{}",
productKey, deviceName, serverHost, serverPort);
try {
// 初始化设备模拟器
DeviceSimulator simulator = new DeviceSimulator(
productKey,
deviceName,
deviceSecret,
serverHost,
serverPort
);
// 启动模拟器
simulator.start();
log.info("设备模拟器启动成功");
// 添加关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("应用关闭,停止设备模拟器");
simulator.stop();
}));
} catch (Exception e) {
log.error("启动设备模拟器失败", e);
}
}
}

View File

@ -0,0 +1,282 @@
/**
* 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.mqtt.client.simulator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.iot.mqtt.core.client.MqttClient;
import org.springblade.mqtt.client.support.DataReq;
import org.springblade.mqtt.client.support.NtpReq;
import org.tio.utils.buffer.ByteBufferUtil;
import org.tio.utils.json.JsonUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 设备模拟器
*
* @author Chill
*/
@Slf4j
@RequiredArgsConstructor
public class DeviceSimulator {
private final String productKey;
private final String deviceName;
private final String deviceSecret;
private final String serverHost;
private final int serverPort;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);
/**
* 启动模拟器
*/
public void start() {
log.info("启动设备模拟器: productKey={}, deviceName={}", productKey, deviceName);
// 初始化 MQTT 客户端
String clientId = generateClientId();
String username = generateUsername();
String password = generatePassword();
log.info("连接配置: clientId={}, username={}, host={}:{}", clientId, username, serverHost, serverPort);
try {
MqttClient client = MqttClient.create()
.ip(serverHost)
.port(serverPort)
.username(username)
.password(password)
.clientId(clientId)
.connectSync();
log.info("MQTT客户端连接成功");
// 订阅主题
subscribeTopics(client);
// 启动任务调度
scheduleTasks(client);
} catch (Exception e) {
log.error("MQTT客户端连接失败", e);
}
}
/**
* 生成客户端ID
*
* @return clientId
*/
private String generateClientId() {
return productKey + "_" + deviceName + "_" + System.currentTimeMillis();
}
/**
* 生成用户名
*
* @return username
*/
private String generateUsername() {
return deviceName + "&" + productKey;
}
/**
* 生成密码
*
* @return password
*/
private String generatePassword() {
// 简化的密码生成逻辑
return deviceSecret;
}
/**
* 订阅主题
*
* @param client MqttClient
*/
private void subscribeTopics(MqttClient client) {
// 订阅设备相关主题
String topicPrefix = "/blade/sys/" + productKey + "/" + deviceName;
// 订阅属性设置
client.subQos0(topicPrefix + "/thing/service/property/set", (context, topic, message, payload) -> {
log.info("收到属性设置: topic={}, payload={}", topic, ByteBufferUtil.toString(payload));
});
// 订阅服务调用
client.subQos0(topicPrefix + "/thing/service/+", (context, topic, message, payload) -> {
log.info("收到服务调用: topic={}, payload={}", topic, ByteBufferUtil.toString(payload));
});
// 订阅NTP响应
client.subQos0("/blade/ext/ntp/" + productKey + "/" + deviceName + "/response", (context, topic, message, payload) -> {
log.info("收到NTP响应: topic={}, payload={}", topic, ByteBufferUtil.toString(payload));
});
log.info("已订阅设备主题: {}", topicPrefix);
}
/**
* 任务调度
*
* @param client MqttClient
*/
private void scheduleTasks(MqttClient client) {
// 定期上报属性数据每30秒
scheduler.scheduleAtFixedRate(() -> {
publishPropertyData(client);
}, 5, 30, TimeUnit.SECONDS);
// 定期上报事件数据每60秒
scheduler.scheduleAtFixedRate(() -> {
publishEventData(client);
}, 10, 60, TimeUnit.SECONDS);
// 定期发送心跳数据每120秒
scheduler.scheduleAtFixedRate(() -> {
publishHeartbeat(client);
}, 15, 120, TimeUnit.SECONDS);
// 定期请求NTP时间每300秒
scheduler.scheduleAtFixedRate(() -> {
publishNtpRequest(client);
}, 20, 300, TimeUnit.SECONDS);
log.info("已启动定时任务调度");
}
/**
* 发布属性数据
*
* @param client MqttClient
*/
private void publishPropertyData(MqttClient client) {
try {
DataReq<Map<String, Object>> req = new DataReq<>();
req.setId(UUID.randomUUID().toString().replace("-", ""));
req.setVersion("1.0");
Map<String, Object> params = new HashMap<>();
params.put("temperature", 20 + Math.random() * 10); // 温度20-30度
params.put("humidity", 40 + Math.random() * 30); // 湿度40-70%
params.put("lightSwitch", Math.random() > 0.5 ? 1 : 0); // 灯开关
req.setParams(params);
String topic = "/blade/sys/" + productKey + "/" + deviceName + "/thing/event/property/post";
client.publish(topic, JsonUtil.toJsonBytes(req));
log.info("已发布属性数据: {}", JsonUtil.toJsonString(params));
} catch (Exception e) {
log.error("发布属性数据失败", e);
}
}
/**
* 发布事件数据
*
* @param client MqttClient
*/
private void publishEventData(MqttClient client) {
try {
DataReq<Map<String, Object>> req = new DataReq<>();
req.setId(UUID.randomUUID().toString().replace("-", ""));
req.setVersion("1.0");
Map<String, Object> params = new HashMap<>();
Map<String, Object> output = new HashMap<>();
output.put("eventLevel", "INFO");
output.put("eventMessage", "设备运行正常");
output.put("timestamp", System.currentTimeMillis());
params.put("output", JsonUtil.toJsonString(output));
params.put("eventName", "状态事件");
params.put("eventType", "info");
req.setParams(params);
String topic = "/blade/sys/" + productKey + "/" + deviceName + "/thing/event/StatusEvent/post";
client.publish(topic, JsonUtil.toJsonBytes(req));
log.info("已发布事件数据: {}", JsonUtil.toJsonString(params));
} catch (Exception e) {
log.error("发布事件数据失败", e);
}
}
/**
* 发布心跳数据
*
* @param client MqttClient
*/
private void publishHeartbeat(MqttClient client) {
try {
Map<String, Object> heartbeat = new HashMap<>();
heartbeat.put("deviceId", deviceName);
heartbeat.put("productKey", productKey);
heartbeat.put("timestamp", System.currentTimeMillis());
heartbeat.put("status", "online");
String topic = "/blade/sys/" + productKey + "/" + deviceName + "/heartbeat";
client.publish(topic, JsonUtil.toJsonBytes(heartbeat));
log.info("已发布心跳数据: {}", JsonUtil.toJsonString(heartbeat));
} catch (Exception e) {
log.error("发布心跳数据失败", e);
}
}
/**
* 发布NTP请求
*
* @param client MqttClient
*/
private void publishNtpRequest(MqttClient client) {
try {
NtpReq req = new NtpReq();
req.setDeviceSendTime(String.valueOf(System.currentTimeMillis()));
String topic = "/blade/ext/ntp/" + productKey + "/" + deviceName + "/request";
client.publish(topic, JsonUtil.toJsonBytes(req));
log.info("已发布NTP请求: {}", JsonUtil.toJsonString(req));
} catch (Exception e) {
log.error("发布NTP请求失败", e);
}
}
/**
* 停止模拟器
*/
public void stop() {
scheduler.shutdown();
log.info("设备模拟器已停止");
}
}

View File

@ -0,0 +1,67 @@
/**
* 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.mqtt.client.support;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serializable;
/**
* 设备通用数据格式
*
* @author Chill
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DataReq<T> implements Serializable {
/**
* 消息ID号uuid去掉短横线32位全局唯一用于ack或系统消息追踪
*/
private String id;
/**
* 协议版本号目前协议版本号唯一取值为1.0
*/
private String version;
/**
* 扩展功能的参数其下包含各功能字段平台可扩展或可自行扩展自行扩展的参数需在自定义解析模块自行解析
*/
private SysBean sys;
/**
* 请求方法
*/
private String method;
/**
* 请求参数
*/
private T params;
}

View File

@ -0,0 +1,45 @@
/**
* 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.mqtt.client.support;
import lombok.Data;
import java.io.Serializable;
/**
* NTP 请求
*
* @author Chill
*/
@Data
public class NtpReq implements Serializable {
/**
* 设备端发送时间
*/
private String deviceSendTime;
}

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.mqtt.client.support;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 扩展功能
*
* @author Chill
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysBean implements Serializable {
/**
* 不响应 ack
*/
public static final int ACK_NO = 0;
/**
* 响应 ack
*/
public static final int ACK_NEED = 1;
public SysBean(boolean ackNeed) {
this(ackNeed ? ACK_NEED : ACK_NO);
}
/**
* 扩展功能字段表示是否返回响应数据0不返回响应数据1返回响应数据
*/
private int ack;
}

View File

@ -0,0 +1,29 @@
# 服务器配置
server:
port: 8291
undertow:
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
buffer-size: 1024
# 是否分配的直接内存
direct-buffers: true
# 线程配置
threads:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
io: 16
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 400
servlet:
# 编码配置
encoding:
charset: UTF-8
force: true
# MQTT客户端配置
mqtt:
client:
enabled: true # 是否启用MQTT客户端
productKey: "BLADE_DEMO_PRODUCT" # 产品标识
deviceName: "BLADE_DEMO_DEVICE_001" # 设备名称
deviceSecret: "blade_demo_secret_123456" # 设备密钥
serverHost: "localhost" # MQTT服务器地址
serverPort: 11883 # MQTT服务器端口

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-example</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-rabbit-listener</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-tool</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
</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.rabbit.listener;
import org.springblade.core.launch.BladeApplication;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import static org.springblade.common.constant.LauncherConstant.APPLICATION_RABBIT_LISTENER_NAME;
/**
* Rabbit 消费者启动器
*
* @author Chill
*/
@EnableRabbit
@SpringBootApplication
public class RabbitListenerApplication {
public static void main(String[] args) {
BladeApplication.run(APPLICATION_RABBIT_LISTENER_NAME, RabbitListenerApplication.class, args);
}
}

View File

@ -0,0 +1,92 @@
/**
* 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.rabbit.listener.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Rabbit 消费者配置
*
* @author Chill
*/
@Configuration
public class RabbitListenerConfiguration {
/**
* 示例队列名
*/
public static final String DEMO_QUEUE_NAME = "blade.demo.queue";
/**
* 消息队列名
*/
public static final String MESSAGE_QUEUE_NAME = "blade.message.queue";
/**
* 交换机名
*/
public static final String EXCHANGE_NAME = "blade.amqp.exchange";
/**
* 示例路由规则
*/
public static final String DEMO_ROUTING_KEY_PATTERN = "demo.#";
/**
* 消息路由规则
*/
public static final String MESSAGE_ROUTING_KEY_PATTERN = "message.#";
@Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
@Bean
public Queue demoQueue() {
return new Queue(DEMO_QUEUE_NAME);
}
@Bean
public Queue messageQueue() {
return new Queue(MESSAGE_QUEUE_NAME);
}
@Bean
public Binding demoBinding(Queue demoQueue, TopicExchange exchange) {
return BindingBuilder.bind(demoQueue).to(exchange).with(DEMO_ROUTING_KEY_PATTERN);
}
@Bean
public Binding messageBinding(Queue messageQueue, TopicExchange exchange) {
return BindingBuilder.bind(messageQueue).to(exchange).with(MESSAGE_ROUTING_KEY_PATTERN);
}
}

View File

@ -0,0 +1,116 @@
/**
* 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.rabbit.listener.listener;
import lombok.extern.slf4j.Slf4j;
import org.springblade.rabbit.listener.config.RabbitListenerConfiguration;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* Rabbit 消息消费者
*
* @author Chill
*/
@Slf4j
@Component
public class MessageConsumer {
/**
* 监听示例队列消息
*
* @param message 消息
*/
@RabbitListener(queues = RabbitListenerConfiguration.DEMO_QUEUE_NAME)
public void receiveDemoMessage(Message message) {
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
String payload = new String(message.getBody());
log.info("收到示例消息: routingKey={}, payload={}", routingKey, payload);
// 处理示例消息的业务逻辑
processDemoMessage(routingKey, payload);
}
/**
* 监听普通消息队列消息
*
* @param message 消息
*/
@RabbitListener(queues = RabbitListenerConfiguration.MESSAGE_QUEUE_NAME)
public void receiveMessage(Message message) {
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
String payload = new String(message.getBody());
log.info("收到普通消息: routingKey={}, payload={}", routingKey, payload);
// 处理普通消息的业务逻辑
processMessage(routingKey, payload);
}
/**
* 处理示例消息
*
* @param routingKey 路由键
* @param payload 消息载荷
*/
private void processDemoMessage(String routingKey, String payload) {
try {
// 这里可以添加具体的业务处理逻辑
log.debug("处理示例消息: routingKey={}, payload={}", routingKey, payload);
// 示例: 如果是特定的路由键执行特定操作
if (routingKey.startsWith("demo.test")) {
log.info("执行测试操作: {}", payload);
} else if (routingKey.startsWith("demo.data")) {
log.info("处理数据: {}", payload);
}
} catch (Exception e) {
log.error("处理示例消息失败: routingKey={}, payload={}", routingKey, payload, e);
}
}
/**
* 处理普通消息
*
* @param routingKey 路由键
* @param payload 消息载荷
*/
private void processMessage(String routingKey, String payload) {
try {
// 这里可以添加具体的业务处理逻辑
log.debug("处理普通消息: routingKey={}, payload={}", routingKey, payload);
// 示例: 根据路由键进行不同的处理
if (routingKey.startsWith("message.notify")) {
log.info("处理通知消息: {}", payload);
} else if (routingKey.startsWith("message.event")) {
log.info("处理事件消息: {}", payload);
}
} catch (Exception e) {
log.error("处理普通消息失败: routingKey={}, payload={}", routingKey, payload, e);
}
}
}

View File

@ -0,0 +1,22 @@
server:
port: 8302
spring:
# RabbitMQ 配置
rabbitmq:
host: localhost
port: 5672
username: root
password: root
virtual-host: /
connection-timeout: 15000
# 消费者配置
listener:
simple:
acknowledge-mode: auto
retry:
enabled: true
initial-interval: 1000
max-attempts: 3
max-interval: 10000
multiplier: 1.0

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-example</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-rabbit-publisher</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-tool</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,49 @@
/**
* 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.rabbit.publisher;
import org.springblade.core.launch.BladeApplication;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import static org.springblade.common.constant.LauncherConstant.APPLICATION_RABBIT_PUBLISHER_NAME;
/**
* Rabbit 生产者启动器
*
* @author Chill
*/
@EnableRabbit
@EnableScheduling
@SpringBootApplication
public class RabbitPublisherApplication {
public static void main(String[] args) {
BladeApplication.run(APPLICATION_RABBIT_PUBLISHER_NAME, RabbitPublisherApplication.class, args);
}
}

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.rabbit.publisher.config;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Rabbit 生产者配置
*
* @author Chill
*/
@Configuration
public class RabbitPublisherConfiguration {
/**
* 交换机名
*/
public static final String EXCHANGE_NAME = "blade.amqp.exchange";
@Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
}

View File

@ -0,0 +1,90 @@
/**
* 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.rabbit.publisher.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.rabbit.publisher.config.RabbitPublisherConfiguration;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
/**
* Rabbit 消息生产者服务
*
* @author Chill
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class RabbitMessageService {
private final RabbitTemplate rabbitTemplate;
/**
* 发送示例消息
*
* @param routingKey 路由键
* @param message 消息内容
*/
public void sendDemoMessage(String routingKey, Object message) {
try {
rabbitTemplate.convertAndSend(RabbitPublisherConfiguration.EXCHANGE_NAME, routingKey, message);
log.info("发送示例消息成功: routingKey={}, message={}", routingKey, message);
} catch (Exception e) {
log.error("发送示例消息失败: routingKey={}, message={}", routingKey, message, e);
}
}
/**
* 发送普通消息
*
* @param routingKey 路由键
* @param message 消息内容
*/
public void sendMessage(String routingKey, Object message) {
try {
rabbitTemplate.convertAndSend(RabbitPublisherConfiguration.EXCHANGE_NAME, routingKey, message);
log.info("发送消息成功: routingKey={}, message={}", routingKey, message);
} catch (Exception e) {
log.error("发送消息失败: routingKey={}, message={}", routingKey, message, e);
}
}
/**
* 发送字节数组消息
*
* @param routingKey 路由键
* @param payload 消息载荷
*/
public void sendPayload(String routingKey, byte[] payload) {
try {
rabbitTemplate.convertAndSend(RabbitPublisherConfiguration.EXCHANGE_NAME, routingKey, payload);
log.info("发送载荷消息成功: routingKey={}, payloadSize={}", routingKey, payload.length);
} catch (Exception e) {
log.error("发送载荷消息失败: routingKey={}, payloadSize={}", routingKey, payload.length, e);
}
}
}

View File

@ -0,0 +1,192 @@
/**
* 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.rabbit.publisher.simulator;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.rabbit.publisher.service.RabbitMessageService;
import org.springblade.rabbit.publisher.support.MessageData;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Rabbit 消息模拟器模拟设备消息的转发
*
* @author Chill
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MessageSimulator implements CommandLineRunner {
private final RabbitMessageService rabbitMessageService;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);
@Override
public void run(String... args) {
log.info("启动 Rabbit 消息生产者模拟器");
// 启动定时任务
startScheduledTasks();
}
/**
* 启动定时任务
*/
private void startScheduledTasks() {
// 定期发送示例消息每30秒
scheduler.scheduleAtFixedRate(this::sendDemoMessage, 10, 30, TimeUnit.SECONDS);
// 定期发送数据消息每60秒
scheduler.scheduleAtFixedRate(this::sendDataMessage, 15, 60, TimeUnit.SECONDS);
// 定期发送通知消息每120秒
scheduler.scheduleAtFixedRate(this::sendNotifyMessage, 20, 120, TimeUnit.SECONDS);
// 定期发送事件消息每180秒
scheduler.scheduleAtFixedRate(this::sendEventMessage, 25, 180, TimeUnit.SECONDS);
log.info("已启动消息生产者定时任务");
}
/**
* 发送示例消息
*/
private void sendDemoMessage() {
try {
MessageData messageData = new MessageData();
messageData.setId(UUID.randomUUID().toString().replace("-", ""));
messageData.setTimestamp(System.currentTimeMillis());
messageData.setType("demo");
Map<String, Object> data = new HashMap<>();
data.put("temperature", 20 + Math.random() * 10);
data.put("humidity", 40 + Math.random() * 30);
data.put("status", Math.random() > 0.5 ? "online" : "offline");
messageData.setData(data);
String routingKey = "demo.test." + System.currentTimeMillis();
rabbitMessageService.sendDemoMessage(routingKey, JsonUtil.toJson(messageData));
log.debug("已发送示例消息: routingKey={}", routingKey);
} catch (Exception e) {
log.error("发送示例消息失败", e);
}
}
/**
* 发送数据消息
*/
private void sendDataMessage() {
try {
MessageData messageData = new MessageData();
messageData.setId(UUID.randomUUID().toString().replace("-", ""));
messageData.setTimestamp(System.currentTimeMillis());
messageData.setType("data");
Map<String, Object> data = new HashMap<>();
data.put("deviceId", "device_" + (int)(Math.random() * 100));
data.put("value", Math.random() * 100);
data.put("unit", "celsius");
messageData.setData(data);
String routingKey = "demo.data." + System.currentTimeMillis();
rabbitMessageService.sendDemoMessage(routingKey, JsonUtil.toJson(messageData));
log.debug("已发送数据消息: routingKey={}", routingKey);
} catch (Exception e) {
log.error("发送数据消息失败", e);
}
}
/**
* 发送通知消息
*/
private void sendNotifyMessage() {
try {
MessageData messageData = new MessageData();
messageData.setId(UUID.randomUUID().toString().replace("-", ""));
messageData.setTimestamp(System.currentTimeMillis());
messageData.setType("notify");
Map<String, Object> data = new HashMap<>();
data.put("title", "系统通知");
data.put("content", "这是一条通知消息");
data.put("level", Math.random() > 0.5 ? "info" : "warning");
messageData.setData(data);
String routingKey = "message.notify." + System.currentTimeMillis();
rabbitMessageService.sendMessage(routingKey, JsonUtil.toJson(messageData));
log.debug("已发送通知消息: routingKey={}", routingKey);
} catch (Exception e) {
log.error("发送通知消息失败", e);
}
}
/**
* 发送事件消息
*/
private void sendEventMessage() {
try {
MessageData messageData = new MessageData();
messageData.setId(UUID.randomUUID().toString().replace("-", ""));
messageData.setTimestamp(System.currentTimeMillis());
messageData.setType("event");
Map<String, Object> data = new HashMap<>();
data.put("eventType", "user_action");
data.put("action", Math.random() > 0.5 ? "login" : "logout");
data.put("userId", "user_" + (int)(Math.random() * 1000));
messageData.setData(data);
String routingKey = "message.event." + System.currentTimeMillis();
rabbitMessageService.sendMessage(routingKey, JsonUtil.toJson(messageData));
log.debug("已发送事件消息: routingKey={}", routingKey);
} catch (Exception e) {
log.error("发送事件消息失败", e);
}
}
/**
* 停止模拟器
*/
public void stop() {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
log.info("消息生产者模拟器已停止");
}
}
}

View File

@ -0,0 +1,69 @@
/**
* 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.rabbit.publisher.support;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* Rabbit 消息数据
*
* @author Chill
*/
@Data
public class MessageData implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 消息ID
*/
private String id;
/**
* 消息类型
*/
private String type;
/**
* 时间戳
*/
private Long timestamp;
/**
* 消息数据
*/
private Object data;
/**
* 消息版本
*/
private String version = "1.0";
}

View File

@ -0,0 +1,15 @@
server:
port: 8301
spring:
# RabbitMQ 配置
rabbitmq:
host: localhost
port: 5672
username: root
password: root
virtual-host: /
connection-timeout: 15000
# 生产者配置
publisher-confirm-type: correlated
publisher-returns: true

View File

@ -0,0 +1,30 @@
-- 创建 order库、业务表、undo_log表
create database seata_order;
use seata_order;
DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `undo_log`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`branch_id` BIGINT(20) NOT NULL,
`xid` VARCHAR(100) NOT NULL,
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL,
`log_status` INT(11) NOT NULL,
`log_created` DATETIME NOT NULL,
`log_modified` DATETIME NOT NULL,
`ext` VARCHAR(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8;

View File

@ -0,0 +1,27 @@
<?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-example</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-seata-order</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-transaction</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.seata.order;
import org.springblade.common.constant.LauncherConstant;
import org.springblade.core.cloud.feign.EnableBladeFeign;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.transaction.annotation.SeataCloudApplication;
/**
* Order启动器
*
* @author Chill
*/
@EnableBladeFeign
@SeataCloudApplication
public class SeataOrderApplication {
public static void main(String[] args) {
BladeApplication.run(LauncherConstant.APPLICATION_SEATA_ORDER_NAME, SeataOrderApplication.class, args);
}
}

View File

@ -0,0 +1,34 @@
package org.springblade.seata.order.controller;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.api.R;
import org.springblade.seata.order.service.IOrderService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* OrderController
*
* @author Chill
*/
@RestController
@RequestMapping("order")
@AllArgsConstructor
public class OrderController {
private final IOrderService orderService;
/**
* 创建订单
*
* @param userId 用户id
* @param commodityCode 商品代码
* @param count 数量
* @return boolean
*/
@RequestMapping("/create")
public R createOrder(String userId, String commodityCode, Integer count) {
return R.status(orderService.createOrder(userId, commodityCode, count));
}
}

View File

@ -0,0 +1,33 @@
package org.springblade.seata.order.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* Order
*
* @author Chill
*/
@Data
@Accessors(chain = true)
@TableName("tb_order")
public class Order implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String userId;
private String commodityCode;
private Integer count;
private BigDecimal money;
}

View File

@ -0,0 +1,25 @@
package org.springblade.seata.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* StorageClient
*
* @author Chill
*/
@FeignClient(name = "blade-seata-storage", fallback = StorageClientFallback.class)
public interface IStorageClient {
/**
* 减库存
*
* @param commodityCode 商品代码
* @param count 数量
* @return boolean
*/
@GetMapping("/storage/deduct")
int deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);
}

View File

@ -0,0 +1,18 @@
package org.springblade.seata.order.feign;
import org.springframework.stereotype.Component;
/**
* StorageClientFallback
*
* @author Chill
*/
@Component
public class StorageClientFallback implements IStorageClient {
@Override
public int deduct(String commodityCode, Integer count) {
return -1;
}
}

View File

@ -0,0 +1,12 @@
package org.springblade.seata.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.seata.order.entity.Order;
/**
* OrderMapper
*
* @author Chill
*/
public interface OrderMapper extends BaseMapper<Order> {
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.springblade.seata.order.mapper.OrderMapper">
</mapper>

View File

@ -0,0 +1,23 @@
package org.springblade.seata.order.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.seata.order.entity.Order;
/**
* IOrderService
*
* @author Chill
*/
public interface IOrderService extends IService<Order> {
/**
* 创建订单
*
* @param userId 用户id
* @param commodityCode 商品代码
* @param count 数量
* @return boolean
*/
boolean createOrder(String userId, String commodityCode, Integer count);
}

View File

@ -0,0 +1,48 @@
package org.springblade.seata.order.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.AllArgsConstructor;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.seata.order.entity.Order;
import org.springblade.seata.order.feign.IStorageClient;
import org.springblade.seata.order.mapper.OrderMapper;
import org.springblade.seata.order.service.IOrderService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
/**
* OrderServiceImpl
*
* @author Chill
*/
@Service
@AllArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
private final IStorageClient storageClient;
@Override
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
public boolean createOrder(String userId, String commodityCode, Integer count) {
int maxCount = 100;
BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5));
Order order = new Order()
.setUserId(userId)
.setCommodityCode(commodityCode)
.setCount(count)
.setMoney(orderMoney);
int cnt1 = baseMapper.insert(order);
int cnt2 = storageClient.deduct(commodityCode, count);
if (cnt2 < 0) {
throw new ServiceException("创建订单失败");
} else if (count > maxCount) {
throw new ServiceException("超过订单最大值,创建订单失败");
}
return cnt1 > 0 && cnt2 > 0;
}
}

View File

@ -0,0 +1,15 @@
#服务器端口
server:
port: 8501
#数据源配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/seata_order?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
username: root
password: root
#常规配置
blade:
mybatis-plus:
tenant-mode: false

View File

@ -0,0 +1,33 @@
-- 创建 storage库、业务表、undo_log表
create database seata_storage;
use seata_storage;
DROP TABLE IF EXISTS `tb_storage`;
CREATE TABLE `tb_storage` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `undo_log`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`branch_id` BIGINT(20) NOT NULL,
`xid` VARCHAR(100) NOT NULL,
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL,
`log_status` INT(11) NOT NULL,
`log_created` DATETIME NOT NULL,
`log_modified` DATETIME NOT NULL,
`ext` VARCHAR(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8;
-- 初始化库存模拟数据
INSERT INTO seata_storage.tb_storage (id, commodity_code, count) VALUES (1, 'product-1', 9999999);
INSERT INTO seata_storage.tb_storage (id, commodity_code, count) VALUES (2, 'product-2', 0);

View File

@ -0,0 +1,28 @@
<?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-example</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-seata-storage</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-transaction</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.seata.storage;
import org.springblade.common.constant.LauncherConstant;
import org.springblade.core.cloud.feign.EnableBladeFeign;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.transaction.annotation.SeataCloudApplication;
/**
* Storage启动器
*
* @author Chill
*/
@EnableBladeFeign
@SeataCloudApplication
public class SeataStorageApplication {
public static void main(String[] args) {
BladeApplication.run(LauncherConstant.APPLICATION_SEATA_STORAGE_NAME, SeataStorageApplication.class, args);
}
}

View File

@ -0,0 +1,31 @@
package org.springblade.seata.storage.controller;
import lombok.AllArgsConstructor;
import org.springblade.seata.storage.service.IStorageService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* StorageController
*
* @author Chill
*/
@RestController
@AllArgsConstructor
@RequestMapping("storage")
public class StorageController {
private final IStorageService storageService;
/**
* 减库存
*
* @param commodityCode 商品代码
* @param count 数量
*/
@RequestMapping(path = "/deduct")
public int deduct(String commodityCode, Integer count) {
return storageService.deduct(commodityCode, count);
}
}

View File

@ -0,0 +1,26 @@
package org.springblade.seata.storage.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* storage
*
* @author Chill
*/
@Data
@TableName("tb_storage")
public class Storage implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
private String commodityCode;
private Long count;
}

View File

@ -0,0 +1,12 @@
package org.springblade.seata.storage.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.seata.storage.entity.Storage;
/**
* StorageMapper
*
* @author Chill
*/
public interface StorageMapper extends BaseMapper<Storage> {
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.springblade.seata.storage.mapper.StorageMapper">
</mapper>

View File

@ -0,0 +1,22 @@
package org.springblade.seata.storage.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.seata.storage.entity.Storage;
/**
* IStorageService
*
* @author Chill
*/
public interface IStorageService extends IService<Storage> {
/**
* 减库存
*
* @param commodityCode 商品代码
* @param count 数量
* @return boolean
*/
int deduct(String commodityCode, int count);
}

View File

@ -0,0 +1,30 @@
package org.springblade.seata.storage.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.seata.storage.entity.Storage;
import org.springblade.seata.storage.mapper.StorageMapper;
import org.springblade.seata.storage.service.IStorageService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* StorageServiceImpl
*
* @author Chill
*/
@Service
public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage> implements IStorageService {
@Override
@Transactional(rollbackFor = Exception.class)
public int deduct(String commodityCode, int count) {
Storage storage = baseMapper.selectOne(Wrappers.<Storage>query().lambda().eq(Storage::getCommodityCode, commodityCode));
if (storage.getCount() < count) {
throw new RuntimeException("超过库存数,扣除失败!");
}
storage.setCount(storage.getCount() - count);
return baseMapper.updateById(storage);
}
}

View File

@ -0,0 +1,15 @@
#服务器端口
server:
port: 8502
#数据源配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/seata_storage?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
username: root
password: root
#常规配置
blade:
mybatis-plus:
tenant-mode: false

View File

@ -0,0 +1,41 @@
<?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-example</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-sharding-jdbc</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<!-- bladex -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-swagger</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-tenant</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-sharding</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

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.sharding;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import static org.springblade.common.constant.LauncherConstant.APPLICATION_SHARDING_NAME;
/**
* 启动器
*
* @author Chill
*/
@BladeCloudApplication
public class ShardingApplication {
public static void main(String[] args) {
BladeApplication.run(APPLICATION_SHARDING_NAME, ShardingApplication.class, args);
}
}

View File

@ -0,0 +1,150 @@
/**
* 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.sharding.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.sharding.annotation.ShardingDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.sharding.entity.Notice;
import org.springblade.sharding.service.INoticeService;
import org.springblade.sharding.vo.NoticeVO;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 控制器
*
* @author Chill
*/
@RestController
@RequestMapping("notice")
@AllArgsConstructor
@Tag(name = "通知公告", description = "通知公告接口")
public class NoticeController extends BladeController {
private final INoticeService noticeService;
/**
* 新增或修改(使用sharding)
*/
@ShardingDS
@PostMapping("/submit")
@ApiOperationSupport(order = 1)
@Operation(summary = "新增或修改", description = "传入notice")
public R submit(@RequestBody Notice notice) {
return R.status(noticeService.saveOrUpdate(notice));
}
/**
* 分页(使用sharding)
*/
@ShardingDS
@GetMapping("/list")
@Parameters({
@Parameter(name = "category", description = "公告类型", in = ParameterIn.QUERY, schema = @Schema(type = "integer")),
@Parameter(name = "title", description = "公告标题", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
})
@ApiOperationSupport(order = 2)
@Operation(summary = "分页", description = "传入notice")
public R<IPage<Notice>> list(@Parameter(hidden = true) @RequestParam Map<String, Object> notice, Query query) {
IPage<Notice> pages = noticeService.page(Condition.getPage(query), Condition.getQueryWrapper(notice, Notice.class));
return R.data(pages);
}
/**
* 多表联合查询自定义分页
*/
@GetMapping("/page")
@Parameters({
@Parameter(name = "category", description = "公告类型", in = ParameterIn.QUERY, schema = @Schema(type = "integer")),
@Parameter(name = "title", description = "公告标题", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
})
@ApiOperationSupport(order = 3)
@Operation(summary = "分页", description = "传入notice")
public R<IPage<NoticeVO>> page(@Parameter(hidden = true) NoticeVO notice, Query query) {
IPage<NoticeVO> pages = noticeService.selectNoticePage(Condition.getPage(query), notice);
return R.data(pages);
}
/**
* 新增
*/
@PostMapping("/save")
@ApiOperationSupport(order = 4)
@Operation(summary = "新增", description = "传入notice")
public R save(@RequestBody Notice notice) {
return R.status(noticeService.save(notice));
}
/**
* 修改
*/
@PostMapping("/update")
@ApiOperationSupport(order = 5)
@Operation(summary = "修改", description = "传入notice")
public R update(@RequestBody Notice notice) {
return R.status(noticeService.updateById(notice));
}
/**
* 详情
*/
@GetMapping("/detail")
@ApiOperationSupport(order = 6)
@Operation(summary = "详情", description = "传入notice")
public R<Notice> detail(Notice notice) {
Notice detail = noticeService.getOne(Condition.getQueryWrapper(notice));
return R.data(detail);
}
/**
* 删除
*/
@ShardingDS
@PostMapping("/remove")
@ApiOperationSupport(order = 7)
@Operation(summary = "逻辑删除", description = "传入notice")
public R remove(@Parameter(name = "主键集合") @RequestParam String ids) {
boolean temp = noticeService.deleteLogic(Func.toLongList(ids));
return R.status(temp);
}
}

View File

@ -0,0 +1,81 @@
/**
* 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.sharding.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.mp.base.BaseEntity;
import java.io.Serial;
import java.util.Date;
/**
* 实体类
*
* @author Chill
*/
@Data
@TableName("blade_notice")
@EqualsAndHashCode(callSuper = true)
public class Notice extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
@Schema(description = "租户ID")
private String tenantId;
/**
* 标题
*/
@Schema(description = "标题")
private String title;
/**
* 通知类型
*/
@Schema(description = "通知类型")
private Integer category;
/**
* 发布日期
*/
@Schema(description = "发布日期")
private Date releaseTime;
/**
* 内容
*/
@Schema(description = "内容")
private String content;
}

View File

@ -0,0 +1,51 @@
/**
* 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.sharding.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.sharding.entity.Notice;
import org.springblade.sharding.vo.NoticeVO;
import java.util.List;
/**
* Mapper 接口
*
* @author Chill
*/
public interface NoticeMapper extends BaseMapper<Notice> {
/**
* 自定义分页
*
* @param page 分页
* @param notice 实体
* @return List<NoticeVO>
*/
List<NoticeVO> selectNoticePage(IPage page, NoticeVO notice);
}

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.springblade.sharding.mapper.NoticeMapper">
<!-- 通用查询映射结果 -->
<resultMap id="noticeResultMap" type="org.springblade.sharding.entity.Notice">
<result column="id" property="id"/>
<result column="create_user" property="createUser"/>
<result column="create_time" property="createTime"/>
<result column="update_user" property="updateUser"/>
<result column="update_time" property="updateTime"/>
<result column="status" property="status"/>
<result column="is_deleted" property="isDeleted"/>
<result column="release_time" property="releaseTime"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
</resultMap>
<!-- 通用查询映射结果 -->
<resultMap id="noticeVOResultMap" type="org.springblade.sharding.vo.NoticeVO">
<result column="id" property="id"/>
<result column="create_user" property="createUser"/>
<result column="create_time" property="createTime"/>
<result column="update_user" property="updateUser"/>
<result column="update_time" property="updateTime"/>
<result column="status" property="status"/>
<result column="is_deleted" property="isDeleted"/>
<result column="release_time" property="releaseTime"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
</resultMap>
<select id="selectNoticePage" resultMap="noticeVOResultMap">
SELECT
n.*,
d.dict_value AS categoryName
FROM
blade_notice n
LEFT JOIN ( SELECT * FROM blade_dict WHERE CODE = 'notice' ) d ON n.category = d.dict_key
WHERE
n.is_deleted = 0 and n.tenant_id = #{notice.tenantId}
<if test="notice.title!=null">
and n.title like concat(concat('%', #{notice.title}), '%')
</if>
<if test="notice.category!=null">
and n.category = #{notice.category}
</if>
</select>
</mapper>

View File

@ -0,0 +1,49 @@
/**
* 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.sharding.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
import org.springblade.sharding.entity.Notice;
import org.springblade.sharding.vo.NoticeVO;
/**
* 服务类
*
* @author Chill
*/
public interface INoticeService extends BaseService<Notice> {
/**
* 自定义分页
* @param page
* @param notice
* @return
*/
IPage<NoticeVO> selectNoticePage(IPage<NoticeVO> page, NoticeVO notice);
}

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.sharding.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.sharding.entity.Notice;
import org.springblade.sharding.mapper.NoticeMapper;
import org.springblade.sharding.service.INoticeService;
import org.springblade.sharding.vo.NoticeVO;
import org.springframework.stereotype.Service;
/**
* 服务实现类
*
* @author Chill
*/
@Service
public class NoticeServiceImpl extends BaseServiceImpl<NoticeMapper, Notice> implements INoticeService {
@Override
public IPage<NoticeVO> selectNoticePage(IPage<NoticeVO> page, NoticeVO notice) {
// 若不使用mybatis-plus自带的分页方法则不会自动带入tenantId所以我们需要自行注入
notice.setTenantId(AuthUtil.getTenantId());
return page.setRecords(baseMapper.selectNoticePage(page, notice));
}
}

View File

@ -0,0 +1,23 @@
package org.springblade.sharding.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.sharding.entity.Notice;
/**
* 通知公告视图类
*
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class NoticeVO extends Notice {
@Schema(description = "通知类型名")
private String categoryName;
@Schema(description = "租户编号")
private String tenantId;
}

View File

@ -0,0 +1,61 @@
#不分库仅分表
spring:
#主数据源
datasource:
# MySql
url: jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
username: root
password: root
#sharding数据源
shardingsphere:
datasource:
#定义的数据库集合
names: ds1
#数据库连接别名
ds1:
#数据连接池类型
type: com.alibaba.druid.pool.DruidDataSource
#驱动类型
driver-class-name: com.mysql.cj.jdbc.Driver
#jdbc地址
url: jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
#数据库账号
username: root
#数据库密码
password: root
rules:
sharding:
#主键生成类型
key-generators:
snowflake:
type: SNOWFLAKE
tables:
#分表代号
blade_notice:
#实际节点名称,格式为 库名$->{0..n1}.表名$->{0..n2}
#blade_notice_$->{1..2} 代表有 blade_notice_1和blade_notice_2两个表
actual-data-nodes: ds1.blade_notice_$->{1..2}
#分表策略
table-strategy:
standard:
#分表列名
sharding-column: id
#分表算法名,不可用下划线
sharding-algorithm-name: blade-notice-inline
#主键生成策略
key-generate-strategy:
#主键名
column: id
#策略算法
key-generator-name: snowflake
#分库分表算法
sharding-algorithms:
#分表算法名
blade-notice-inline:
#算法类型
type: INLINE
props:
#算法表达式
#表达式写法非常自由,以下配置仅作为参考
#blade_notice_$->{id % 2 + 1} 代表对id进行取余根据id的基偶来指向blade_notice_1和blade_notice_2
algorithm-expression: blade_notice_$->{id % 2 + 1}

View File

@ -0,0 +1,71 @@
#不分表仅分库
spring:
#主数据源
datasource:
# MySql
url: jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
username: root
password: root
#sharding数据源
shardingsphere:
datasource:
names: ds1,ds2
#数据库连接别名
ds1:
#数据连接池类型
type: com.alibaba.druid.pool.DruidDataSource
#驱动类型
driver-class-name: com.mysql.cj.jdbc.Driver
#jdbc地址
url: jdbc:mysql://localhost:3306/bladex1?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
#数据库账号
username: root
#数据库密码
password: root
#数据库连接别名
ds2:
#数据连接池类型
type: com.alibaba.druid.pool.DruidDataSource
#驱动类型
driver-class-name: com.mysql.cj.jdbc.Driver
#jdbc地址
jdbc-url: jdbc:mysql://localhost:3306/bladex2?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
#数据库账号
username: root
#数据库密码
password: root
rules:
sharding:
#主键生成类型
key-generators:
snowflake:
type: SNOWFLAKE
#默认分库策略
default-database-strategy:
standard:
#列名
sharding-column: id
#算法名
sharding-algorithm-name: database-inline
tables:
#表名代号
blade_notice:
#实际节点名称,格式为 库名$->{0..n1}.表名
actual-data-nodes: ds$->{1..2}.blade_notice
#主键生成策略
key-generate-strategy:
#主键名
column: id
#策略算法
key-generator-name: snowflake
#分库分表算法
sharding-algorithms:
#分库算法名
database-inline:
#算法类型
type: INLINE
props:
#算法表达式
#表达式写法非常自由,以下配置仅作为参考
#ds$->{id % 2 + 1} 代表对id进行取余根据id的基偶来指向 ds1和ds2
algorithm-expression: ds$->{id % 2 + 1}

View File

@ -0,0 +1,88 @@
#分库+分表
spring:
#主数据源
datasource:
# MySql
url: jdbc:mysql://localhost:3306/bladex?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
username: root
password: root
#sharding数据源
shardingsphere:
datasource:
names: ds1,ds2
#数据库连接别名
ds1:
#数据连接池类型
type: com.alibaba.druid.pool.DruidDataSource
#驱动类型
driver-class-name: com.mysql.cj.jdbc.Driver
#jdbc地址
url: jdbc:mysql://localhost:3306/bladex1?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
#数据库账号
username: root
#数据库密码
password: root
#数据库连接别名
ds2:
#数据连接池类型
type: com.alibaba.druid.pool.DruidDataSource
#驱动类型
driver-class-name: com.mysql.cj.jdbc.Driver
#jdbc地址
jdbc-url: jdbc:mysql://localhost:3306/bladex2?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
#数据库账号
username: root
#数据库密码
password: root
rules:
sharding:
#主键生成类型
key-generators:
snowflake:
type: SNOWFLAKE
#默认分库策略
default-database-strategy:
standard:
#列名
sharding-column: id
#算法名
sharding-algorithm-name: database-inline
tables:
#分表代号
blade_notice:
#实际节点名称,格式为 库名$->{0..n1}.表名$->{0..n2}
#ds$->{1..2}.blade_notice_$->{1..2} 代表有 ds1和ds2两个库以及blade_notice_1和blade_notice_2两个表
actual-data-nodes: ds$->{1..2}.blade_notice_$->{1..2}
#分表策略
table-strategy:
standard:
#分表列名
sharding-column: id
#分表算法名,不可用下划线
sharding-algorithm-name: blade-notice-inline
#主键生成策略
key-generate-strategy:
#主键名
column: id
#策略算法
key-generator-name: snowflake
#分库分表算法
sharding-algorithms:
#分库算法名
database-inline:
#算法类型
type: INLINE
props:
#算法表达式
#表达式写法非常自由,以下配置仅作为参考
#ds$->{id % 2 + 1} 代表对id进行取余根据id的基偶来指向 ds1和ds2
algorithm-expression: ds$->{id % 2 + 1}
#分表算法名
blade-notice-inline:
#算法类型
type: INLINE
props:
#算法表达式
#表达式写法非常自由,以下配置仅作为参考
#blade_notice_$->{id % 2 + 1} 代表对id进行取余根据id的基偶来指向blade_notice_1和blade_notice_2
algorithm-expression: blade_notice_$->{id % 2 + 1}

View File

@ -0,0 +1,18 @@
## server
server:
port: 8701
spring:
#sharding通用配置
shardingsphere:
mode:
#内存模式
type: Standalone
props:
#是否展示sql
sql-show: true
blade:
#开启分库分表自动配置
#nacos会覆盖此配置需要在nacos也配置为true
sharding:
enabled: true

View File

@ -0,0 +1,13 @@
FROM bladex/alpine-java:openjdk17_cn_slim
LABEL maintainer="bladejava@qq.com"
RUN mkdir -p /blade/xxljob
WORKDIR /blade/xxljob
EXPOSE 7008
COPY ./target/blade-xxljob.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"]

View File

@ -0,0 +1,22 @@
## 分布式任务调度服务启动
### XXL-JOB
1. 执行/doc/sql文件夹下的sql脚本
2. 拉取docker镜像
```shell
docker pull xuxueli/xxl-job-admin:2.4.0
```
3. 执行docker命令运行服务
```shell
docker run -d --add-host="host.docker.internal:host-gateway" \
-p 8080:8080 \
--restart=always \
--name xxl-job \
-e TZ="Asia/Shanghai" \
-e JAVA_OPTS="-Xms512M -Xmx512m" \
-e PARAMS="--spring.datasource.url=jdbc:mysql://host.docker.internal:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai --spring.datasource.username=root --spring.datasource.password=root" \
-v ~/docker/xxl-job:/data/applogs \
xuxueli/xxl-job-admin:2.4.0
```

View File

@ -0,0 +1,123 @@
-- --------------------------------------
-- XXL-JOB v2.4.0
-- Copyright (c) 2015-present, xuxueli.
-- --------------------------------------
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_unicode_ci;
use `xxl_job`;
SET NAMES utf8mb4;
CREATE TABLE `xxl_job_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_desc` varchar(255) NOT NULL,
`add_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`author` varchar(64) DEFAULT NULL COMMENT '作者',
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
`schedule_type` varchar(50) NOT NULL DEFAULT 'NONE' COMMENT '调度类型',
`schedule_conf` varchar(128) DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型',
`misfire_strategy` varchar(50) NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略',
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
`executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
`glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
`glue_source` mediumtext COMMENT 'GLUE源代码',
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID多个逗号分隔',
`trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态0-停止1-运行',
`trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
`trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
`trigger_code` int(11) NOT NULL COMMENT '调度-结果',
`trigger_msg` text COMMENT '调度-日志',
`handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
`handle_code` int(11) NOT NULL COMMENT '执行-状态',
`handle_msg` text COMMENT '执行-日志',
`alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态0-默认、1-无需告警、2-告警成功、3-告警失败',
PRIMARY KEY (`id`),
KEY `I_trigger_time` (`trigger_time`),
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_log_report` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
`running_count` int(11) NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
`suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
`fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_logglue` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
`glue_source` mediumtext COMMENT 'GLUE源代码',
`glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
`add_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_registry` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(50) NOT NULL,
`registry_key` varchar(255) NOT NULL,
`registry_value` varchar(255) NOT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_group` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型0=自动注册、1=手动录入',
`address_list` text COMMENT '执行器地址列表,多地址逗号分隔',
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
`role` tinyint(4) NOT NULL COMMENT '角色0-普通用户、1-管理员',
`permission` varchar(255) DEFAULT NULL COMMENT '权限执行器ID列表多个逗号分割',
PRIMARY KEY (`id`),
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_lock` (
`lock_name` varchar(50) NOT NULL COMMENT '锁名称',
PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`, `update_time`) VALUES (1, 'blade-xxl-job-executor', '示例执行器', 1, 'http://host.docker.internal:9999', '2024-04-01 00:00:00' );
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '测试任务', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
INSERT INTO `xxl_job_lock` ( `lock_name`) VALUES ( 'schedule_lock');
commit;

View File

@ -0,0 +1,59 @@
<?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-example</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-xxl-job</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<properties>
<xxljob.version>2.4.0</xxljob.version>
</properties>
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-biz-common</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-cloud</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-launch</artifactId>
</dependency>
<!--Job-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxljob.version}</version>
</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,44 @@
/**
* 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.xxljob;
import org.springblade.common.constant.LauncherConstant;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
/**
* 启动器
*
* @author Chill
*/
@BladeCloudApplication
public class XxlJobApplication {
public static void main(String[] args) {
BladeApplication.run(LauncherConstant.APPLICATION_XXLJOB_NAME, XxlJobApplication.class, args);
}
}

View File

@ -0,0 +1,77 @@
package org.springblade.xxljob.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* xxl-job config
*
* @author xuxueli
*/
@Slf4j
@Configuration(proxyBeanMethods = false)
public class XxlJobConfig {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.appname}")
private String appname;
@Value("${xxl.job.executor.address}")
private String address;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
log.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setAddress(address);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
/**
* 针对多网卡容器内部署等情况可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP
*
* 1引入依赖
* <dependency>
* <groupId>org.springframework.cloud</groupId>
* <artifactId>spring-cloud-commons</artifactId>
* <version>${version}</version>
* </dependency>
*
* 2配置文件或者容器启动变量
* spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
*
* 3获取IP
* String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
*/
}

View File

@ -0,0 +1,256 @@
package org.springblade.xxljob.handler;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* XxlJob开发示例Bean模式
* <p>
* 开发步骤
* 1任务开发在Spring Bean实例中开发Job方法
* 2注解配置为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")"注解value值对应的是调度中心新建任务的JobHandler属性的值
* 3执行日志需要通过 "XxlJobHelper.log" 打印执行日志
* 4任务结果默认任务结果为 "成功" 状态不需要主动设置如有诉求比如设置任务结果为失败可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果
*
* @author xuxueli 2019-12-11 21:52:51
*/
@Component
public class SampleXxlJob {
private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);
/**
* 1简单任务示例Bean模式
*/
@XxlJob("demoJobHandler")
public void demoJobHandler() throws Exception {
String param = XxlJobHelper.getJobParam();
XxlJobHelper.log("XXL-JOB, Hello World." + param);
for (int i = 0; i < 5; i++) {
XxlJobHelper.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2);
}
// default success
}
/**
* 2分片广播任务
*/
@XxlJob("shardingJobHandler")
public void shardingJobHandler() throws Exception {
// 分片参数
int shardIndex = XxlJobHelper.getShardIndex();
int shardTotal = XxlJobHelper.getShardTotal();
XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);
// 业务逻辑
for (int i = 0; i < shardTotal; i++) {
if (i == shardIndex) {
XxlJobHelper.log("第 {} 片, 命中分片开始处理", i);
} else {
XxlJobHelper.log("第 {} 片, 忽略", i);
}
}
}
/**
* 3命令行任务
*/
@XxlJob("commandJobHandler")
public void commandJobHandler() throws Exception {
String command = XxlJobHelper.getJobParam();
int exitValue = -1;
BufferedReader bufferedReader = null;
try {
// command process
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(command);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
//Process process = Runtime.getRuntime().exec(command);
BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream());
bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
// command log
String line;
while ((line = bufferedReader.readLine()) != null) {
XxlJobHelper.log(line);
}
// command exit
process.waitFor();
exitValue = process.exitValue();
} catch (Exception e) {
XxlJobHelper.log(e);
} finally {
if (bufferedReader != null) {
bufferedReader.close();
}
}
if (exitValue == 0) {
// default success
} else {
XxlJobHelper.handleFail("command exit value(" + exitValue + ") is failed");
}
}
/**
* 4跨平台Http任务
* 参数示例
* "url: http://www.baidu.com\n" +
* "method: get\n" +
* "data: content\n";
*/
@XxlJob("httpJobHandler")
public void httpJobHandler() throws Exception {
// param parse
String param = XxlJobHelper.getJobParam();
if (param == null || param.trim().length() == 0) {
XxlJobHelper.log("param[" + param + "] invalid.");
XxlJobHelper.handleFail();
return;
}
String[] httpParams = param.split("\n");
String url = null;
String method = null;
String data = null;
for (String httpParam : httpParams) {
if (httpParam.startsWith("url:")) {
url = httpParam.substring(httpParam.indexOf("url:") + 4).trim();
}
if (httpParam.startsWith("method:")) {
method = httpParam.substring(httpParam.indexOf("method:") + 7).trim().toUpperCase();
}
if (httpParam.startsWith("data:")) {
data = httpParam.substring(httpParam.indexOf("data:") + 5).trim();
}
}
// param valid
if (url == null || url.trim().length() == 0) {
XxlJobHelper.log("url[" + url + "] invalid.");
XxlJobHelper.handleFail();
return;
}
if (method == null || !Arrays.asList("GET", "POST").contains(method)) {
XxlJobHelper.log("method[" + method + "] invalid.");
XxlJobHelper.handleFail();
return;
}
boolean isPostMethod = method.equals("POST");
// request
HttpURLConnection connection = null;
BufferedReader bufferedReader = null;
try {
// connection
URL realUrl = new URL(url);
connection = (HttpURLConnection) realUrl.openConnection();
// connection setting
connection.setRequestMethod(method);
connection.setDoOutput(isPostMethod);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setReadTimeout(5 * 1000);
connection.setConnectTimeout(3 * 1000);
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
// do connection
connection.connect();
// data
if (isPostMethod && data != null && data.trim().length() > 0) {
DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
dataOutputStream.write(data.getBytes("UTF-8"));
dataOutputStream.flush();
dataOutputStream.close();
}
// valid StatusCode
int statusCode = connection.getResponseCode();
if (statusCode != 200) {
throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid.");
}
// result
bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
StringBuilder result = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
String responseMsg = result.toString();
XxlJobHelper.log(responseMsg);
return;
} catch (Exception e) {
XxlJobHelper.log(e);
XxlJobHelper.handleFail();
return;
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (Exception e2) {
XxlJobHelper.log(e2);
}
}
}
/**
* 5生命周期任务示例任务初始化与销毁时支持自定义相关逻辑
*/
@XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
public void demoJobHandler2() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
}
public void init() {
logger.info("init");
}
public void destroy() {
logger.info("destroy");
}
}

View File

@ -0,0 +1,25 @@
server:
port: 7008
logging:
config: classpath:logback.xml
spring:
main:
allow-circular-references: true
xxl:
job:
accessToken: default_token
admin:
addresses: http://127.0.0.1:8080/xxl-job-admin
executor:
# 执行器名称
appname: blade-xxl-job-executor
# 若使用docker部署xxl-job-admin并且本地开发机调试连接需要开启指定ip采用docker的internal配置
# 若生产部署不涉及跨网段可不配置ip
ip: 'http://host.docker.internal'
port: 9999
logpath: ../data/applogs/xxl-job/jobhandler
logretentiondays: -1
address: ''

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="false">
<contextName>logback</contextName>
<property name="log.path" value="../data/applogs/xxl-job/xxl-job-executor-sample-springboot.log"/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
</configuration>

36
blade-example/pom.xml Normal file
View File

@ -0,0 +1,36 @@
<?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-Biz</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-example</artifactId>
<name>${project.artifactId}</name>
<packaging>pom</packaging>
<description>BladeX 微服务范例集合</description>
<modules>
<module>blade-apollo</module>
<module>blade-mqtt-broker</module>
<module>blade-mqtt-client</module>
<module>blade-rabbit-listener</module>
<module>blade-rabbit-publisher</module>
<module>blade-seata-order</module>
<module>blade-seata-storage</module>
<module>blade-sharding-jdbc</module>
<module>blade-xxl-job</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-biz-common</artifactId>
</dependency>
</dependencies>
</project>

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-Biz</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-biz-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;
}

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