593 Commits

Author SHA1 Message Date
zhouhao
e395e4041e build: 升级依赖 2025-07-23 10:06:47 +08:00
zhouhao
e134dfec6f build: 升级依赖版本 2025-06-30 13:54:01 +08:00
zhouhao
3a45ac86da refactor: 升级依赖 2025-06-19 16:05:08 +08:00
zhouhao
f99b648b88 refactor: 优化兼容性 2025-06-12 14:05:48 +08:00
zhouhao
d4645ad089 refactor: 优化查询条件转换 2025-06-03 19:14:08 +08:00
zhouhao
88e0dd4667 Merge remote-tracking branch 'origin/master' 2025-05-27 10:41:27 +08:00
zhouhao
1bd136f3d2 refactor: 移除无用统计 2025-05-27 10:41:14 +08:00
Zhang Ji
fc651ecd9d fix(场景联动): 修复执行动作的内置参数获取可能失效的问题 (#636) 2025-05-26 19:03:49 +08:00
zhouhao
dbceadc5e0 Merge remote-tracking branch 'origin/master' 2025-05-20 15:32:09 +08:00
zhouhao
520ca24fed refactor: 增加ui resource接口 2025-05-20 15:31:49 +08:00
laokou
a6100d36f9 fix: 修复MqttClient网络组件定义的元数据名称和类型错误 (#632) 2025-05-06 09:25:11 +08:00
zhouhao
80ed50211a refactor: 优化标签存储 2025-04-29 18:11:00 +08:00
zhouhao
3a1f0b65de fix: 修复设备标签无法更新问题 2025-04-28 18:33:07 +08:00
zhouhao
2cfc78f1da Merge remote-tracking branch 'origin/master' 2025-04-28 11:41:26 +08:00
zhouhao
ad34196a2a refactor: mqtt服务接入支持onClientConnect 2025-04-28 11:41:08 +08:00
老周
1e37c685cc Update README.md 2025-04-27 18:12:06 +08:00
老周
7fdfe87e9d build: 2.3 版本发布 (#615)
* build: 2.3 版本发布

* feat: 更新网关及设备管理相关模块代码 (#614)

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>

* feat(community): 场景联动以及通知模块2.3相关功能更新 (#612)

* feat(community): 场景联动以及通知模块2.3相关功能更新

* Update pull_request.yml

* feat(community): 场景联动清除资产相关代码

* fix(场景联动): 修复场景初始化问题

* feat(community): 注册个人订阅通道

* fix(通知模块): 订阅通道初始化

* fix(通知模块): 删除无用类

* fix(community): 解决合并冲突

---------

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>
Co-authored-by: 老周 <zh.sqy@qq.com>

* build: 升级依赖版本

* feat(community): 认证模块更新2.3相关功能 (#616)

* feat(community): 认证模块更新2.3相关功能

* fix(community): 修复自定义查询报错的问题

* fix(community): 优化邮件附件名称获取

---------

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>

* feat(community): 补充国际化相关内容,修复设备上线/离线没有日志的问题 (#619)

* feat(community): 补充国际化相关内容,修复设备上线/离线没有日志的问题

* feat(community): 补充设备物模型映射接口

---------

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>

* fix(notify): 修复消息通知相关问题 (#620)

* fix(notify): 修复消息通知相关问题

* fix(notify): 修复订阅管理数据展示错误问题

---------

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>

* fix(rule-engine): 场景联动相关bug修复 (#621)

* fix(rule-engine): 场景联动相关bug修复

* fix(rule-engine): 场景联动相关bug修复

* fix(rule-engine): 场景联动相关bug修复

* fix(rule-engine): 修复程序启动时订阅提供商禁用,但用户已订阅仍加载成功并订阅的问题

---------

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>

* fix(community): 修复认证中心以及设备相关的bug (#622)

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>

* feat(auth): 增加密码校验功能 (#625)

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>

* fix(auth): 修复组织父节点清除失败问题 (#627)

* fix(auth): 修复组织父节点清除失败问题

* fix(store): 补充存储模式名称国际化方法

---------

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>

* fix(auth): 修复用户绑定组织,查询用户关联组织失败的问题 (#628)

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>

* build: 升级依赖版本

---------

Co-authored-by: fighter-wang <118291973+fighter-wang@users.noreply.github.com>
Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>
2025-04-25 19:15:32 +08:00
老周
08a358ffab Update pull_request.yml 2025-03-28 09:52:51 +08:00
zhouhao
0325eaddbf refactor: 优化设备注册逻辑 2025-03-26 14:46:10 +08:00
zhouhao
f640e2b45f refactor: 优化会话处理逻辑 2025-03-26 11:06:02 +08:00
zhouhao
091b8c4f0c fix: 修复编译错误 2025-03-24 10:13:21 +08:00
zhouhao
d3adf0e752 refactor: 优化设备会话持久化逻辑 2025-03-24 09:53:50 +08:00
zhouhao
7b7b96f096 refactor: 优化DeviceGatewayHelper 2025-03-19 10:54:27 +08:00
zhouhao
c01d31606c refactor: 优化资源释放 2025-03-13 10:25:32 +08:00
老周
fd2d73d4eb Update README.md 2025-02-17 09:12:45 +08:00
老周
2e487a42e2 feat(基础模块): 增加命令模式支持 (#607)
可使用 CommandSupportManagerProviders相关方法来执行服务命令进行解耦.
2025-02-13 12:33:18 +08:00
bestfeng1020
694595d446 fix(docker配置): 修复文件路径挂载错误 (#602)
* fix(docker配置): 修复文件路径挂载错误

* Update docker-compose.yml

---------

Co-authored-by: 老周 <zh.sqy@qq.com>
2025-02-12 11:52:22 +08:00
Zhang Ji
27a9fd6618 feat(场景联动): 添加设备数据执行动作,扩展数组条件,优化国际化 (#605) 2025-01-23 18:57:15 +08:00
bestfeng1020
ad6279ed22 feat(readme): 添加新QQ群信息 (#604)
feat(readme): 添加新QQ群信息
2025-01-13 15:29:35 +08:00
bestfeng1020
bc0c01d01f fix(通用模块): 修复TDengine排序逻辑 (#599) 2024-12-31 10:35:05 +08:00
fighter-wang
6e40f6fb8a fix(基础模块): 修复阿里云语音仅拨打一个用户号码的问题 (#597)
Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>
2024-12-18 16:15:42 +08:00
zhouhao
647f6ec9f3 fix(基础模块): 修复mqtt client 设备会话恢复后gatewayId为null问题 2024-12-16 17:52:17 +08:00
老周
7d79927d4d Merge pull request #594 from zxl1951/fix-td-save
fix(存储策略): 涛思数据库存储使用无模式写入创建表缺失messageId列
2024-12-11 19:36:27 +08:00
ZxL
806db31c4a fix(存储策略): 涛思数据库存储使用无模式写入创建表缺失messageId列 2024-12-11 17:17:36 +08:00
老周
cd6f2f1049 Merge pull request #593 from jetlinks/bestfeng1020-patch-1
feat(readme): 删除DTU售卖信息
2024-12-11 10:06:40 +08:00
bestfeng1020
89eb2d66a8 feat(readme): 删除DTU售卖信息
feat(readme): 删除DTU售卖信息
2024-12-11 09:53:49 +08:00
fighter-wang
9c26d7d6c3 feat(设备管理): 新增在reactorQL中获取设备属性上报时间 (#592)
Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>
2024-12-09 13:53:14 +08:00
fighter-wang
b890f4e520 fix(关系配置): 关系配置增加反转关系名称字段 (#591) 2024-12-05 19:50:57 +08:00
zhouhao
f2bed57dc6 Merge remote-tracking branch 'origin/master' 2024-12-05 16:57:17 +08:00
zhouhao
13dd4e5e40 refactor: 优化mqtt 客户端配置 2024-12-05 16:56:59 +08:00
bestfeng1020
13ea87ff46 feat(docker镜像配置): 修改docker镜像版本 (#590) 2024-12-03 19:40:46 +08:00
zhouhao
d2772e8881 refactor: 优化列式存储策略数字类型转换逻辑 2024-11-06 15:50:59 +08:00
zhouhao
b5944c6d3a fix: #580 2024-10-29 15:02:06 +08:00
zhouhao
77abf004f7 Merge remote-tracking branch 'origin/master' 2024-10-21 17:26:09 +08:00
zhouhao
945bf2ea82 refactor(基础模块): 优化设备连接 2024-10-21 17:25:56 +08:00
dependabot[bot]
b83dc75943 build(deps): bump commons-io:commons-io from 2.11.0 to 2.15.1 in /jetlinks-components/io-component (#576)
* build(deps): bump commons-io:commons-io

Bumps commons-io:commons-io from 2.11.0 to 2.14.0.

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update pom.xml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 老周 <zh.sqy@qq.com>
2024-10-08 13:32:20 +08:00
dependabot[bot]
cc55f4ad6c build(deps): bump org.elasticsearch:elasticsearch (#546)
Bumps [org.elasticsearch:elasticsearch](https://github.com/elastic/elasticsearch) from 7.17.13 to 7.17.23.
- [Release notes](https://github.com/elastic/elasticsearch/releases)
- [Changelog](https://github.com/elastic/elasticsearch/blob/main/CHANGELOG.md)
- [Commits](https://github.com/elastic/elasticsearch/compare/v7.17.13...v7.17.23)

---
updated-dependencies:
- dependency-name: org.elasticsearch:elasticsearch
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 09:41:40 +08:00
XiXuanHao
9b9545ecca fix(产品管理): 产品启用时会重复触发DeviceProductDeployEvent事件 (#513)
Co-authored-by: XIXUANHAO <xi_xh@foxmail.com>
2024-10-08 09:40:17 +08:00
zhouhao
6dc16ce715 Merge remote-tracking branch 'origin/master' 2024-09-27 11:09:34 +08:00
zhouhao
e3d7831073 build(maven): 2.3.0-SNAPSHOT 2024-09-27 11:09:22 +08:00
PengyuDeng
e855993511 fix: 修改日志文件的文件名 (#573) 2024-09-14 16:19:45 +08:00
zhouhao
01d0a033d3 Merge remote-tracking branch 'origin/master' 2024-09-12 15:41:18 +08:00
zhouhao
f1733e5880 build: 升级jetlinks-core版本1.2.3-SNAPSHOT 2024-09-12 15:41:05 +08:00
fighter-wang
0f78fec18f feat(设备管理): 增加解析文件为属性物模型功能 (#569)
Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>
2024-09-12 11:57:10 +08:00
bestfeng1020
4d9e8c5b00 fix(README): 修正二次开发文档链接 (#571)
fix(README): 修正二次开发文档链接
2024-09-11 16:41:26 +08:00
zhouhao
600766c4a5 Merge remote-tracking branch 'origin/master' 2024-09-10 17:06:53 +08:00
zhouhao
e8b6f27afe feat: 优化LocalFileThingsDataManager性能 2024-09-10 17:06:39 +08:00
fighter-wang
824466037b feat(菜单管理): 新增清空菜单授权功能 (#570)
Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>
2024-09-06 14:55:20 +08:00
fighter-wang
bf7e19c92e fix(邮件通知): 优化获取邮件附件,过大时的抛错国际化 (#568)
* fix(邮件通知): 优化获取邮件附件,过大时的抛错国际化

* fix(邮件通知): 优化使用通用的国际化信息

* fix(邮件通知): 代码优化

---------

Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>
2024-09-03 18:32:39 +08:00
Zhang Ji
3ce520ebe6 fix(场景联动): 修复重启服务后,场景联动未初始化的问题 (#566) 2024-09-02 12:23:46 +08:00
PengyuDeng
412fbd6e2c feat(权限管理): 分页获取用户详情,新增组织信息 (#560) 2024-09-02 09:33:12 +08:00
jk9991xx
594b2937d5 refactor: 移除重复代码
Co-authored-by: lmx <18570651508@wo.cn>
2024-09-02 09:32:15 +08:00
fighter-wang
bf5ba69bcb fix(场景联动): 修复场景联动使用指标值无法触发问题 (#562)
Co-authored-by: fighter-wang <11291691+fighter-wang@user.noreply.gitee.com>
2024-08-29 15:50:38 +08:00
zhouhao
845206fe76 feat: 优化topic解析 2024-08-29 12:02:32 +08:00
fighter-wang
99e15697db fix(场景联动): 修复场景联动中设备属性翻译问题 (#561) 2024-08-28 16:39:53 +08:00
老周
39687d8302 build(maven): Release 2.2.0 (#557)
* build(maven): Release 2.2.0

* build(maven): 升级spring版本
2024-08-21 11:44:58 +08:00
liusq
f754740646 feat(rule-engine): 告警场景2.2相关功能更新 (#552) 2024-08-21 10:58:03 +08:00
PengyuDeng
f946c97ce5 refactor(基础模块): 优化菜单ID生成策略. (#556) 2024-08-19 17:17:58 +08:00
fighter-wang
65e5ac9046 fix(场景联动): 修复保存场景联动时缺少actionId以及features字段问题 (#549) 2024-08-07 10:17:57 +08:00
zhouhao
c2ae9306a2 Merge remote-tracking branch 'origin/master' 2024-08-02 16:28:00 +08:00
zhouhao
7322bf1fe7 refactor(基础模块): 优化存储策略以及ID生成策略. 2024-08-02 16:27:49 +08:00
PengyuDeng
a9b2754a41 feat(系统监控): 监控信息推送至消息总线。 (#544) 2024-07-19 15:00:19 +08:00
PengyuDeng
19604d8328 add(场景联动): 增加批量启动、禁用场景的接口 (#539) 2024-07-16 09:36:18 +08:00
老周
2dd0ef3c93 feat(基础模块): MqttClient设备会话支持可恢复. (#538) 2024-07-10 10:59:22 +08:00
PengyuDeng
8b3509442f refactor(告警模块):告警级别信息增加拓展 (#536)
用户可能需要对告警级别所含信息进行拓展,如我司每个告警级别对应一个不同的播报音。播报音在前端配置,由浏览器读出。
2024-07-08 16:27:50 +08:00
PengyuDeng
ed6540fbca add(规则引擎-场景联动): 场景联动增加读取属性后回复触发场景联动的拓展 (#533)
需要结合前端一起拓展,前端的实现基于属性上报,把属性上报reportProperty修改为eadPropertyReply即可。
2024-07-08 10:12:07 +08:00
fighter-wang
a4b48c7ec5 fix(消息通知管理): 修复订阅管理中订阅配置关闭后,查看个人中心依然展示订阅配置及通道问题 (#534) 2024-07-05 17:12:38 +08:00
PengyuDeng
1ad1e448ee refactor(基础模块): 优化es索引配置,增加拓展性。 (#526) 2024-06-24 10:34:59 +08:00
fighter-wang
fee733672c fix(消息通知模块): 修复场景联动设备告警短信通知无法发送问题 (#525) 2024-06-21 16:01:55 +08:00
fighter-wang
fdd8f4004c fix(设备模块): 新增网关设备批量解绑网关子设备功能 (#522) 2024-06-19 09:44:01 +08:00
bestfeng1020
8515c9b385 fix(告警记录): 添加通过告警配置Id查询告警日志接口 (#520) 2024-06-17 12:40:45 +08:00
zhouhao
7e24f3d006 refactor: 优化PersistenceBuffer逻辑 2024-06-11 11:05:41 +08:00
zhouhao
af11c55ad9 refactor: 优化tcp判断 2024-05-31 10:18:18 +08:00
zhouhao
0bdc14668e Merge remote-tracking branch 'origin/master' 2024-05-30 18:25:40 +08:00
zhouhao
173680bd92 refactor: ThingsDataManager增加标签实现 2024-05-30 18:25:26 +08:00
Zhang Ji
76d1b07210 fix(TDengine): 修复like条件语法错误 (#514) 2024-05-30 10:02:59 +08:00
zhouhao
3d722235c0 refactor: 优化系统配置逻辑 2024-05-27 17:27:11 +08:00
Zhang Ji
53a5d0c8e5 fix(网络组件): 修复关闭mqtt网关禁用逻辑错误 (#512) 2024-05-24 16:44:56 +08:00
tancong
2b8f571e9a feat(规则引擎): 增加场景分支executeAnyway配置.优化场景条件分支逻辑. (#511) 2024-05-23 17:12:50 +08:00
zhouhao
86b073b389 refactor: 统一bouncycastle版本 2024-05-21 17:42:57 +08:00
zhouhao
a0c17312cc Merge branch 'refactor-session-duration' 2024-05-21 16:50:34 +08:00
zhouhao
39eb4eb47d Merge branch 'master' of github.com:jetlinks/jetlinks-community 2024-05-21 16:50:26 +08:00
zhouhao
a819096019 refactor: 优化索引模版创建逻辑 2024-05-21 09:45:44 +08:00
老周
dcc43cb47a refactor: 修改设备会话统计时长类型为long (#510) 2024-05-14 10:45:28 +08:00
zhouhao
3d7d3efc86 refactor: 修改设备会话统计时长类型为long 2024-05-14 10:41:43 +08:00
fighter-wang
8d7d900289 fix(短信通知模块): 修复产品/设备发生告警,短信通知内置函数未进行格式化问题 (#507) 2024-05-09 19:23:38 +08:00
fighter-wang
ff0f7ddfa2 fix(场景联动): 修复场景联动触发条件,当选择为事件时,json参数为enum,没有enum枚举选项问题 (#503) 2024-05-06 17:44:31 +08:00
dependabot[bot]
884d7ea34f build(deps): bump org.bouncycastle:bcprov-jdk18on from 1.76 to 1.78 (#501)
* build(deps): bump org.bouncycastle:bcprov-jdk18on from 1.76 to 1.78

Bumps [org.bouncycastle:bcprov-jdk18on](https://github.com/bcgit/bc-java) from 1.76 to 1.78.
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcprov-jdk18on
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update pom.xml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 老周 <zh.sqy@qq.com>
2024-05-06 17:44:07 +08:00
fighter-wang
16599ba067 fix(消息通知): 修复产品类型告警,展示产品ID 转换为 展示产品名称 (#499) 2024-04-24 14:01:15 +08:00
zhouhao
9157c88024 refactor: 优化子设备注销消息逻辑 2024-04-19 11:34:13 +08:00
zhouhao
a83ba95124 refactor: 优化nashorn支持 2024-04-19 11:33:23 +08:00
zhouhao
05abda43c7 fix: 修复标签条件获取错误问题 2024-04-18 14:07:10 +08:00
zhouhao
49345c9062 fix: 修复无法访问文件问题 2024-04-07 16:16:01 +08:00
老周
16fee41d89 refactor: 优化文件地址逻辑 (#493)
* refactor: 优化文件地址逻辑
2024-04-07 15:01:18 +08:00
老周
b61b0e3568 refactor: 使用新的协议加载逻辑. (#491)
使用eventbus来传递协议变更事件
2024-04-07 15:00:19 +08:00
zhouhao
cf2c781414 fix: 修复2字节时小端模式失效问题 2024-03-28 15:12:22 +08:00
bestfeng1020
0f874dd4fb 修改付费支持价格单位 (#485) 2024-03-27 16:37:13 +08:00
老周
a6281edb78 refactor(基础模块): 使用FileManager来存储静态文件 (#484) 2024-03-27 15:37:47 +08:00
zhouhao
0074468c29 fix(基础模块): 修复es查询返回array类型数据转换错误问题. 2024-03-12 18:02:31 +08:00
zhouhao
121c065d27 refactor: 优化设备上线离线消息时间戳逻辑 2024-03-12 12:32:33 +08:00
zhouhao
330930c2e1 refactor: 优化聚合查询逻辑 2024-03-12 10:02:40 +08:00
zhouhao
cfc4f877a7 refactor: 优化Setting保存逻辑 2024-02-26 10:56:04 +08:00
zhouhao
3e11d6500d refactor: 移除无用依赖 2024-01-31 10:05:14 +08:00
zhouhao
b77cdcb606 refactor: 优化设备接入网关,支持decodeContext.handleMessage 2024-01-25 16:34:33 +08:00
zhouhao
3e8c477b1b Merge remote-tracking branch 'origin/master' 2024-01-11 17:22:09 +08:00
zhouhao
28d323a2f8 refactor: 优化设备注销逻辑 2024-01-11 17:21:55 +08:00
老周
828f2743e6 Update README.md 2024-01-08 11:03:59 +08:00
zhouhao
270583033a Merge remote-tracking branch 'origin/master' 2023-12-28 15:58:10 +08:00
zhouhao
0b6c8451d3 refactor: 优化设备数据查询 2023-12-28 15:57:46 +08:00
PengyuDeng
454fa46df1 add(文件管理): 增加删除文件接口 (#466)
---------

Co-authored-by: 老周 <zh.sqy@qq.com>
2023-12-27 16:32:15 +08:00
zhouhao
73fa7607e9 Merge remote-tracking branch 'origin/master' 2023-12-27 10:06:23 +08:00
zhouhao
a7114c6591 refactor: 优化包含字符串的处理逻辑 2023-12-27 10:06:09 +08:00
fighter-wang
46360dfa66 fix(设备管理): 修复数据查询设备指定属性列表数据不全问题 (#463) 2023-12-26 13:59:29 +08:00
PengyuDeng
9b87ed740c fix: 配置文件增加接口扫描路径 (#464) 2023-12-26 13:59:06 +08:00
zhouhao
a20814b68d refactor: 初始化数据时不触发crud事件 2023-12-21 10:13:10 +08:00
zhouhao
27ff0510a6 refactor: 优化文件导入 2023-12-20 14:58:58 +08:00
老周
7f028885fe fix(基础模块): 修复在极端情况下物属性缓存可能出现污染. (#458) 2023-12-18 17:48:22 +08:00
tancong
a780869dd3 fix(订阅模块): 其他用户无法订阅消息修复 (#455) 2023-12-12 17:39:32 +08:00
liusq
51f7afc9e1 feat(文档): 更新项目结构说明 (#453) 2023-12-06 18:24:38 +08:00
liusq
4cb4422537 feat(角色分组): 社区版移植角色分组功能 (#451) 2023-12-01 13:36:53 +08:00
ningqingsheng
a5b3039be9 feat: 文件管理api文档补充 (#447) 2023-11-30 10:28:09 +08:00
zhouhao
eb2dabeebb Merge remote-tracking branch 'origin/master' 2023-11-28 17:02:44 +08:00
zhouhao
acf38bc7e1 refactor: 优化规则日志记录逻辑 2023-11-28 17:02:34 +08:00
dependabot[bot]
6203f481d9 build(deps): bump org.elasticsearch:elasticsearch (#446)
Bumps [org.elasticsearch:elasticsearch](https://github.com/elastic/elasticsearch) from 7.17.13 to 7.17.14.
- [Release notes](https://github.com/elastic/elasticsearch/releases)
- [Changelog](https://github.com/elastic/elasticsearch/blob/main/CHANGELOG.md)
- [Commits](https://github.com/elastic/elasticsearch/compare/v7.17.13...v7.17.14)

---
updated-dependencies:
- dependency-name: org.elasticsearch:elasticsearch
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-23 10:02:29 +08:00
tancong
af02ab4fb5 fix(用户): 超级管理员初始化类型修改 (#443) 2023-11-16 10:13:31 +08:00
tancong
47975b0da2 feat(告警模块): 其他类型的告警,每个场景联动产生一条告警记录。告警日志添加告警说明 (#441) 2023-11-13 19:03:40 +08:00
tancong
d9603381e0 feat(用户模块): 添加用户类型查询 (#438) 2023-11-08 10:53:33 +08:00
zhouhao
4f1c89ccc2 Merge remote-tracking branch 'origin/master' 2023-11-06 17:32:43 +08:00
zhouhao
f0a59a6e07 refactor: 优化MQTT设备接入网关 2023-11-06 17:32:31 +08:00
ningqingsheng
b078d88f82 fix: 修复并完善ReactorUtils.limit()方法 (#436) 2023-11-03 11:31:16 +08:00
zhouhao
efad62a75b refactor: 优化设备接入网关 2023-11-02 14:16:30 +08:00
zhouhao
edd1b9aba2 Merge remote-tracking branch 'origin/master' 2023-11-01 09:26:47 +08:00
zhouhao
865b0ec8ec refactor: 优化通知模版处理逻辑 2023-11-01 09:26:37 +08:00
dependabot[bot]
a1e88bc608 build(deps): bump org.json:json from 20230227 to 20231013 (#435)
Bumps [org.json:json](https://github.com/douglascrockford/JSON-java) from 20230227 to 20231013.
- [Release notes](https://github.com/douglascrockford/JSON-java/releases)
- [Changelog](https://github.com/stleary/JSON-java/blob/master/docs/RELEASES.md)
- [Commits](https://github.com/douglascrockford/JSON-java/commits)

---
updated-dependencies:
- dependency-name: org.json:json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 17:05:07 +08:00
老周
2814fac218 fix: 修复在设备里单独定义物模型时,订阅的数据格式不对问题. (#432)
* fix: 修复在设备里单独定义物模型时,订阅的数据格式不对问题.

* fix: 修复编译错误
2023-10-31 16:03:14 +08:00
dependabot[bot]
827dc1ef6b build(deps): bump org.elasticsearch:elasticsearch from 7.17.5 to 7.17.13 (#434)
Bumps [org.elasticsearch:elasticsearch](https://github.com/elastic/elasticsearch) from 7.17.5 to 7.17.13.
- [Release notes](https://github.com/elastic/elasticsearch/releases)
- [Changelog](https://github.com/elastic/elasticsearch/blob/main/CHANGELOG.md)
- [Commits](https://github.com/elastic/elasticsearch/compare/v7.17.5...v7.17.13)

---
updated-dependencies:
- dependency-name: org.elasticsearch:elasticsearch
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 16:02:49 +08:00
老周
a46c4fd07c fix: 修复阿里云语音通知可能提示参数过长问题 (#433) 2023-10-30 10:06:46 +08:00
Zhang Ji
807f1629ca fix(设备消息): 修复设备消息订阅报错 (#431)
PropertyMessage类型没有默认的解码实现,已改为订阅ThingMessage
2023-10-26 14:08:30 +08:00
zhouhao
7746a59b03 fix: 修复启动报错 2023-10-20 12:38:58 +08:00
老周
98861ac1ed refactor: 使用新的eventbus实现,增加相关订阅优先级支持. (#430) 2023-10-20 11:37:21 +08:00
老周
00997d9ee2 fix: 修复ThingsDataManager获取属性缓存数据可能错误. (#429) 2023-10-20 09:45:20 +08:00
老周
8b4ae09cf5 refactor: 设备接入网关同一个连接上报的消息使用串行处理. (#427) 2023-10-18 22:12:17 -05:00
bestfeng1020
cb17ae9d8b 定价修改 (#426)
修改硬件支持定价为699
2023-10-17 06:10:36 -05:00
zhouhao
9ef7f41aa9 revert: 回退reactor版本到 2020.0.31 2023-10-16 17:39:25 +08:00
老周
0fe35ae1ec build:使用bcprov-jdk18on替代bcprov-jdk15on (#424) 2023-10-06 20:55:01 -05:00
zhouhao
19f90e3cef refactor: 设置默认externalHost为127.0.0.1 2023-09-26 10:12:49 +08:00
zhouhao
448b132c56 Merge remote-tracking branch 'origin/master' 2023-09-19 15:31:19 +08:00
zhouhao
1cfecd50ec build: 统一bouncycastle版本 2023-09-19 15:31:07 +08:00
老周
5e849c882c build: 升级依赖版本 (#422) 2023-09-19 02:28:38 -05:00
bestfeng1020
f79ca77c9d fix(ES查询): 解决动态terms条件转ES查询条件错误问题 (#421) 2023-09-13 14:49:53 +08:00
bestfeng1020
fdc0cbd7e0 feat(联系方式): 添加qq群5联系方式 (#418)
* feat(联系方式): 添加qq群5联系方式

* Update README.md

---------

Co-authored-by: 老周 <zh.sqy@qq.com>
2023-08-31 10:25:40 +08:00
老周
fcfbeaa373 build: 升级guava版本到32.1.2-jre (#416) 2023-08-29 09:22:52 +08:00
bestfeng1020
6abafda194 fix(docker): 规范社区办docker镜像命名 (#415)
* fix(docker): 规范社区办docker镜像命名

* fix(docker): 规范社区办docker镜像命名
2023-08-29 09:20:31 +08:00
zhouhao
4de7de6148 build: 优化.editorconfig配置内容 2023-08-28 14:45:09 +08:00
老周
b0027125ea feat: 切换新的DeviceDataManager实现,使用ThingsDataManager桥接实现。 (#412) 2023-08-18 11:50:11 +08:00
老周
d972bde5c6 feat: 透传数据解析脚本中增加json()和jsonArray() api. (#410) 2023-08-18 10:36:33 +08:00
老周
3420f36110 fix: 修复透传解析脚本无法使用onDownstream,onUpstream函数注册回调. (#409) 2023-08-18 09:44:22 +08:00
老周
f84621178b fix: 修复开启链路追踪后可能报错问题 (#407) 2023-08-17 16:10:52 +08:00
老周
7d750a1e0c build: 优化maven子模块的relativePath配置 (#406) 2023-08-17 16:10:35 +08:00
老周
85b35bc320 build: 升级 jetlinks core,easyorm,hsweb 依赖版本 (#405)
jetlinks.version:1.2.2-SNAPSHOT
easyorm.version:4.1.2-SNAPSHOT
hsweb.framework.version: 4.0.17-SNAPSHOT
2023-08-17 14:32:16 +08:00
tancong
0ec335c2d9 feat(tcp解析): 粘拆包脚本解析器相关补全提示 (#404) 2023-08-16 13:47:21 +08:00
tancong
20276d0c19 refactor(设备模块): 添加指标查询和保存指标功能 (#403) 2023-08-09 11:57:58 +08:00
老周
7ffcf4bc4b build: 升级依赖 reactor-excel:1.0.6-SNAPSHOT (#402) 2023-08-08 16:58:11 +08:00
tancong
118065b15c fix(场景联动): 解决指标场景告警不触发问题 (#398) 2023-08-08 10:50:17 +08:00
zhouhao
276ffdf100 build: 开启下一个版本 2.2.0-SNAPSHOT 2023-08-07 14:03:58 +08:00
PengyuDeng
dacee03833 doc(告警模块):修改有歧义的字段描述(#396) 2023-08-07 09:21:58 +08:00
tancong
25da12e83c fix(场景联动): 解决并行场景告警不触发问题 (#394) 2023-08-04 21:00:26 +08:00
zhouhao
66eee1bf26 Merge remote-tracking branch 'origin/master' 2023-08-04 18:42:42 +08:00
zhouhao
ec53681151 build: 升级maven依赖版本 2023-08-04 18:42:30 +08:00
tancong
21646b4024 fix(设备模块): 解决tag枚举类型设置无参数问题 (#393) 2023-08-04 15:48:19 +08:00
老周
e0f980aed9 Update maven-wrapper.properties 2023-08-04 15:46:30 +08:00
zhouhao
ab7e603706 fix(规则引擎): 修复mysql下索引长度错误问题 2023-08-03 14:17:06 +08:00
zhouhao
83a6f39ea0 feat(基础模块): 增加更高效的对象属性操作器 2023-08-02 10:40:51 +08:00
zhouhao
92ba89714f refactor(规则引擎): 优化定时任务 2023-08-02 10:33:10 +08:00
tancong
b1319225e8 fix(设备模块): 解决修改设备物模型后,设备物模型脱离产品物模型问题 (#387)
* fix(设备模块): 解决修改设备物模型后,设备物模型脱离产品物模型问题
2023-08-02 09:20:24 +08:00
tancong
943d0739da fix(设备模块): 解决设备tag没有返回dataType字段 (#389) 2023-08-01 16:32:44 +08:00
老周
714b2cadd2 build: 修改nexus私服地址 2023-08-01 16:15:22 +08:00
zhouhao
aa9e2423c5 Merge remote-tracking branch 'origin/master' 2023-08-01 09:19:45 +08:00
zhouhao
5134b955d8 fix(设备管理): 修复ts文件缺失问题 2023-08-01 09:19:33 +08:00
bestfeng1020
e8a79edca0 feat(readme): DTU接入平台的视频文档说明 (#386) 2023-07-31 12:11:33 +08:00
bestfeng1020
054d42ef06 fix(系统配置): 优化base-path请求验证超时提示 (#383)
* fix(系统配置): 优化base-path请求验证超时提示

* fix(系统配置): 优化base-path请求验证超时提示

* fix(系统配置): 优化国际化提示格式
2023-07-31 11:17:45 +08:00
gyl
9f374dd7b8 fix(产品分类): 修复初始化失败 (#385) 2023-07-28 13:52:05 +08:00
tancong
33a8dbb692 fix: 重构场景联动,迁移指标函数 (#384)
* fix: 重构场景联动,迁移指标函数
2023-07-28 10:53:41 +08:00
zhouhao
71b4d7578a fix(设备管理): 修复创建设备时被填充无关的信息到configuration中 2023-07-27 13:44:32 +08:00
zhouhao
d4e8a430f4 build(maven): 升级r2dbc-mysql 0.9.3 2023-07-27 10:59:35 +08:00
zhouhao
440345cf82 refactor(设备管理): 优化设备名称同步逻辑 2023-07-27 10:01:53 +08:00
Zhang Ji
a9d1928f07 fix(通知管理): 修复收信人解析为空字符串导致无法发送的问题 (#381)
* feat(基础模块): 增加通用导入工具

* feat(设备): 导入设备数据,并提供日志下载

* feat(设备接入网关): 修改MQTT服务网关时,重新加载网络组件

* feat(设备接入网关): 修改MQTT服务网关时,重新加载网络组件

* fix(通知管理): 修复收信人解析为空字符串导致无法发送的问题
2023-07-26 10:12:58 +08:00
bestfeng1020
c10db4499e fix(服务支持): 修改服务支持的联系二维码不图片分辨率的问题 (#380)
* fix(服务支持): 修改服务支持的联系二维码不图片分辨率的问题

* fix(服务支持): 修改服务支持的联系二维码不图片分辨率的问题
2023-07-26 10:10:34 +08:00
bestfeng1020
ba1d08cef6 Merge pull request #376 from tancongsir/move-notify-manager
feat(通知模块): 重构用户个人通知订阅
2023-07-24 16:11:54 +08:00
tancongsir
ccc8ea41c2 feat(通知模块): 重构用户个人通知订阅 2023-07-24 16:09:32 +08:00
tancongsir
d0484545c4 feat(订阅模块): 企业版订阅模块迁移 2023-07-24 14:48:02 +08:00
bestfeng1020
045c5f6848 fix(服务支持): 修改服务支持的联系二维码不显示的问题 (#374) 2023-07-24 11:25:35 +08:00
bestfeng1020
c64f2421b1 feat(服务支持): 添加付费服务支持联系二维码 (#370) 2023-07-21 18:09:17 +08:00
bestfeng1020
c15a1fd7fe feat(服务支持): 添加JetLinks服务器支持说明 (#369) 2023-07-21 13:22:12 +08:00
tancong
679ceb6858 fix(认证模块): 修复更新不存在的角色可能报错问题 (#368) 2023-07-20 18:52:05 +08:00
zhouhao
142c720f39 feat(基础模块): 增加脚本默认允许访问的类 2023-07-19 10:15:05 +08:00
zhouhao
2bf841c408 feat(设备管理): 增加查询列式存储时全部属性的API 2023-07-19 10:14:44 +08:00
tancong
69282edfce fix(设备管理): 删除设备后,解绑子设备 (#365) 2023-07-18 19:37:25 +08:00
tancong
85f3e65fdb fix(设备管理): 修复设备导入空指针异常 (#362) 2023-07-18 14:14:21 +08:00
tancong
0d8b175a15 refactor(认证模块): 加密key校验 (#364) 2023-07-18 14:09:49 +08:00
tancong
ae7f083e95 fix(告警中心): 新增告警配置时默认启用 (#358) 2023-07-18 10:45:36 +08:00
tancong
a26474a243 perf(设备接入网关): 设备接入网关文案(中文)修改 (#355) 2023-07-18 10:45:07 +08:00
tancong
333c95bdb3 refactor(基础模块): 优化excel导入数字类型格式错误提示 (#356) 2023-07-18 10:31:08 +08:00
tancong
998616fe37 fix(设备管理): 优化设备导入校验 (#354) 2023-07-18 10:29:56 +08:00
tancong
3b0871dab2 doc(基础模块): 修复文档说明错误 (#359) 2023-07-18 10:25:50 +08:00
tancong
db16c2009f fix(通知): 修复获取企业微信部门可能错误问题 (#351) 2023-07-18 10:25:19 +08:00
Zhang Ji
a90d180fa4 feat(设备接入网关): 修改MQTT服务网关时,重新加载网络组件 (#336)
* feat(基础模块): 增加通用导入工具

* feat(设备): 导入设备数据,并提供日志下载

* feat(设备接入网关): 修改MQTT服务网关时,重新加载网络组件
2023-07-11 16:30:48 +08:00
bestfeng1020
5271799511 fix(场景联动):修复设备选择器条件会无限叠加问题 (#334) 2023-07-11 10:20:13 +08:00
bestfeng1020
98508e5a62 fix(文档):修复文案跳转链接错误 (#332) 2023-07-06 15:11:09 +08:00
dependabot[bot]
e442009ce6 build(deps): bump grpc-protobuf (#331)
Bumps [grpc-protobuf](https://github.com/grpc/grpc-java) from 1.47.0 to 1.53.0.
- [Release notes](https://github.com/grpc/grpc-java/releases)
- [Commits](https://github.com/grpc/grpc-java/compare/v1.47.0...v1.53.0)

---
updated-dependencies:
- dependency-name: io.grpc:grpc-protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-06 09:26:49 +08:00
bestfeng1020
7dafe5c0b8 fix(系统配置):解决base-path校验可能失效问题 (#330) 2023-07-05 18:23:21 +08:00
本宫在,尔等都是妃
4b5fa4a0dc fix(通知管理): 修复邮件收件方不显示自定义的发件人昵称 (#327) 2023-07-03 13:47:18 +08:00
老周
6259181f26 Update maven-wrapper.properties 2023-06-30 11:44:11 +08:00
Zhang Ji
563f9a328e feat(设备): 导入设备数据,并提供日志下载 (#326)
* feat(基础模块): 增加通用导入工具

* feat(设备): 导入设备数据,并提供日志下载
2023-06-30 11:43:38 +08:00
zhouhao
81a5f8999a feat(基础模块): 优化链路追踪 2023-06-28 09:40:30 +08:00
zhouhao
2756a31a35 feat(基础模块): 优化设备指令下发变量获取逻辑 2023-06-26 19:39:44 +08:00
zhouhao
f7e12a8306 Merge remote-tracking branch 'origin/master' 2023-06-26 11:45:05 +08:00
zhouhao
d45adde412 feat(认证模块): 增加登录时密码加密以及限制密码错误次数 2023-06-26 11:44:52 +08:00
bestfeng1020
3df3b90bd9 fix(系统配置):解决批量保存系统配置可能导致的mysql死锁问题 (#324) 2023-06-26 10:28:59 +08:00
dependabot[bot]
d6aa72bee9 Bump snakeyaml from 1.32 to 2.0 (#253)
Bumps [snakeyaml](https://bitbucket.org/snakeyaml/snakeyaml) from 1.32 to 2.0.
- [Commits](https://bitbucket.org/snakeyaml/snakeyaml/branches/compare/snakeyaml-2.0..snakeyaml-1.32)

---
updated-dependencies:
- dependency-name: org.yaml:snakeyaml
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-25 15:40:17 +08:00
老周
6009a8a233 Update maven-wrapper.properties 2023-06-15 17:53:02 +08:00
dependabot[bot]
f28098e3c1 build(deps): bump guava from 31.0.1-jre to 32.0.0-jre (#320)
Bumps [guava](https://github.com/google/guava) from 31.0.1-jre to 32.0.0-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-15 17:50:49 +08:00
zhouhao
122750aa93 feat(协议管理): 优化协议加载错误提示 2023-06-13 14:54:33 +08:00
zhouhao
52a35c353a Merge remote-tracking branch 'origin/master'
# Conflicts:
#	jetlinks-components/common-component/src/main/resources/i18n/common-component/messages_en.properties
#	jetlinks-components/common-component/src/main/resources/i18n/common-component/messages_zh.properties
2023-06-13 13:37:40 +08:00
zhouhao
509afa40df feat(基础模块): 增加jvm异常处理 2023-06-13 13:37:13 +08:00
zhouhao
d7c0806b84 fix: buffer size error 2023-06-10 16:48:23 +08:00
bestfeng1020
db1df5e121 feat(系统配置): base-path值正确性校验 (#318) 2023-06-09 19:27:22 +08:00
zhouhao
8b85aa305d feat(基础模块): 移除内存判断 2023-06-08 12:40:55 +08:00
zhouhao
d20e705a21 feat(基础模块): 设备会话增加通过配置文件进行相关配置 2023-06-07 15:50:19 +08:00
Zhang Ji
c4cb274569 fix(场景联动): 设备触发添加所属产品作为条件 (#314)
---------

Co-authored-by: zhou-hao <zh.sqy@qq.com>
2023-06-07 11:09:15 +08:00
zhouhao
1355f5209f Merge remote-tracking branch 'origin/master' 2023-06-07 10:29:12 +08:00
zhouhao
09636925a9 feat(基础模块): 优化脚本中的JSON支持 2023-06-07 10:29:02 +08:00
Zhang Ji
65ccee4f6d fix(关系): 优化固定值的判断 (#313)
* fix(阿里云短信): 解决短信模板和标签只能查询第一页数据问题 (#258)

* feat(查询条件): 添加设备查询条件构造器 (#260)

* feat(仪表盘): 系统监控添加历史记录支持

* fix(关系): 优化固定值的判断

value为空时可能抛出异常

* fix(关系): 优化固定值的判断

value为空时可能抛出异常

* fix(通知): 还原版本

---------

Co-authored-by: zhou-hao <zh.sqy@qq.com>
2023-06-06 19:02:35 +08:00
bestfeng1020
5d739c43fb fix(用户管理): 解决用户管理类型不存在问题 (#312) 2023-06-06 18:52:12 +08:00
zhangji
8fac4eab36 Revert "fix(关系): 优化固定值的判断"
This reverts commit 015df6d6
2023-06-06 17:37:19 +08:00
zhangji
015df6d6c9 fix(关系): 优化固定值的判断
value为空时可能抛出异常
2023-06-06 16:00:23 +08:00
bestfeng1020
8efe92cda0 fix(READEME): 修改产品文地址 (#307) 2023-06-02 17:44:54 +08:00
zhouhao
ad66985fb1 feat(基础模块): 优化es查询条件值类型转换 2023-06-01 15:15:51 +08:00
bestfeng1020
ec66ae9ae7 fix(设备管理): 添加post方式的设备属性列表查询接口 (#302) 2023-06-01 15:15:17 +08:00
老周
3f01310a9d doc: 修复注释错误 (#297) 2023-05-26 16:14:10 +08:00
老周
c7d1aac44c Update README.md 2023-05-22 18:10:32 +08:00
zhouhao
a7306ca921 Merge remote-tracking branch 'origin/master' 2023-05-19 14:34:51 +08:00
zhouhao
981351aaeb feat(网络组件): 增加模版配置,可通过jetlinks.network....设置网络组件的默认配置选项. 2023-05-19 14:34:35 +08:00
bestfeng1020
4c370c1924 feat(系统配置): base-path值正确性校验 (#286)
* feat(系统配置): base-path值正确性校验

* feat(系统配置): base-path值正确性校验

* feat(系统配置): base-path值正确性校验
2023-05-19 11:59:58 +08:00
Zhang Ji
015570066a feat(仪表盘): 系统监控添加历史记录支持 (#284) 2023-05-16 19:02:32 +08:00
zhouhao
7f49a4e932 feat(maven): 升级依赖版本 2023-05-11 19:21:41 +08:00
zhouhao
f8a3199138 fix(设备消息): 修复设备上线消息错误 2023-05-10 13:42:31 +08:00
zhouhao
3f3f06a8e6 fix(系统配置): 修复可能无法报错系统配置 2023-05-09 15:51:00 +08:00
zhouhao
a2cd2688cb Merge remote-tracking branch 'origin/master' 2023-05-08 16:45:36 +08:00
zhouhao
a9b906a0d5 fix(设备会话): 修复设置DeviceOnlineMessage header无效 2023-05-08 16:45:22 +08:00
老周
72362b2508 Update README.md 2023-05-05 19:16:32 +08:00
zeje
edb692d1a4 优化重置设备配置信息 (#277)
Co-authored-by: 蔡泽智 <czz@eviewgps.com>
2023-05-05 12:02:22 +08:00
bestfeng1020
309e5a57dc 修复通过场景联动发送阿里云短信失败问题 (#279) 2023-05-04 16:46:09 +08:00
bestfeng1020
fc5f9a5a25 fix(docker镜像版本): 修改前端镜像版本 (#278) 2023-04-27 16:24:25 +08:00
bestfeng1020
6a2035fee6 feat(通知订阅): 支持告警消息站内信通知 (#274) 2023-04-21 20:20:37 +08:00
bestfeng1020
d51aa6ac1f fix(网络组件): 设置支持路由设置类型的网络组件可以被复用 (#273) 2023-04-21 18:22:35 +08:00
zhouhao
7eb601b849 refactor(maven): 升级reactor 2020.0.31 2023-04-20 10:25:08 +08:00
zhouhao
b89aa1138a refactor(maven): 升级依赖版本 2023-04-20 10:15:48 +08:00
zhouhao
c145abb6b3 refactor(maven): 升级依赖版本 2023-04-20 10:15:30 +08:00
zhouhao
3f4297ed06 refactor(基础模块): 优化协议加载日志打印 2023-04-20 10:10:23 +08:00
zhouhao
32a3452f7b feat(maven): 2.1.0-SNAPSHOT 2023-04-20 10:09:58 +08:00
bestfeng1020
5fc41d5bf1 feat(产品): 根据指定的接入方式获取产品需要的配置定义 (#266) 2023-04-18 15:52:30 +08:00
dependabot[bot]
dc4b582fe7 Bump json from 20180130 to 20230227 (#264)
Bumps [json](https://github.com/douglascrockford/JSON-java) from 20180130 to 20230227.
- [Release notes](https://github.com/douglascrockford/JSON-java/releases)
- [Changelog](https://github.com/stleary/JSON-java/blob/master/docs/RELEASES.md)
- [Commits](https://github.com/douglascrockford/JSON-java/commits)

---
updated-dependencies:
- dependency-name: org.json:json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-17 09:34:31 +08:00
zhouhao
037c790550 fix(TDEngine): 修复TDEngine数据库事件精度不是毫秒时无法按时间查询问题 2023-04-14 17:50:10 +08:00
zhouhao
cf52a6ce68 Merge remote-tracking branch 'origin/master' 2023-04-13 16:58:16 +08:00
zhouhao
9a3d67e3fb refactor(配置): 本地地址使用127.0.0.1 2023-04-13 16:58:04 +08:00
bestfeng1020
b0d7b65148 feat(查询条件): 添加设备查询条件构造器 (#259) 2023-04-07 17:22:05 +08:00
bestfeng1020
e6e400b365 fix(阿里云短信): 解决短信模板和标签只能查询第一页数据问题 (#257) 2023-04-07 15:55:26 +08:00
zhouhao
cb6ced51b5 Merge remote-tracking branch 'origin/master' 2023-04-07 10:35:36 +08:00
zhouhao
887bda8ebf feat(设备管理): 透传消息解析支持子设备会话创建 2023-04-07 10:35:23 +08:00
ayan
0ce219c04f fix(菜单管理)添加菜单ID为空时的默认值 2023-03-30 16:17:13 +08:00
zhouhao
8957c8b386 feat(日志): 访问日志增加过滤支持 2023-03-27 10:26:45 +08:00
zhouhao
4a421c980f feat(监控): 优化micrometer初始化逻辑,增加ignore配置 2023-03-27 10:19:46 +08:00
zhouhao
547e495d56 feat(设备管理): 增加产品保存时自动同步相关信息到设备表 2023-03-24 16:19:39 +08:00
zhouhao
dd2ea79640 refactor(基础模块): 移除无用代码 2023-03-23 14:32:47 +08:00
zhouhao
67901c3732 feat(maven): Update new r2dbc-mysql driver #mirromutth/r2dbc-mysql/issues/251 2023-03-23 14:32:28 +08:00
zhouhao
552dfd550b fix(基础模块): 修复js脚本中无法使用console问题 2023-03-14 16:51:09 +08:00
zhouhao
d0208967f0 fix(设备管理): 导入设备时,忽略空字符串的配置 2023-03-09 11:12:04 +08:00
zhouhao
3fc1b11e29 fix(设备管理): 修复注销产品后设备中心中的缓存未清理问题 2023-03-08 14:20:45 +08:00
zhouhao
a6503b49e9 feat(TDEngine): 优化错误处理,查询时找不到表时不抛出错误。 2023-03-06 17:40:10 +08:00
zhouhao
6a186d6eba feat(设备管理): 优化设备详情的标签排序逻辑 2023-03-02 09:46:07 +08:00
zhouhao
703012c2c3 Merge remote-tracking branch 'origin/master' 2023-02-28 15:55:33 +08:00
zhouhao
0efeff2dd7 feat(repo): add build profile 2023-02-28 15:55:16 +08:00
bestfeng1020
d2262e278a fix(接口缺失): 添加菜单和权限数据验证接口 (#245) 2023-02-27 15:08:40 +08:00
bestfeng1020
1e58dbb944 添加透传消息转换支持 (#237) 2023-02-15 13:52:10 +08:00
zhouhao
57211334c3 RUN true 2023-02-13 12:38:29 +08:00
zhouhao
6640f1bdf0 优化 2023-02-13 12:01:31 +08:00
zhouhao
2076a69a56 优化 2023-02-10 13:59:03 +08:00
zhouhao
aaa6889b8d Merge remote-tracking branch 'origin/master'
# Conflicts:
#	README.md
2023-02-10 13:57:11 +08:00
zhouhao
8172baf52a Merge branch '2.0'
# Conflicts:
#	jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/client/VertxMqttClient.java
#	jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/gateway/device/MqttServerDeviceGateway.java
#	jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/server/vertx/VertxMqttConnection.java
#	jetlinks-components/network-component/mqtt-component/src/main/java/org/jetlinks/community/network/mqtt/server/vertx/VertxMqttServerProvider.java
#	jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/client/VertxTcpClient.java
#	jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/server/TcpServerProvider.java
#	jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/measurements/message/DeviceMessageMeasurement.java
#	jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/service/DeviceMessageBusinessHandler.java
#	pom.xml
2023-02-10 13:51:42 +08:00
zhouhao
bf6e75876b 2.0 2023-02-10 13:50:53 +08:00
zhouhao
8a0728c566 升级依赖 2023-01-18 11:54:13 +08:00
zhouhao
c1198ebdfe 优化http接入,增加websocket支持 2023-01-10 17:24:26 +08:00
zhouhao
a0e1b6bad6 修复查询事件条件错误 2023-01-10 15:12:09 +08:00
zhouhao
661281b6ff Merge remote-tracking branch 'origin/2.0' into 2.0 2023-01-10 13:34:58 +08:00
zhouhao
a77f5dbbc6 fixed #232 2023-01-10 13:34:44 +08:00
Zhang Ji
599ca456f3 更新README (#231) 2023-01-10 11:43:12 +08:00
Zhang Ji
203f97afc9 同步README文档链接 (#230) 2023-01-10 11:42:44 +08:00
zhouhao
d76b599ddd 修复并行只执行第一个动作问题 2023-01-08 10:01:06 +08:00
zhouhao
b32d2fddbc 增加长度字段粘拆包解析规则 2023-01-06 13:51:38 +08:00
zhouhao
b81f0b176d 修复状态类型错误 2023-01-04 15:56:20 +08:00
ayan
29bc67e3a4 启动服务时自动启动场景 2023-01-04 14:32:04 +08:00
ayan
4701ac0c29 修改代码 @see 的包路径 2023-01-04 14:00:52 +08:00
ayan
778e06134b LocalWorker 2022-12-29 17:02:35 +08:00
zhouhao
da4b53846e ClusterWorker 2022-12-29 15:46:14 +08:00
ayan
7004a92f57 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-12-29 15:16:30 +08:00
ayan
b6fbccacb1 更新官方jar文件 2022-12-29 15:16:14 +08:00
bestfeng1020
8cb5ebcd06 重构场景联动 (#227)
* 重构场景联动
2022-12-29 11:44:31 +08:00
老周
2c6b85e8af Update README.md 2022-12-23 17:10:36 +08:00
zhouhao
ecae631bd9 优化mqtt5支持 2022-12-22 15:05:17 +08:00
zhouhao
575d08c191 优化tcp 2022-12-22 15:05:11 +08:00
zhouhao
b61f452d46 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-12-13 18:05:55 +08:00
zhouhao
6c3d99ff0d 启动服务时自动启动场景 2022-12-13 18:05:40 +08:00
zhouhao
d4c14be015 优化通知 2022-12-13 18:02:16 +08:00
zhouhao
9975d3ea00 优化webhook通知 2022-12-13 18:02:05 +08:00
zhouhao
4c1de801ac 优化邮件通知 2022-12-13 18:01:52 +08:00
zhouhao
6fd335c84a 移除无用字段 2022-12-12 14:13:41 +08:00
zhouhao
d24ba36dc0 调整类型 2022-12-12 14:11:08 +08:00
zhouhao
8cae9a395e 优化初始化顺序 2022-12-06 11:16:16 +08:00
zhouhao
af364756d7 新版模拟器 2022-12-01 16:46:29 +08:00
zhouhao
a0465f79b3 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-12-01 09:59:34 +08:00
zhouhao
4831e89a13 优化日志配置 2022-12-01 09:59:22 +08:00
liujq
6607301e59 重构场景联动 2022-11-29 14:51:50 +08:00
liujq
1cd01b1701 协议包更新 2022-11-28 11:01:20 +08:00
liujq
d1746f323f Merge remote-tracking branch 'origin/2.0' into 2.0 2022-11-28 09:53:08 +08:00
liujq
37cb0e51d4 协议包更新 2022-11-28 09:52:49 +08:00
zhouhao
d3e0e524db 默认js 2022-11-24 21:18:32 +08:00
zhouhao
4c5ec7cfdf 优化脚本 2022-11-24 11:06:20 +08:00
zhouhao
07fa602ede 优化脚本功能 2022-11-24 10:55:28 +08:00
zhouhao
ec010f0aef 增加缺失的接口 2022-11-18 14:43:35 +08:00
zhouhao
d66f0dd513 修复依赖错误 2022-11-17 17:34:40 +08:00
zhouhao
5c238cd789 relativePath 2022-11-17 17:32:43 +08:00
zhouhao
a7b7e31388 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-11-17 11:36:55 +08:00
zhouhao
8ee311a9ef 增加tdengine 2022-11-17 11:36:28 +08:00
liujq
06ccfdfe31 修复docker启动配置文件 2022-11-16 18:06:17 +08:00
liujq
95ac6b7af3 支持local协议 2022-11-11 10:53:37 +08:00
liujq
a219e6414e 支持local协议 2022-11-10 16:25:14 +08:00
zhouhao
db89b37be8 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-11-10 09:46:26 +08:00
zhouhao
a96e189af1 fix password error 2022-11-10 09:45:58 +08:00
zhouhao
ddb6a263df 优化条件处理 2022-11-08 11:02:44 +08:00
zhouhao
1e3d888c22 注册DeviceMessageSendTaskExecutorProvider 2022-11-08 11:02:02 +08:00
zhouhao
9b2b6d224f getLong 2022-11-07 10:28:53 +08:00
zhouhao
1cbe4638ed getLong 2022-11-07 10:25:39 +08:00
zhouhao
5339951174 修改es配置 2022-11-03 11:36:44 +08:00
zhouhao
fbba69a28f 优化资源释放 2022-11-02 18:20:23 +08:00
zhouhao
c67d7b99ce 优化资源释放 2022-11-02 18:13:30 +08:00
zhouhao
7540ab9cac 修复tcp可能内存泄漏 2022-11-02 16:58:12 +08:00
zhouhao
a83af4ba9f 修复tcp可能内存泄漏 2022-11-02 16:57:42 +08:00
zhouhao
b99b921c15 优化标签校验 2022-11-02 16:28:57 +08:00
zhouhao
cc7e40b11d 优化标签校验 2022-11-02 16:28:37 +08:00
zhouhao
1a0d2637e6 统一依赖版本 2022-11-02 13:39:53 +08:00
zhouhao
6a4e1d9617 修复列式模式查询结果错误 2022-11-01 18:18:38 +08:00
zhouhao
812ecd26b3 增加自动刷新会话 2022-11-01 10:23:25 +08:00
zhouhao
1f36074090 增加自动刷新会话 2022-11-01 10:22:54 +08:00
zhouhao
a8b0cf5df2 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-10-31 10:11:02 +08:00
zhouhao
4c86f9d971 优化状态同步 2022-10-31 10:08:07 +08:00
zhouhao
1bc1ee0fdd 优化状态同步 2022-10-31 10:06:32 +08:00
ayan
b6a3ec7061 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-10-28 15:16:39 +08:00
ayan
bea90951a9 添加设备调试websocket支持 2022-10-28 15:16:25 +08:00
zhouhao
77b5c876a3 优化存储 2022-10-28 11:30:37 +08:00
ayan
acf84eacdd 更新官网协议包 2022-10-28 10:37:41 +08:00
ayan
99a25e39ec 修复设备详情中无网关ID返回问题 2022-10-28 10:28:31 +08:00
ayan
f63d837654 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-10-27 10:51:30 +08:00
ayan
92e181d0b3 解决mvn打包zip文件异常问题 2022-10-27 10:49:20 +08:00
zhouhao
6f2003ce16 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-10-26 17:50:19 +08:00
zhouhao
6f4686c022 remove serializer 2022-10-26 17:50:07 +08:00
ayan
7f4d52e618 优化docker-compsoe配置 2022-10-26 17:23:21 +08:00
ayan
af605d76bc Merge remote-tracking branch 'origin/2.0' into 2.0 2022-10-26 17:20:42 +08:00
ayan
47ebc48edb 增加默认协议添加接口 2022-10-26 17:20:26 +08:00
zhouhao
26d058d546 优化配置 2022-10-26 14:36:16 +08:00
zhouhao
ec13e0cbab 禁用es过期日志 2022-10-26 11:51:04 +08:00
ayan
6ca937a3d9 删除错误的dev配置文件 2022-10-25 18:23:19 +08:00
bestfeng1020
88b94ac837 代码优化 (#212)
* 阿里云通知被叫号码支持空值

* 修改订阅状态显示文案

* docker启动配置文件更新
2022-10-25 14:46:10 +08:00
dependabot[bot]
3749e9e06d Bump commons-text (#209)
Bumps commons-text from 1.9 to 1.10.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-text
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-24 17:23:24 +08:00
dependabot[bot]
c939f28248 Bump commons-text from 1.9 to 1.10.0 (#210)
Bumps commons-text from 1.9 to 1.10.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-text
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-24 17:22:57 +08:00
老周
fdfa4e3e96 Update maven.yml 2022-10-24 17:19:19 +08:00
老周
c8d8926b0f Update maven.yml 2022-10-24 17:17:59 +08:00
zhouhao
9e4baa2993 Merge remote-tracking branch 'origin/master' 2022-10-24 11:12:45 +08:00
zhouhao
eda3a24a80 优化子设备会话处理 2022-10-24 11:12:32 +08:00
zhouhao
cc0db07345 优化子设备会话处理 2022-10-24 11:11:42 +08:00
zhouhao
6dcb26c02a Merge remote-tracking branch 'origin/2.0' into 2.0 2022-10-24 11:06:06 +08:00
zhouhao
ae3dbc5ddf 优化 2022-10-24 11:05:29 +08:00
bestfeng1020
f408d72159 同步2.0相关功能代码 (#206) 2022-10-21 19:06:52 +08:00
老周
1a25e693b9 Merge pull request #204 from jetlinks/fix-bug
同步协议模块代码
2022-10-18 18:22:29 +08:00
ayan
7d7a6f2d93 优化协议处理 2022-10-18 17:39:44 +08:00
ayan
d5d3c0dd9d 产品分类支持 2022-10-18 17:26:52 +08:00
ayan
9795f7a11a 菜单身份验证初始化服务 2022-10-18 17:25:57 +08:00
ayan
84c6e48f0e 添加获取指定传输协议的消息协议接口 2022-10-18 17:24:38 +08:00
ayan
14d41cc706 Merge remote-tracking branch 'origin/2.0' into 2.0 2022-10-18 16:49:40 +08:00
ayan
cc66d06503 添加默认开放的网络端口 2022-10-18 16:49:25 +08:00
老周
eb7f78c6d1 Merge pull request #202 from jetlinks/fix-bug
解决产品未选择网关时,查询产品配置抛出的NPE
2022-10-18 10:18:47 +08:00
ayan
00cb7ce8de 移除规则引擎控制器 2022-10-17 17:51:06 +08:00
ayan
3ddc3c7a6d 解决产品未选泽网关时,查询产品配置抛出的NPE 2022-10-17 14:52:53 +08:00
老周
0cc7378389 Merge pull request #200 from vvsd/oscs_fix_cd36lv0au51of7vbl1j0
fix(sec): upgrade org.bouncycastle:bcprov-jdk15on to 1.69
2022-10-12 17:54:41 +08:00
vvsd
d5f8a6f7b0 update org.bouncycastle:bcprov-jdk15on 1.67 to 1.69 2022-10-12 15:21:39 +08:00
zhouhao
779eddd4c8 优化线程池逻辑 2022-10-09 17:05:09 +08:00
zhouhao
4c18ef264f 优化线程池逻辑 2022-10-09 13:53:07 +08:00
zhouhao
3cc59ec882 增加拓展查询条件信息 2022-09-28 17:58:00 +08:00
zhouhao
c7f4b19ed1 2.0 2022-09-28 17:56:32 +08:00
zhouhao
4a9977903e 优化仓库地址 2022-09-28 10:03:52 +08:00
zhouhao
0490e3a6e2 优化依赖 2022-09-28 10:03:30 +08:00
zhouhao
bd3bf2fa91 2.0 2022-09-26 17:40:16 +08:00
zhouhao
2c84fee332 优化配置 2022-09-26 17:38:29 +08:00
zhouhao
0242ce4188 优化配置 2022-09-26 17:28:55 +08:00
zhouhao
279e21a5d3 使用es实现物数据存储 2022-09-26 17:28:42 +08:00
zhouhao
e0fe2a5960 使用物模块数据存储策略来进行设备数据存储 2022-09-26 17:28:18 +08:00
zhouhao
b8adab7af3 增加通用物模块 2022-09-26 17:27:59 +08:00
zhouhao
8d2d26a28a 使用新的script api 2022-09-26 17:27:37 +08:00
zhouhao
c2e39e9ca9 增加脚本模块 2022-09-26 17:25:35 +08:00
zhouhao
14cc4224ef 增加通用配置功能 2022-09-26 17:25:24 +08:00
zhouhao
4fcda29b7a 增加角色、机构管理,优化菜单管理 2022-09-26 17:25:07 +08:00
zhouhao
143095d440 优化子设备注册 2022-09-21 13:54:45 +08:00
zhouhao
d5598e2c71 fix error 2022-09-21 11:08:26 +08:00
zhouhao
5bf47c59bd Merge remote-tracking branch 'origin/master' 2022-09-20 15:34:43 +08:00
zhouhao
0aac04ba10 优化es数据写入性能 2022-09-20 15:34:23 +08:00
老周
03fea2398e Update README.md 2022-09-19 11:43:18 +08:00
zhouhao
de028b763f ④群 2022-09-15 13:59:39 +08:00
zhouhao
74b2837435 使用parallel线程池处理数据 2022-09-15 11:06:00 +08:00
zhouhao
417a7ab4b0 移除弃用的类 2022-09-13 09:47:17 +08:00
zhouhao
c671a1cb52 Merge remote-tracking branch 'origin/master' 2022-09-07 20:50:25 +08:00
zhouhao
a8595c9aec 优化类型 2022-09-07 20:50:13 +08:00
老周
5da5107335 Merge pull request #197 from jetlinks/dependabot/maven/jetlinks-components/notify-component/notify-email/org.jsoup-jsoup-1.15.3
Bump jsoup from 1.14.3 to 1.15.3 in /jetlinks-components/notify-component/notify-email
2022-09-02 14:47:26 +08:00
dependabot[bot]
ecdb1db3f0 Bump jsoup in /jetlinks-components/notify-component/notify-email
Bumps [jsoup](https://github.com/jhy/jsoup) from 1.14.3 to 1.15.3.
- [Release notes](https://github.com/jhy/jsoup/releases)
- [Changelog](https://github.com/jhy/jsoup/blob/master/CHANGES)
- [Commits](https://github.com/jhy/jsoup/compare/jsoup-1.14.3...jsoup-1.15.3)

---
updated-dependencies:
- dependency-name: org.jsoup:jsoup
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-01 23:48:20 +00:00
zhouhao
24a45f5b67 优化mqtt client topic处理 2022-08-19 13:38:56 +08:00
zhouhao
a3d080d1ee RpcDeviceOperationBroker 2022-08-11 18:04:51 +08:00
zhouhao
a00016018a 优化使用脚本作为粘拆包规则时的tcp性能 2022-08-05 19:05:01 +08:00
zhouhao
31299b2e28 增加api配置信息 2022-08-04 14:24:55 +08:00
zhouhao
f31f71dc14 优化自动注册以及设备会话管理 2022-08-03 17:43:43 +08:00
zhouhao
80185b911a 调整状态同步逻辑 2022-08-03 14:07:14 +08:00
zhouhao
a895e9e5d3 fix package error 2022-07-28 12:27:58 +08:00
zhouhao
90d5ea7872 优化文件管理 2022-07-27 10:14:27 +08:00
zhouhao
65e8988639 优化es索引管理 2022-07-14 14:08:29 +08:00
zhouhao
cc992f8207 优化设备会话 2022-07-12 20:51:36 +08:00
zhouhao
bc5f21fe20 优化设备会话 2022-07-12 19:40:09 +08:00
zhouhao
9daee536f9 优化设备会话 2022-07-12 19:31:48 +08:00
zhouhao
1b3dd4400a 优化dashboard 2022-07-08 17:02:35 +08:00
zhouhao
824e6241cd 增加设备会话统计指标 2022-07-08 17:02:15 +08:00
zhouhao
def407d81d upgrade netty.version 2022-07-01 10:16:09 +08:00
zhouhao
7dcfabf503 优化告警逻辑 2022-07-01 09:21:39 +08:00
zhouhao
dcad116f3b hsweb 4.0.15-SNAPSHOT 2022-06-27 18:17:55 +08:00
zhouhao
3fc16470b0 优化设备会话处理 2022-06-27 18:17:37 +08:00
zhouhao
bc985539e2 优化MQTT 2022-06-27 15:40:37 +08:00
zhouhao
0a683684ed 优化设备会话处理 2022-06-27 15:36:39 +08:00
zhouhao
677e421848 ignore dev/ 2022-06-23 14:14:25 +08:00
zhouhao
6e97c2edcc add Badge 2022-06-22 18:08:07 +08:00
zhouhao
edec71c78c Merge remote-tracking branch 'origin/1.13' 2022-06-20 18:16:56 +08:00
老周
bcf3720562 Merge pull request #175 from wujun8/fix-monoerror
fix: Mono in Mono error
2022-06-20 18:12:58 +08:00
Winston
b1f34881a0 fix: Mono in Mono error 2022-06-20 18:10:35 +08:00
zhouhao
426394328b Merge branch '1.20'
# Conflicts:
#	jetlinks-standalone/pom.xml
#	pom.xml
2022-06-20 14:41:22 +08:00
zhouhao
e5cb557787 优化说明 2022-06-20 14:38:51 +08:00
zhouhao
a9594d28c4 优化设备会话处理 2022-06-20 14:35:29 +08:00
zhouhao
291cfec2c7 停止时压缩文件 2022-06-20 14:35:21 +08:00
zhouhao
2968cf9b4f 升级依赖 2022-06-20 14:32:20 +08:00
zhouhao
f3c5ab68c1 升级依赖 2022-06-20 14:21:21 +08:00
zhouhao
6b68990230 add dependency jackson-dataformat-cbor 2022-06-20 14:00:42 +08:00
zhouhao
b5c7a0e5b2 Merge remote-tracking branch 'origin/master' 2022-06-20 09:50:16 +08:00
zhouhao
2b102fe315 fixed https://github.com/jetlinks/jetlinks-community/issues/173 2022-06-20 09:50:02 +08:00
zhouhao
cb3fd4989e use apache Base64 2022-06-20 09:49:09 +08:00
老周
b3702588f9 Merge pull request #171 from wujun8/delete-reg
批量删除设备,同时在注册中心取消激活已激活设备,并解绑子设备和网关.
2022-06-15 11:17:12 +08:00
zhouhao
35947b3567 修复windows下文件名可能非法问题 2022-06-15 10:23:58 +08:00
Winston
5d8a7f4050 collectMultimap -> groupBy 2022-06-14 19:44:03 +08:00
Winston
f50dd08430 DefaultAsyncEvent.async 不能正确处理 Flux 2022-06-14 19:27:01 +08:00
zhouhao
cabc095367 优化 2022-06-14 17:51:43 +08:00
zhouhao
5f871ebd13 优化 2022-06-14 17:09:41 +08:00
zhouhao
ede32aa7fc Merge branch 'master' into 1.20
# Conflicts:
#	jetlinks-components/io-component/pom.xml
#	jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/FileManagerConfiguration.java
#	jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/FileProperties.java
#	jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/file/web/FileManagerController.java
#	jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/parser/strateies/DelimitedPayloadParserBuilder.java
#	jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifier.java
#	pom.xml
2022-06-14 17:07:28 +08:00
Winston
106ef49155 Optimize imports 2022-06-14 15:07:20 +08:00
Winston
00502e621c 删除设备后置处理 2022-06-14 14:47:23 +08:00
Winston
16cf97055c 批量删除设备,同时在注册中心取消激活已激活设备,并解绑子设备和网关. 2022-06-13 19:05:24 +08:00
老周
f4dc74b1d3 Merge pull request #170 from jetlinks/file-auth-support
静态文件授权支持
2022-06-13 17:02:54 +08:00
ayan
812749393d 静态文件授权支持 2022-06-13 15:38:37 +08:00
zhouhao
0996f140a9 Merge remote-tracking branch 'origin/master' 2022-06-13 14:52:59 +08:00
zhouhao
44241548f4 增加文件管理 2022-06-13 14:52:22 +08:00
zhouhao
6760b7e817 增加文件管理 2022-06-13 14:45:37 +08:00
zhouhao
79e3db9266 增加文件管理 2022-06-13 14:45:34 +08:00
老周
bf04b7f908 Merge pull request #168 from wujun8/master
ProtocolSupport.onChildBind onChildUnbind
2022-06-08 17:29:59 +08:00
Winston
189756ebfd ProtocolSupport.onChildBind onChildUnbind 2022-06-08 17:18:46 +08:00
zhouhao
aab9215293 优化 2022-05-24 17:22:40 +08:00
zhouhao
72e3011946 优化 2022-05-24 17:20:40 +08:00
zhouhao
87a922687f 优化 2022-05-24 16:58:47 +08:00
zhouhao
b688e55cd2 fastjson 1.2.83 2022-05-24 15:08:26 +08:00
zhouhao
e41ce546dd fastjson 1.2.83 2022-05-24 15:04:30 +08:00
zhouhao
34cb265917 优化 2022-05-17 18:01:08 +08:00
zhouhao
890fe14b31 优化集群 2022-05-17 17:51:39 +08:00
zhouhao
33864d544c 协议包以数据库为准 2022-05-17 17:51:28 +08:00
zhouhao
e29672b66f 优化滚动查询 2022-05-11 15:10:08 +08:00
zhouhao
47c14686c1 优化滚动查询 2022-05-11 15:09:33 +08:00
zhouhao
fc412d652e Merge remote-tracking branch 'origin/1.20' into 1.20 2022-05-10 16:53:22 +08:00
zhouhao
e35ee69588 优化在线数量统计 2022-05-10 16:53:08 +08:00
zhouhao
cf8d1285eb 修复状态错误 2022-05-08 13:32:15 +08:00
zhouhao
30a996070b remove ReactiveHashCommands.java 2022-05-07 19:29:37 +08:00
zhouhao
de9bd5b570 修改跨域 2022-05-07 14:16:07 +08:00
zhouhao
024c3d9649 1.20.0-SNAPSHOT 2022-05-07 14:15:09 +08:00
zhouhao
68e4acdfb0 优化es查询 2022-04-21 09:22:40 +08:00
zhouhao
c987a5bb85 修复依赖冲突 2022-04-19 20:57:02 +08:00
zhouhao
44ba268306 Merge remote-tracking branch 'origin/master' 2022-04-11 10:49:16 +08:00
zhouhao
0c9b286558 优化会话管理 2022-04-11 10:48:42 +08:00
zhouhao
3621ef1ea8 Merge remote-tracking branch 'origin/master' 2022-04-06 18:23:00 +08:00
zhouhao
759176ec4a 修复设备告警定时触发可能不生效问题 2022-04-06 18:22:42 +08:00
zhouhao
4a8489fcb0 upgrade jsoup version 2022-02-09 15:23:06 +08:00
zhouhao
c7e0676071 upgrade easy-orm version 2022-02-09 15:04:38 +08:00
zhouhao
13f7e87a5c 优化代码格式 2022-02-09 15:04:11 +08:00
zhouhao
7eb0a320d8 优化ElasticSearchService 2022-02-08 14:44:06 +08:00
zhouhao
0d62f1c9eb 1.13.0 2022-01-25 15:36:29 +08:00
zhouhao
393bff113c 升级依赖 2022-01-25 15:36:01 +08:00
zhouhao
5b103aaca7 1.13.0-SNAPSHOT 2022-01-17 16:17:38 +08:00
zhouhao
263701be8b 升级netty以及vertx版本 2022-01-17 16:17:21 +08:00
zhouhao
803b7af565 1.13.0-SNAPSHOT 2022-01-17 16:16:52 +08:00
zhouhao
888f776124 修复未激活设备无法获取标签信息 2022-01-13 11:09:43 +08:00
zhouhao
1794e3d33a 1.12.0 2022-01-07 14:15:22 +08:00
zhouhao
20a72c5caf 优化mqtt重复deviceId会话注销逻辑 2022-01-05 16:29:20 +08:00
zhouhao
0a6869fa44 设备注册时,如果设备已经注册则更新配置等信息。 2022-01-05 15:00:13 +08:00
老周
884ecd8000 Merge pull request #133 from jetlinks/dependabot/maven/org.apache.logging.log4j-log4j-api-2.17.1
Bump log4j-api from 2.17.0 to 2.17.1
2022-01-05 09:03:49 +08:00
老周
be85d229c8 Merge pull request #134 from jetlinks/dependabot/maven/org.apache.logging.log4j-log4j-core-2.17.1
Bump log4j-core from 2.17.0 to 2.17.1
2022-01-05 09:03:24 +08:00
dependabot[bot]
20431fb2f5 Bump log4j-core from 2.17.0 to 2.17.1
Bumps log4j-core from 2.17.0 to 2.17.1.

---
updated-dependencies:
- dependency-name: org.apache.logging.log4j:log4j-core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-04 16:56:48 +00:00
dependabot[bot]
262535221e Bump log4j-api from 2.17.0 to 2.17.1
Bumps log4j-api from 2.17.0 to 2.17.1.

---
updated-dependencies:
- dependency-name: org.apache.logging.log4j:log4j-api
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-04 16:51:58 +00:00
zhouhao
0618d457d6 优化设备告警逻辑 2021-12-30 16:31:40 +08:00
zhouhao
fc696856d6 添加消息全局UID 2021-12-30 16:22:46 +08:00
zhouhao
e18c99afce 添加消息全局UID 2021-12-30 16:22:37 +08:00
zhouhao
4339bca003 Merge remote-tracking branch 'origin/master' 2021-12-30 16:14:43 +08:00
zhouhao
da7e8093cf 修复无法转换非条件中属性值问题 2021-12-30 16:13:22 +08:00
老周
b75f39e005 Merge pull request #130 from zeje/feature-time-by-day-2
按日期来划分索引策略
2021-12-30 11:51:36 +08:00
zeje
c88d29afbd 按日期来划分索引策略 2021-12-30 11:40:59 +08:00
zhouhao
1e1fb62b54 修复多个告警条件可能不触发的问题 2021-12-23 18:30:28 +08:00
zhouhao
adddec3082 upgrade logback 1.2.9 2021-12-23 13:58:02 +08:00
zhouhao
be122e8a6f 升级log4j 2.17.0 2021-12-20 09:32:10 +08:00
zhouhao
b5cec7dba6 升级log4j 2.16.0 2021-12-15 09:27:34 +08:00
zhou-hao
3f6445527f 升级log4j版本 2021-12-10 11:27:25 +08:00
zhou-hao
4294f218a5 升级cron-utils版本 2021-12-08 10:59:44 +08:00
zhou-hao
af20c81032 升级依赖版本 2021-12-08 10:57:09 +08:00
zhou-hao
ec414a3cc4 优化聚合查询 2021-12-08 10:54:26 +08:00
zhou-hao
7a13499887 Subscribe注解可以使用表达式来引用配置值,如: ${a.b.c} 2021-12-08 10:51:32 +08:00
zhou-hao
8ba8941d78 优化设备状态同步逻辑 2021-12-01 14:35:17 +08:00
zhou-hao
72becbaadb remove onErrorContinue 2021-11-30 11:54:50 +08:00
zhou-hao
1a353d05fa Merge remote-tracking branch 'origin/master' 2021-11-29 16:38:57 +08:00
zhou-hao
8cbcc43024 优化配置说明 2021-11-29 16:38:41 +08:00
老周
3259f3f30a Update README.md 2021-11-25 10:19:00 +08:00
zhou-hao
5583a5f50d 优化菜单赋权逻辑 2021-11-18 17:10:27 +08:00
zhou-hao
f2fdb4d4b5 优化菜单赋权逻辑 2021-11-18 17:10:06 +08:00
zhou-hao
0096f67553 Merge remote-tracking branch 'origin/master' 2021-11-17 14:41:39 +08:00
zhou-hao
1f3bba8726 优化子设备会话处理 2021-11-17 14:40:58 +08:00
zhouhao
35cac66804 修复初始化数据格式错误 2021-11-12 16:09:57 +08:00
zhou-hao
663fc062bb hsweb-framework 4.0.13-SNAPSHOT 2021-11-11 18:33:34 +08:00
zhou-hao
7f9b0f6ed5 1.12.0-SNAPSHOT 2021-11-10 18:01:21 +08:00
zhou-hao
f46b9fb673 Merge remote-tracking branch 'origin/master' 2021-11-10 15:29:15 +08:00
zhou-hao
1d0514292e fix error 2021-11-10 15:28:43 +08:00
老周
36de16e287 删除重复依赖 2021-11-09 16:30:55 +08:00
zhou-hao
f98de97c4b feat: 增加根据设备告警记录查询设备相关数据条件
传入查询条件
where = id dev-alarm 'state eq newer'
等同于
{
"column":"id",
"termType":"dev-alarm",
"value":"state eq newer"
}
2021-10-28 12:44:25 +08:00
zhou-hao
2e5087c9bd 升级依赖 2021-10-26 16:57:31 +08:00
zhou-hao
89fd9bf38c 修复未回复子设备指令问题 2021-10-20 09:18:16 +08:00
zhou-hao
684daa6e4c 去除环境判断 2021-10-19 11:50:40 +08:00
zhou-hao
0aa9b3d887 修复未调用初始化访问 2021-10-19 11:48:19 +08:00
zhou-hao
6846b7415a 优化地址获取 2021-10-18 12:33:04 +08:00
zhou-hao
0136c7e4ea 使用EventBusDeviceOperationBroker 2021-10-18 11:32:03 +08:00
zhou-hao
3f10bc0642 1.12.0 2021-10-15 11:16:57 +08:00
zhou-hao
d99931fd88 1.12.0-SNAPSHOT 2021-10-13 14:55:50 +08:00
zhou-hao
caf5e54d37 1.11.0 2021-10-13 14:53:38 +08:00
zhou-hao
ddbf17b7f5 升级依赖 2021-10-13 14:47:44 +08:00
zhou-hao
cee26b5c1c 修复子设备状态设置错误 2021-10-12 15:51:19 +08:00
zhou-hao
182de36567 增加循环依赖检查 2021-10-12 15:06:36 +08:00
zhou-hao
16529b93c1 优化es 2021-10-12 09:39:34 +08:00
zhou-hao
2a96d6896d 优化菜单管理 2021-10-12 09:39:11 +08:00
zhou-hao
70b45ee12c 优化消息提示 2021-09-28 15:59:05 +08:00
zhou-hao
cb11d4ce16 优化邮件发送 2021-09-28 15:57:22 +08:00
zhou-hao
e2d5ffd965 修复依赖错误 2021-09-24 17:06:58 +08:00
zhou-hao
a029da90e4 增加自定义r2dbc配置 2021-09-24 17:03:49 +08:00
zhou-hao
1b29bd5e9f 优化设备告警逻辑 2021-09-07 11:19:31 +08:00
zhou-hao
ea91293b31 优化ElasticsearchClient,增加兼容性 2021-09-07 09:29:16 +08:00
zhou-hao
eeebe4db1c ignore data 2021-08-23 16:17:46 +08:00
zhou-hao
ecd470c9dc 1.11.0 2021-08-23 16:16:36 +08:00
zhou-hao
4daa0bb4ab add include_type_name 2021-08-23 16:16:26 +08:00
zhou-hao
48068ab06f Merge remote-tracking branch 'origin/master' 2021-08-16 09:53:30 +08:00
zhou-hao
d22ff38384 优化代码结构 2021-08-16 09:52:51 +08:00
zhou-hao
07b3028e67 修复已禁用设备会被自动注册问题 2021-08-16 09:52:41 +08:00
老周
67e3021ebc Merge pull request #92 from jetlinks/dependabot/maven/jetlinks-components/network-component/network-core/org.bouncycastle-bcprov-jdk15on-1.67
Bump bcprov-jdk15on from 1.64 to 1.67 in /jetlinks-components/network-component/network-core
2021-08-14 00:25:17 +08:00
dependabot[bot]
ce593a4fc0 Bump bcprov-jdk15on
Bumps [bcprov-jdk15on](https://github.com/bcgit/bc-java) from 1.64 to 1.67.
- [Release notes](https://github.com/bcgit/bc-java/releases)
- [Changelog](https://github.com/bcgit/bc-java/blob/master/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcprov-jdk15on
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-13 15:35:32 +00:00
zhou-hao
600e7a881c 1.11.0-SNAPSHOT 2021-08-05 10:43:48 +08:00
zhou-hao
ed7ea1e0fc 1.11.0-SNAPSHOT 2021-08-05 10:41:45 +08:00
zhou-hao
3592b74aec Merge remote-tracking branch 'origin/master' 2021-08-05 09:41:26 +08:00
zhou-hao
d27d54a234 优化消息日志处理逻辑 2021-08-05 09:39:43 +08:00
zhouhao
27471ca990 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	jetlinks-manager/authentication-manager/src/main/java/org/jetlinks/community/auth/web/OrganizationController.java
2021-08-04 20:41:44 +08:00
zhouhao
49165a593a Merge branch '1.8' 2021-08-04 20:41:17 +08:00
zhouhao
270aec11f3 优化 2021-08-04 20:40:47 +08:00
zhouhao
195e4ea695 增加dictionary依赖 2021-03-23 21:00:46 +08:00
1297 changed files with 86654 additions and 14731 deletions

View File

@@ -3,10 +3,318 @@ root = true
[*]
charset = utf-8
end_of_line = lf
[*.java]
indent_style = space
indent_size = tab
indent_style = space
insert_final_newline = false
max_line_length = 120
tab_width = 4
trim_trailing_whitespace = true
insert_final_newline = false
ij_continuation_indent_size = 4
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true
ij_smart_tabs = false
ij_visual_guides =
ij_wrap_on_typing = true
[*.java]
ij_wrap_on_typing = false
ij_java_align_consecutive_assignments = false
ij_java_align_consecutive_variable_declarations = false
ij_java_align_group_field_declarations = false
ij_java_align_multiline_annotation_parameters = false
ij_java_align_multiline_array_initializer_expression = false
ij_java_align_multiline_assignment = false
ij_java_align_multiline_binary_operation = false
ij_java_align_multiline_chained_methods = true
ij_java_align_multiline_deconstruction_list_components = true
ij_java_align_multiline_extends_list = false
ij_java_align_multiline_for = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = true
ij_java_align_multiline_parameters_in_calls = true
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_records = true
ij_java_align_multiline_resources = true
ij_java_align_multiline_ternary_operation = false
ij_java_align_multiline_text_blocks = false
ij_java_align_multiline_throws_list = false
ij_java_align_subsequent_simple_methods = false
ij_java_align_throws_keyword = false
ij_java_align_types_in_multi_catch = true
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = false
ij_java_array_initializer_wrap = off
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = off
ij_java_assignment_wrap = off
ij_java_binary_operation_sign_on_next_line = false
ij_java_binary_operation_wrap = off
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_after_imports = 1
ij_java_blank_lines_after_package = 1
ij_java_blank_lines_around_class = 1
ij_java_blank_lines_around_field = 0
ij_java_blank_lines_around_field_in_interface = 0
ij_java_blank_lines_around_initializer = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 0
ij_java_blank_lines_before_imports = 1
ij_java_blank_lines_before_method_body = 0
ij_java_blank_lines_before_package = 0
ij_java_block_brace_style = end_of_line
ij_java_block_comment_add_space = false
ij_java_block_comment_at_first_column = true
ij_java_builder_methods =
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = false
ij_java_call_parameters_wrap = off
ij_java_case_statement_on_separate_line = true
ij_java_catch_on_new_line = false
ij_java_class_annotation_wrap = split_into_lines
ij_java_class_brace_style = end_of_line
ij_java_class_count_to_use_import_on_demand = 5
ij_java_class_names_in_javadoc = 1
ij_java_deconstruction_list_wrap = normal
ij_java_do_not_indent_top_level_class_members = false
ij_java_do_not_wrap_after_single_annotation = false
ij_java_do_not_wrap_after_single_annotation_in_parameter = false
ij_java_do_while_brace_force = never
ij_java_doc_add_blank_line_after_description = true
ij_java_doc_add_blank_line_after_param_comments = false
ij_java_doc_add_blank_line_after_return = false
ij_java_doc_add_p_tag_on_empty_lines = true
ij_java_doc_align_exception_comments = true
ij_java_doc_align_param_comments = true
ij_java_doc_do_not_wrap_if_one_line = false
ij_java_doc_enable_formatting = true
ij_java_doc_enable_leading_asterisks = true
ij_java_doc_indent_on_continuation = false
ij_java_doc_keep_empty_lines = true
ij_java_doc_keep_empty_parameter_tag = true
ij_java_doc_keep_empty_return_tag = true
ij_java_doc_keep_empty_throws_tag = true
ij_java_doc_keep_invalid_tags = true
ij_java_doc_param_description_on_new_line = false
ij_java_doc_preserve_line_breaks = false
ij_java_doc_use_throws_not_exception_tag = true
ij_java_else_on_new_line = false
ij_java_entity_dd_prefix =
ij_java_entity_dd_suffix = EJB
ij_java_entity_eb_prefix =
ij_java_entity_eb_suffix = Bean
ij_java_entity_hi_prefix =
ij_java_entity_hi_suffix = Home
ij_java_entity_lhi_prefix = Local
ij_java_entity_lhi_suffix = Home
ij_java_entity_li_prefix = Local
ij_java_entity_li_suffix =
ij_java_entity_pk_class = java.lang.String
ij_java_entity_ri_prefix =
ij_java_entity_ri_suffix =
ij_java_entity_vo_prefix =
ij_java_entity_vo_suffix = VO
ij_java_enum_constants_wrap = off
ij_java_extends_keyword_wrap = off
ij_java_extends_list_wrap = off
ij_java_field_annotation_wrap = split_into_lines
ij_java_field_name_prefix =
ij_java_field_name_suffix =
ij_java_filter_class_prefix =
ij_java_filter_class_suffix =
ij_java_filter_dd_prefix =
ij_java_filter_dd_suffix =
ij_java_finally_on_new_line = false
ij_java_for_brace_force = never
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = off
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_if_brace_force = never
ij_java_imports_layout = *, |, javax.**, java.**, |, $*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 2
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
ij_java_keep_blank_lines_in_code = 2
ij_java_keep_blank_lines_in_declarations = 2
ij_java_keep_builder_methods_indents = false
ij_java_keep_control_statement_in_one_line = true
ij_java_keep_first_column_comment = true
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = false
ij_java_keep_simple_lambdas_in_one_line = false
ij_java_keep_simple_methods_in_one_line = false
ij_java_label_indent_absolute = false
ij_java_label_indent_size = 0
ij_java_lambda_brace_style = end_of_line
ij_java_layout_static_imports_separately = true
ij_java_line_comment_add_space = false
ij_java_line_comment_add_space_on_reformat = false
ij_java_line_comment_at_first_column = true
ij_java_listener_class_prefix =
ij_java_listener_class_suffix =
ij_java_local_variable_name_prefix =
ij_java_local_variable_name_suffix =
ij_java_message_dd_prefix =
ij_java_message_dd_suffix = EJB
ij_java_message_eb_prefix =
ij_java_message_eb_suffix = Bean
ij_java_method_annotation_wrap = split_into_lines
ij_java_method_brace_style = end_of_line
ij_java_method_call_chain_wrap = on_every_item
ij_java_method_parameters_new_line_after_left_paren = false
ij_java_method_parameters_right_paren_on_new_line = false
ij_java_method_parameters_wrap = off
ij_java_modifier_list_wrap = false
ij_java_multi_catch_types_wrap = normal
ij_java_names_count_to_use_import_on_demand = 3
ij_java_new_line_after_lparen_in_annotation = false
ij_java_new_line_after_lparen_in_deconstruction_pattern = true
ij_java_new_line_after_lparen_in_record_header = false
ij_java_packages_to_use_import_on_demand = java.awt.*, javax.swing.*
ij_java_parameter_annotation_wrap = off
ij_java_parameter_name_prefix =
ij_java_parameter_name_suffix =
ij_java_parentheses_expression_new_line_after_left_paren = false
ij_java_parentheses_expression_right_paren_on_new_line = false
ij_java_place_assignment_sign_on_next_line = false
ij_java_prefer_longer_names = true
ij_java_prefer_parameters_wrap = false
ij_java_record_components_wrap = normal
ij_java_repeat_synchronized = true
ij_java_replace_instanceof_and_cast = false
ij_java_replace_null_check = true
ij_java_replace_sum_lambda_with_method_ref = true
ij_java_resource_list_new_line_after_left_paren = false
ij_java_resource_list_right_paren_on_new_line = false
ij_java_resource_list_wrap = off
ij_java_rparen_on_new_line_in_annotation = false
ij_java_rparen_on_new_line_in_deconstruction_pattern = true
ij_java_rparen_on_new_line_in_record_header = false
ij_java_servlet_class_prefix =
ij_java_servlet_class_suffix =
ij_java_servlet_dd_prefix =
ij_java_servlet_dd_suffix =
ij_java_session_dd_prefix =
ij_java_session_dd_suffix = EJB
ij_java_session_eb_prefix =
ij_java_session_eb_suffix = Bean
ij_java_session_hi_prefix =
ij_java_session_hi_suffix = Home
ij_java_session_lhi_prefix = Local
ij_java_session_lhi_suffix = Home
ij_java_session_li_prefix = Local
ij_java_session_li_suffix =
ij_java_session_ri_prefix =
ij_java_session_ri_suffix =
ij_java_session_si_prefix =
ij_java_session_si_suffix = Service
ij_java_space_after_closing_angle_bracket_in_type_argument = false
ij_java_space_after_colon = true
ij_java_space_after_comma = true
ij_java_space_after_comma_in_type_arguments = true
ij_java_space_after_for_semicolon = true
ij_java_space_after_quest = true
ij_java_space_after_type_cast = true
ij_java_space_before_annotation_array_initializer_left_brace = false
ij_java_space_before_annotation_parameter_list = false
ij_java_space_before_array_initializer_left_brace = false
ij_java_space_before_catch_keyword = true
ij_java_space_before_catch_left_brace = true
ij_java_space_before_catch_parentheses = true
ij_java_space_before_class_left_brace = true
ij_java_space_before_colon = true
ij_java_space_before_colon_in_foreach = true
ij_java_space_before_comma = false
ij_java_space_before_deconstruction_list = false
ij_java_space_before_do_left_brace = true
ij_java_space_before_else_keyword = true
ij_java_space_before_else_left_brace = true
ij_java_space_before_finally_keyword = true
ij_java_space_before_finally_left_brace = true
ij_java_space_before_for_left_brace = true
ij_java_space_before_for_parentheses = true
ij_java_space_before_for_semicolon = false
ij_java_space_before_if_left_brace = true
ij_java_space_before_if_parentheses = true
ij_java_space_before_method_call_parentheses = false
ij_java_space_before_method_left_brace = true
ij_java_space_before_method_parentheses = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
ij_java_space_before_quest = true
ij_java_space_before_switch_left_brace = true
ij_java_space_before_switch_parentheses = true
ij_java_space_before_synchronized_left_brace = true
ij_java_space_before_synchronized_parentheses = true
ij_java_space_before_try_left_brace = true
ij_java_space_before_try_parentheses = true
ij_java_space_before_type_parameter_list = false
ij_java_space_before_while_keyword = true
ij_java_space_before_while_left_brace = true
ij_java_space_before_while_parentheses = true
ij_java_space_inside_one_line_enum_braces = false
ij_java_space_within_empty_array_initializer_braces = false
ij_java_space_within_empty_method_call_parentheses = false
ij_java_space_within_empty_method_parentheses = false
ij_java_spaces_around_additive_operators = true
ij_java_spaces_around_annotation_eq = true
ij_java_spaces_around_assignment_operators = true
ij_java_spaces_around_bitwise_operators = true
ij_java_spaces_around_equality_operators = true
ij_java_spaces_around_lambda_arrow = true
ij_java_spaces_around_logical_operators = true
ij_java_spaces_around_method_ref_dbl_colon = false
ij_java_spaces_around_multiplicative_operators = true
ij_java_spaces_around_relational_operators = true
ij_java_spaces_around_shift_operators = true
ij_java_spaces_around_type_bounds_in_type_parameters = true
ij_java_spaces_around_unary_operator = false
ij_java_spaces_within_angle_brackets = false
ij_java_spaces_within_annotation_parentheses = false
ij_java_spaces_within_array_initializer_braces = false
ij_java_spaces_within_braces = false
ij_java_spaces_within_brackets = false
ij_java_spaces_within_cast_parentheses = false
ij_java_spaces_within_catch_parentheses = false
ij_java_spaces_within_deconstruction_list = false
ij_java_spaces_within_for_parentheses = false
ij_java_spaces_within_if_parentheses = false
ij_java_spaces_within_method_call_parentheses = false
ij_java_spaces_within_method_parentheses = false
ij_java_spaces_within_parentheses = false
ij_java_spaces_within_record_header = false
ij_java_spaces_within_switch_parentheses = false
ij_java_spaces_within_synchronized_parentheses = false
ij_java_spaces_within_try_parentheses = false
ij_java_spaces_within_while_parentheses = false
ij_java_special_else_if_treatment = true
ij_java_static_field_name_prefix =
ij_java_static_field_name_suffix =
ij_java_subclass_name_prefix =
ij_java_subclass_name_suffix = Impl
ij_java_ternary_operation_signs_on_next_line = false
ij_java_ternary_operation_wrap = off
ij_java_test_name_prefix =
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = off
ij_java_throws_list_wrap = off
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = off
ij_java_visibility = public
ij_java_while_brace_force = never
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = true
ij_java_wrap_long_lines = false

View File

@@ -1,26 +1,27 @@
name: Auto Deploy Docker
on: [push]
on:
push:
branches: [ "master","2.0","2.1","2.2" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Cache Maven Repository
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: ~/.m2
key: jetlinks-community-maven-repository
key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }}
- name: Build with Maven
run: ./mvnw clean install -Dmaven.build.timestamp="$(date "+%Y-%m-%d %H:%M:%S")" -Dmaven.test.skip=true -Pbuild && cd jetlinks-standalone && docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) .
run: ./mvnw clean install -Dmaven.build.timestamp="$(date "+%Y-%m-%d %H:%M:%S")" -Dmaven.test.skip=true -Pbuild && cd jetlinks-standalone && docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-community:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) .
- name: Login Docker Repo
run: echo "${{ secrets.ALIYUN_DOCKER_REPO_PWD }}" | docker login registry.cn-shenzhen.aliyuncs.com -u ${{ secrets.ALIYUN_DOCKER_REPO_USERNAME }} --password-stdin
- name: Push Docker
run: docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
run: docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-community:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)

View File

@@ -19,7 +19,7 @@ jobs:
with:
java-version: 1.8
- name: Cache Maven Repository
uses: actions/cache@v1
uses: actions/cache@v4.2.3
with:
path: ~/.m2
key: jetlinks-community-maven-repository

6
.gitignore vendored
View File

@@ -20,11 +20,13 @@ hs_err_pid*
**/transaction-logs/
!/.mvn/wrapper/maven-wrapper.jar
*.db
./data/
/data/
/static/
/upload
/ui/upload/
docker/data
!device-simulator.jar
!demo-protocol-1.0.jar
application-local.yml
application-local.yml
dev/
.DS_Store

View File

@@ -1 +1 @@
distributionUrl=https://downloads.apache.org/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip
distributionUrl=https://archive.apache.org/dist/maven/maven-3/3.9.3/binaries/apache-maven-3.9.3-bin.zip

View File

@@ -1,11 +1,18 @@
# JetLinks 物联网基础平台
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/jetlinks/jetlinks-community/Auto%20Deploy%20Docker?label=docker)
![Version](https://img.shields.io/badge/version-1.9--RELEASE-brightgreen)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/jetlinks/jetlinks-community/maven.yml?branch=master)
![Version](https://img.shields.io/badge/version-2.3-brightgreen)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/e8d527d692c24633aba4f869c1c5d6ad)](https://app.codacy.com/gh/jetlinks/jetlinks-community?utm_source=github.com&utm_medium=referral&utm_content=jetlinks/jetlinks-community&utm_campaign=Badge_Grade_Settings)
[![QQ①群2021514](https://img.shields.io/badge/QQ①群-2021514-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=LGf0OPQqvLGdJIZST3VTcypdVWhdfAOG&jump_from=webapi)
[![OSCS Status](https://www.oscs1024.com/platform/badge/jetlinks/jetlinks-community.svg?size=small)](https://www.oscs1024.com/project/jetlinks/jetlinks-community?ref=badge_small)
[![star](https://img.shields.io/github/stars/jetlinks/jetlinks-community?style=social)](https://github.com/jetlinks/jetlinks-community)
[![star](https://gitee.com/jetlinks/jetlinks-community/badge/star.svg?theme=gvp)](https://gitee.com/jetlinks/jetlinks-community/stargazers)
[![QQ⑥群572077464](https://img.shields.io/badge/QQ⑥群-572077464-brightgreen)](https://qm.qq.com/q/kLT3trlXuE)
[![QQ⑤群554591908](https://img.shields.io/badge/QQ⑤群-554591908-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=jiirLiyFUecy_gsankzVQ-cl6SrZCnv9&&jump_from=webapi)
[![QQ④群780133058](https://img.shields.io/badge/QQ④群-780133058-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=Gj47w9kg7TlV5ceD5Bqew_M_O0PIjh_l&jump_from=webapi)
[![QQ③群647954464](https://img.shields.io/badge/QQ③群-647954464-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=K5m27CkhDn3B_Owr-g6rfiTBC5DKEY59&jump_from=webapi)
[![QQ②群324606263](https://img.shields.io/badge/QQ②群-324606263-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=IMas2cH-TNsYxUcY8lRbsXqPnA2sGHYQ&jump_from=webapi)
![jetlinks](https://visitor-badge.glitch.me/badge?page_id=jetlinks)
[![QQ①群2021514](https://img.shields.io/badge/QQ①群-2021514-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=LGf0OPQqvLGdJIZST3VTcypdVWhdfAOG&jump_from=webapi)
JetLinks 基于Java8,Spring Boot 2.x,WebFlux,Netty,Vert.x,Reactor等开发,
是一个开箱即用,可二次开发的企业级物联网基础平台。平台实现了物联网相关的众多基础功能,
@@ -14,37 +21,37 @@ JetLinks 基于Java8,Spring Boot 2.x,WebFlux,Netty,Vert.x,Reactor等开发,
## 核心特性
支持统一物模型管理,多种设备,多种厂家,统一管理。
#### 开放源代码
统一设备连接管理,多协议适配(TCP,MQTT,UDP,CoAP,HTTP等),屏蔽网络编程复杂性,灵活接入不同厂家不同协议的设备
全部源代码开放,可自由拓展功能,不再受制于人.前后端分离,接口全开放
灵活的规则引擎,设备告警,消息通知,数据转发.
#### 统一设备接入,海量设备管理
TCP/UDP/MQTT/HTTP、TLS/DTLS、不同厂商、不同设备、不同报文、统一接入统一管理。
强大的ReactorQL引擎,使用SQL来处理实时数据.
#### 规则引擎
灵活的规则模型配置,支持多种规则模型以及自定义规则模型. 设备告警,场景联动,均由统一的规则引擎管理。
地理位置:统一管理地理位置信息,支持区域搜索.
官方QQ: ①群 [2021514](https://qm.qq.com/cgi-bin/qm/qr?k=LGf0OPQqvLGdJIZST3VTcypdVWhdfAOG&jump_from=webapi)
, ②群 [324606263](https://qm.qq.com/cgi-bin/qm/qr?k=IMas2cH-TNsYxUcY8lRbsXqPnA2sGHYQ&jump_from=webapi)
#### 数据权限控制
灵活的非侵入数据权限控制。可实现菜单、按钮、数据三维维度的数据权限控制。可控制单条数据的操作权限。
## 技术栈
1. [Spring Boot 2.3.x](https://spring.io/projects/spring-boot)
1. [Spring Boot 2.7.x](https://spring.io/projects/spring-boot)
2. [Spring WebFlux](https://spring.io/) 响应式Web支持
3. [R2DBC](https://r2dbc.io/) 响应式关系型数据库驱动
4. [Project Reactor](https://projectreactor.io/) 响应式编程框架
4. [Netty](https://netty.io/) ,[Vert.x](https://vertx.io/) 高性能网络编程框架
4. [Netty](https://netty.io/),[Vert.x](https://vertx.io/) 高性能网络编程框架
5. [ElasticSearch](https://www.elastic.co/cn/products/enterprise-search) 全文检索,日志,时序数据存储
6. [PostgreSQL](https://www.postgresql.org) 业务功能数据管理
7. [hsweb framework 4](https://github.com/hs-web) 业务功能基础框架
## 架构
![platform](./platform.svg)
![platform](./platform.png)
## 设备接入流程
![flow](./flow.svg)
![device-flow](./device-flow.png)
## 模块
@@ -54,13 +61,62 @@ JetLinks 基于Java8,Spring Boot 2.x,WebFlux,Netty,Vert.x,Reactor等开发,
------|------|----dev-env # 启动开发环境
------|------|----run-all # 启动全部,通过http://localhost:9000 访问系统.
------|----jetlinks-components # 公共组件模块
------|-------|----common-component # 通用组件.
------|-------|----configuration-component # 通用配置.
------|-------|----dashboard-component # 仪表盘.
------|-------|----datasource-component # 数据源.
------|-------|----elasticsearch-component # elasticsearch集成.
------|-------|----gateway-component # 网关组件,消息网关,设备接入.
------|-------|----io-component # IO 组件,Excel导入导出等.
------|-------|----logging-component # 日志组件
------|-------|----network-component # 网络组件,MQTT,TCP,CoAP,UDP等
------|-------|----notify-component # 通知组件,短信,右键等通知
------|-------|----protocol-component # 协议组件
------|-------|----relation-component # 关系组件
------|-------|----rule-engine-component # 规则引擎
------|-------|----script-component # 脚本组件
------|-------|----timeseries-component # 时序数据组件
------|-------|----tdengine-component # TDengine集成
------|-------|----things-component # 物组件
------|----jetlinks-manager # 业务管理模块
------|-------|----authentication-manager # 用户,权限管理
------|-------|----device-manager # 设备管理
------|-------|----logging-manager # 日志管理
------|-------|----network-manager # 网络组件管理
------|-------|----notify-manager # 通知管理
------|-------|----visualization-manager # 数据可视化管理
------|-------|----rule-engine-manager # 规则引擎管理
------|----jetlinks-standalone # 服务启动模块
------|----simulator # 设备模拟器
```
## 服务支持
我们提供了各种服务方式帮助您深入了解物联网平台和代码,通过产品文档、技术交流群、付费教学等方式,你将获得如下服务:
| 服务项 | 服务内容 | 服务收费 | 服务方式 |
|-----------|-----------------|--------|-------------|
| 基础问题答疑 | 问题答疑 | 免费 | 技术交流群支持 [![QQ⑤群554591908](https://img.shields.io/badge/QQ⑤群-554591908-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=jiirLiyFUecy_gsankzVQ-cl6SrZCnv9&&jump_from=webapi) [![QQ④群780133058](https://img.shields.io/badge/QQ④群-780133058-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=Gj47w9kg7TlV5ceD5Bqew_M_O0PIjh_l&jump_from=webapi) [![QQ③群647954464](https://img.shields.io/badge/QQ③群-647954464-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=K5m27CkhDn3B_Owr-g6rfiTBC5DKEY59&jump_from=webapi) [![QQ②群324606263](https://img.shields.io/badge/QQ②群-324606263-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=IMas2cH-TNsYxUcY8lRbsXqPnA2sGHYQ&jump_from=webapi) [![QQ①群2021514](https://img.shields.io/badge/QQ①群-2021514-brightgreen)](https://qm.qq.com/cgi-bin/qm/qr?k=LGf0OPQqvLGdJIZST3VTcypdVWhdfAOG&jump_from=webapi) |
| 系统部署 | 系统部署 | 免费 | 文档自助。[源码部署](https://hanta.yuque.com/px7kg1/yfac2l/vvoa3u2ztymtp4oh) [Docker部署](https://hanta.yuque.com/px7kg1/yfac2l/mzq23z4iey5ev1a5) |
| 产品使用 | 教学产品各功能使用 | 免费 | 文档自助。[产品文档](https://hanta.yuque.com/px7kg1/yfac2l) |
| 二次开发 | 教学平台源码开发过程、工具使用等;| 免费 | 文档自助。[开发文档](https://hanta.yuque.com/px7kg1/dev) |
| 系统部署 | 在客户指定的网络和硬件环境中完成社区版服务部署;提供**模拟**设备接入到平台中,并能完成正常设备上线、数据上下行 | 199元 | 线上部署支持 |
| 技术支持 | 提供各类部署、功能使用中遇到的问题答疑 | 100元 | 半小时内 线上远程支持|
| 设备接入协议开发 | 根据提供的设备型号,编写并提供接入平台协议包的源码。| 3000+元 | 定制化开发 |
| 其他服务 | 企业版源码购买;定制化开发;定制化时长、功能服务等 | 面议 | 面议 |
### **付费**服务支持或商务合作请联系
![qrCode.jpg](./qrCode.png)
## 文档
[快速开始](http://doc.jetlinks.cn/basics-guide/quick-start.html)
[开发文档](http://doc.jetlinks.cn/dev-guide/start.html)
[常见问题](http://doc.jetlinks.cn/common-problems/network-components.html)
[产品文档](https://hanta.yuque.com/px7kg1/yfac2l)
[快速开始](https://hanta.yuque.com/px7kg1/yfac2l/raspyc4p1asfuxks)
[开发文档](https://hanta.yuque.com/px7kg1/nn1gdr)
[![Stargazers over time](https://starchart.cc/jetlinks/jetlinks-community.svg?variant=adaptive)](https://starchart.cc/jetlinks/jetlinks-community)

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
dockerImage=registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
dockerImage=registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-community:$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
./mvnw clean package -Dmaven.test.skip=true -Dmaven.build.timestamp="$(date "+%Y-%m-%d %H:%M:%S")"
if [ $? -ne 0 ];then
echo "构建失败!"

BIN
device-flow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

View File

@@ -6,7 +6,7 @@ services:
ports:
- "6379:6379"
volumes:
- "redis-volume:/data"
- "./data/redis:/data"
command: redis-server --appendonly yes
environment:
- TZ=Asia/Shanghai
@@ -40,7 +40,7 @@ services:
ports:
- "5432:5432"
volumes:
- "postgres-volume:/var/lib/postgresql/data"
- "./data/pg:/var/lib/postgresql/data"
environment:
POSTGRES_PASSWORD: jetlinks
POSTGRES_DB: jetlinks

View File

@@ -6,7 +6,7 @@ services:
# ports:
# - "6379:6379"
volumes:
- "redis-volume:/data"
- "./data/redis:/data"
command: redis-server --appendonly yes --requirepass "JetLinks@redis"
environment:
- TZ=Asia/Shanghai
@@ -20,8 +20,8 @@ services:
bootstrap.memory_lock: "true"
discovery.zen.minimum_master_nodes: 1
discovery.zen.ping.unicast.hosts: elasticsearch
volumes:
- elasticsearch-volume:/usr/share/elasticsearch/data
# volumes:
# - ./data/elasticsearch:/usr/share/elasticsearch/data
# ports:
# - "9200:9200"
# - "9300:9300"
@@ -33,14 +33,14 @@ services:
links:
- elasticsearch:elasticsearch
ports:
- "5602:5601"
- "5601:5601"
depends_on:
- elasticsearch
postgres:
image: postgres:11-alpine
container_name: jetlinks-ce-postgres
volumes:
- "postgres-volume:/var/lib/postgresql/data"
- "./data/postgres:/var/lib/postgresql/data"
ports:
- "5432:5432"
environment:
@@ -48,39 +48,44 @@ services:
POSTGRES_DB: jetlinks
TZ: Asia/Shanghai
ui:
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-antd:1.10.0
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.3.0-SNAPSHOT
container_name: jetlinks-ce-ui
ports:
- 9000:80
environment:
- "API_BASE_PATH=http://jetlinks:8848/" #API根路径
volumes:
- "jetlinks-volume:/usr/share/nginx/html/upload"
- "./data/jetlinks-ui:/usr/share/nginx/html/upload"
links:
- jetlinks:jetlinks
jetlinks:
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:1.10.0
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-community:2.3.0-SNAPSHOT
container_name: jetlinks-ce
ports:
- "8848:8848" # API端口
- "1883-1890:1883-1890" # 预留
- "8000-8010:8000-8010" # 预留
- "8800-8810:8800-8810" # 预留
- "5060-5061:5060-5061" # 预留
volumes:
- "jetlinks-volume:/application/static/upload" # 持久化上传的文件
- "jetlinks-protocol-volume:/application/data/protocols"
- "./data/jetlinks/upload:/application/static/upload"
- "./data/jetlinks:/application/data"
#entrypoint: /entrypoint.sh -d redis:5601,postgres:5432,elasticsearch:9200 'echo "start jetlinks service here"';
environment:
# - "SLEEP_SECOND=4"
- "JAVA_OPTS=-Duser.language=zh -XX:+UseG1GC"
- "TZ=Asia/Shanghai"
- "hsweb.file.upload.static-location=http://127.0.0.1:8848/upload" #上传的静态文件访问根地址,为ui的地址.
- "spring.r2dbc.url=r2dbc:postgresql://postgres:5432/jetlinks" #数据库连接地址
- "spring.r2dbc.username=postgres"
- "spring.r2dbc.password=jetlinks"
- "spring.data.elasticsearch.client.reactive.endpoints=elasticsearch:9200"
# - "spring.data.elasticsearch.client.reactive.username=admin"
# - "spring.data.elasticsearch.client.reactive.password=admin"
# - "spring.reactor.debug-agent.enabled=false" #设置为false能提升性能
- "spring.elasticsearch.uris=elasticsearch:9200"
# - "spring.elasticsearch.username=admin"
# - "spring.elasticsearch.password=admin"
# - "spring.reactor.debug-agent.enabled=false" #设置为false能提升性能
- "spring.redis.host=redis"
- "spring.redis.port=6379"
- "file.manager.storage-base-path=/application/data/files"
- "spring.redis.password=JetLinks@redis"
- "logging.level.io.r2dbc=warn"
- "logging.level.org.springframework.data=warn"
@@ -88,6 +93,19 @@ services:
- "logging.level.org.jetlinks=warn"
- "logging.level.org.hswebframework=warn"
- "logging.level.org.springframework.data.r2dbc.connectionfactory=warn"
- "network.resources[0]=0.0.0.0:8800-8810/tcp"
- "network.resources[1]=0.0.0.0:1883-1890"
- "hsweb.cors.enable=true"
- "hsweb.cors.configs[0].path=/**"
- "hsweb.cors.configs[0].allowed-credentials=true"
- "hsweb.cors.configs[0].allowed-headers=*"
- "hsweb.cors.configs[0].allowed-origins=*"
- "hsweb.cors.configs[0].allowed-methods[0]=GET"
- "hsweb.cors.configs[0].allowed-methods[1]=POST"
- "hsweb.cors.configs[0].allowed-methods[2]=PUT"
- "hsweb.cors.configs[0].allowed-methods[3]=PATCH"
- "hsweb.cors.configs[0].allowed-methods[4]=DELETE"
- "hsweb.cors.configs[0].allowed-methods[5]=OPTIONS"
links:
- redis:redis
- postgres:postgres
@@ -96,9 +114,3 @@ services:
- postgres
- redis
- elasticsearch
volumes:
postgres-volume:
redis-volume:
elasticsearch-volume:
jetlinks-volume:
jetlinks-protocol-volume:

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -5,7 +5,8 @@
<parent>
<artifactId>jetlinks-components</artifactId>
<groupId>org.jetlinks.community</groupId>
<version>1.10.0</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -18,6 +19,11 @@
<version>${jetlinks.version}</version>
</dependency>
<dependency>
<groupId>org.jetlinks</groupId>
<artifactId>jetlinks-supports</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-authorization-api</artifactId>
@@ -44,5 +50,50 @@
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2-mvstore</artifactId>
</dependency>
<dependency>
<groupId>org.jetlinks.sdk</groupId>
<artifactId>jetlinks-sdk-api</artifactId>
<version>${jetlinks.sdk.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-system-dictionary</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.cronutils</groupId>
<artifactId>cron-utils</artifactId>
<version>9.2.0</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
</project>

View File

@@ -1,6 +1,9 @@
package org.jetlinks.community;
import org.jetlinks.core.config.ConfigKey;
import org.jetlinks.core.metadata.MergeOption;
import java.util.Map;
/**
* 数据验证配置常量类
@@ -18,5 +21,10 @@ public interface ConfigMetadataConstants {
ConfigKey<Boolean> allowInput = ConfigKey.of("allowInput", "允许输入", Boolean.TYPE);
ConfigKey<Boolean> required = ConfigKey.of("required", "是否必填", Boolean.TYPE);
ConfigKey<String> format = ConfigKey.of("format", "格式", String.class);
ConfigKey<String> defaultValue = ConfigKey.of("defaultValue", "默认值", String.class);
ConfigKey<Boolean> indexEnabled = ConfigKey.of("indexEnabled", "开启索引", Boolean.TYPE);
}

View File

@@ -0,0 +1,16 @@
package org.jetlinks.community;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class DynamicOperationType implements OperationType {
private String id;
private String name;
}

View File

@@ -1,17 +1,20 @@
package org.jetlinks.community;
import com.alibaba.fastjson.annotation.JSONType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.*;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.function.BiFunction;
import java.util.function.Function;
@Getter
@AllArgsConstructor
@@ -28,44 +31,43 @@ public class Interval {
public static final String hours = "h";
public static final String minutes = "m";
public static final String seconds = "s";
public static final String millis = "S";
private BigDecimal number;
private String expression;
public boolean isFixed() {
return expression.equalsIgnoreCase(hours) ||
expression.equals(minutes) ||
expression.equals(seconds);
}
public boolean isCalendar() {
return expression.equals(days) ||
expression.equals(month) ||
expression.equals(year);
}
@Override
public String toString() {
return (number) + expression;
}
@Generated
public static Interval ofSeconds(int seconds) {
return of(seconds, Interval.seconds);
}
@Generated
public static Interval ofDays(int days) {
return of(days, Interval.days);
}
@Generated
public static Interval ofHours(int hours) {
return of(hours, Interval.hours);
}
@Generated
public static Interval ofMonth(int month) {
return of(month, Interval.month);
}
@Generated
public static Interval ofMinutes(int month) {
return of(month, Interval.minutes);
}
@Generated
public static Interval of(int month, String expression) {
return new Interval(new BigDecimal(month), expression);
}
@@ -107,6 +109,31 @@ public class Interval {
}
}
public IntervalUnit getUnit() {
switch (expression) {
case year:
return IntervalUnit.YEARS;
case quarter:
return IntervalUnit.QUARTER;
case month:
return IntervalUnit.MONTHS;
case weeks:
return IntervalUnit.WEEKS;
case days:
return IntervalUnit.DAYS;
case hours:
return IntervalUnit.HOURS;
case minutes:
return IntervalUnit.MINUTES;
case seconds:
return IntervalUnit.SECONDS;
case millis:
return IntervalUnit.MILLIS;
}
throw new UnsupportedOperationException("unsupported interval express:" + expression);
}
public static class IntervalJSONDeserializer extends JsonDeserializer<Interval> {
@Override
@@ -121,6 +148,54 @@ public class Interval {
}
return of(node.textValue());
}
}
public static class IntervalJSONSerializer extends JsonSerializer<Interval> {
@Override
public void serialize(Interval value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.toString());
}
}
public long toMillis() {
return getUnit().toMillis(number.intValue());
}
/**
* 对指定的时间戳按周期取整
*
* @param timestamp 时间戳
* @return 取整后的值
*/
public long round(long timestamp) {
return getUnit().truncatedTo(timestamp, number.intValue());
}
/**
* 按当前周期对指定的时间范围进行迭代,每次迭代一个周期的时间戳
*
* @param from 时间从
* @param to 时间止
* @return 迭代器
*/
public Iterable<Long> iterate(long from, long to) {
return getUnit().iterate(from, to, number.intValue());
}
public <T> Flux<T> generate(long from, long to, Function<Long, T> converter) {
return Flux
.fromIterable(iterate(from, to))
.map(converter);
}
public <T> Flux<T> generateWithFormat(long from,
long to,
String pattern,
BiFunction<Long, String, T> converter) {
DateTimeFormatter formatter = DateTimeFormat.forPattern(pattern);
return generate(from, to, t -> converter.apply(t, new DateTime(t).toString(formatter)));
}
}

View File

@@ -0,0 +1,159 @@
package org.jetlinks.community;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.Iterator;
/**
* 时间间隔单位,可用于计算时间范围的间隔周期
*
* @author zhouhao
* @since 1.12
*/
@AllArgsConstructor
public enum IntervalUnit {
MILLIS(1, ChronoUnit.MILLIS),
SECONDS(1, ChronoUnit.SECONDS),
MINUTES(1, ChronoUnit.MINUTES),
HOURS(1, ChronoUnit.HOURS),
DAYS(1, ChronoUnit.DAYS),
WEEKS(1, ChronoUnit.WEEKS) {
@Override
protected LocalDateTime doTruncateTo(LocalDateTime time) {
return time.truncatedTo(ChronoUnit.DAYS)
.minusDays(time.getDayOfWeek().getValue() - 1);
}
},
MONTHS(1, ChronoUnit.MONTHS) {
@Override
protected LocalDateTime doTruncateTo(LocalDateTime time) {
return time.truncatedTo(ChronoUnit.DAYS)
.withDayOfMonth(1);
}
},
//季度
QUARTER(3, ChronoUnit.MONTHS) {
@Override
protected LocalDateTime doTruncateTo(LocalDateTime time) {
return time
.withMonth(time.getMonth().firstMonthOfQuarter().getValue())
.truncatedTo(ChronoUnit.DAYS)
.withDayOfMonth(1);
}
},
YEARS(1, ChronoUnit.YEARS) {
@Override
protected LocalDateTime doTruncateTo(LocalDateTime time) {
return time.truncatedTo(ChronoUnit.DAYS)
.withDayOfYear(1);
}
},
//不分区,永远返回0
FOREVER(1, ChronoUnit.FOREVER) {
@Override
public long truncatedTo(long timestamp) {
return 0;
}
@Override
public Iterable<Long> iterate(long from, long to, int duration) {
return () -> new Iterator<Long>() {
private boolean nexted;
@Override
public boolean hasNext() {
return !nexted;
}
@Override
public Long next() {
nexted = true;
return 0L;
}
};
}
};
@Getter
private final int durationOfUnit;
@Getter
private final ChronoUnit unit;
protected LocalDateTime doTruncateTo(LocalDateTime time) {
return time.truncatedTo(unit);
}
protected LocalDateTime next(LocalDateTime time, int duration) {
return time.plus((long) durationOfUnit * duration, unit);
}
protected long next(long timestamp) {
return next(timestamp, 1);
}
protected long next(long timestamp, int duration) {
return this
.next(LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC), duration)
.toInstant(ZoneOffset.UTC)
.toEpochMilli();
}
public long truncatedTo(long timestamp) {
return this
.doTruncateTo(LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC))
.toInstant(ZoneOffset.UTC)
.toEpochMilli();
}
public final long truncatedTo(long timestamp, int duration) {
long ts = truncatedTo(timestamp);
//指定了多个周期
if (Math.abs(duration) > 1) {
ts = next(ts, duration);
}
return ts;
}
public long toMillis(int duration) {
return duration * durationOfUnit * unit.getDuration().toMillis();
}
public final Iterable<Long> iterate(long from, long to) {
return iterate(from, to, 1);
}
/**
* 迭代时间区间的每一个周期时间
*
* @param from 时间从
* @param to 时间止
* @param duration 间隔数量,比如 2天为一个间隔
* @return 每个间隔的时间戳迭代器
*/
public Iterable<Long> iterate(long from, long to, int duration) {
return () -> new Iterator<Long>() {
long _from = truncatedTo(Math.min(from, to));
final long _to = truncatedTo(Math.max(from, to));
@Override
public boolean hasNext() {
return _from <= _to;
}
@Override
public Long next() {
long that = truncatedTo(_from, duration);
_from = IntervalUnit.this.next(_from, duration);
return that;
}
};
}
}

View File

@@ -0,0 +1,12 @@
package org.jetlinks.community;
import org.hswebframework.web.exception.I18nSupportException;
public class JvmErrorException extends I18nSupportException {
public JvmErrorException(Throwable cause) {
super("error.jvm_error",cause);
}
}

View File

@@ -0,0 +1,53 @@
package org.jetlinks.community;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.jetlinks.core.utils.SerializeUtils;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* 描述,用于对某些操作的通用描述.
*
* @author zhouhao
* @since 2.0
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class Operation implements Externalizable {
/**
* 操作来源
*/
private OperationSource source;
/**
* 操作类型,比如: transparent-codec等
*/
private OperationType type;
@Override
public String toString() {
return type.getId() + "(" + type.getName() + "):[" + source.getId() + "]";
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
source.writeExternal(out);
SerializeUtils.writeObject(type.getId(), out);
SerializeUtils.writeObject(type.getName(), out);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
source = new OperationSource();
source.readExternal(in);
type = new DynamicOperationType((String) SerializeUtils.readObject(in), (String) SerializeUtils.readObject(in));
}
}

View File

@@ -0,0 +1,65 @@
package org.jetlinks.community;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.jetlinks.core.utils.SerializeUtils;
import reactor.util.context.Context;
import reactor.util.context.ContextView;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Optional;
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
@Getter
@Setter
public class OperationSource implements Externalizable {
private static final long serialVersionUID = 1L;
/**
* ID,type对应操作的唯一标识
*/
private String id;
/**
* 操作源名称
*/
private String name;
/**
* 操作目标,通常为ID对应的详情数据
*/
private Object data;
public static OperationSource of(String id, Object data) {
return of(id, id, data);
}
public static Context ofContext(String id, String name, Object data) {
return Context.of(OperationSource.class, of(id, name, data));
}
public static Optional<OperationSource> fromContext(ContextView ctx) {
return ctx.getOrEmpty(OperationSource.class);
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(id);
SerializeUtils.writeObject(name, out);
SerializeUtils.writeObject(data, out);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
id = in.readUTF();
name = (String) SerializeUtils.readObject(in);
data = SerializeUtils.readObject(in);
}
}

View File

@@ -0,0 +1,11 @@
package org.jetlinks.community;
public interface OperationType {
String getId();
String getName();
static OperationType of(String id,String name){
return new DynamicOperationType(id,name);
}
}

View File

@@ -1,30 +1,105 @@
package org.jetlinks.community;
import lombok.Generated;
import org.hswebframework.web.id.IDGenerator;
import org.jetlinks.core.config.ConfigKey;
import org.jetlinks.core.message.HeaderKey;
import org.springframework.core.ResolvableType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
/**
* @author wangzheng
* @since 1.0
*/
@Generated
public interface PropertyConstants {
//机构ID
Key<String> orgId = Key.of("orgId");
//设备名称
Key<String> deviceName = Key.of("deviceName");
//产品名称
Key<String> productName = Key.of("productName");
//产品ID
Key<String> productId = Key.of("productId");
/**
* 关系信息.值格式:
* <pre>{@code
* [{"type":"user","id":"userId","rel":"manager"}]
* }</pre>
*/
Key<List<Map<String, Object>>> relations = Key.of("relations");
/**
* 租户ID
*
* @see org.jetlinks.pro.tenant.TenantMember
*/
Key<List<String>> tenantId = Key.of("tenantId");
//分组ID
Key<List<String>> groupId = Key.of("groupId");
//是否记录task记录
Key<Boolean> useTask = Key.of("useTask", false);
//taskId
Key<String> taskId = Key.of("taskId");
//最大重试次数
Key<Long> maxRetryTimes = Key.of("maxRetryTimes", () -> Long.getLong("device.message.task.retryTimes", 1), Long.class);
//当前重试次数
Key<Long> retryTimes = Key.of("retryTimes", () -> 0L, Long.class);
//服务ID
Key<String> serverId = Key.of("serverId");
//全局唯一ID
Key<String> uid = Key.of("_uid", IDGenerator.RANDOM::generate);
//设备接入网关ID
Key<String> accessId = Key.of("accessId");
/**
* 设备接入方式
*
* @see org.jetlinks.pro.gateway.supports.DeviceGatewayProvider#getId
*/
Key<String> accessProvider = Key.of("accessProvider");
//设备创建者
Key<String> creatorId = Key.of("creatorId");
@SuppressWarnings("all")
static <T> Optional<T> getFromMap(ConfigKey<T> key, Map<String, Object> map) {
return Optional.ofNullable((T) map.get(key.getKey()));
}
@SuppressWarnings("all")
static <T> T getFromMapOrElse(ConfigKey<T> key, Map<String, Object> map, Supplier<T> defaultIfEmpty) {
Object value = map.get(key.getKey());
if (value == null) {
return defaultIfEmpty.get();
}
return (T) value;
}
@Generated
interface Key<V> extends ConfigKey<V>, HeaderKey<V> {
@Override
default Type getValueType() {
return ConfigKey.super.getValueType();
}
@Override
default Class<V> getType() {
return ConfigKey.super.getType();
@@ -44,5 +119,54 @@ public interface PropertyConstants {
};
}
static <T> Key<T> of(String key, T defaultValue) {
return new Key<T>() {
@Override
public String getKey() {
return key;
}
@Override
public T getDefaultValue() {
return defaultValue;
}
};
}
static <T> Key<T> of(String key, Supplier<T> defaultValue) {
return new Key<T>() {
@Override
public String getKey() {
return key;
}
@Override
public T getDefaultValue() {
return defaultValue == null ? null : defaultValue.get();
}
};
}
static <T> Key<T> of(String key, Supplier<T> defaultValue, Type type) {
return new Key<T>() {
@Override
public Type getValueType() {
return type;
}
@Override
public String getKey() {
return key;
}
@Override
public T getDefaultValue() {
return defaultValue == null ? null : defaultValue.get();
}
};
}
}
}

View File

@@ -1,23 +1,43 @@
package org.jetlinks.community;
import org.jetlinks.community.utils.ConverterUtils;
import org.jetlinks.core.message.DeviceMessage;
import org.jetlinks.core.message.HeaderKey;
import org.jetlinks.core.metadata.PropertyMetadata;
import org.jetlinks.reactor.ql.utils.CastUtils;
import java.util.*;
public interface PropertyMetadataConstants {
/**
* 属性来源
*/
interface Source {
//数据来源
String id = "source";
HeaderKey<String> headerKey = HeaderKey.of(id, null);
//手动写值
String manual = "manual";
//规则,虚拟属性
String rule = "rule";
static boolean isManual(DeviceMessage message) {
return message
.getHeader(Source.headerKey)
.map(Source.manual::equals)
.orElse(false);
}
static void setManual(DeviceMessage message) {
message.addHeader(headerKey, manual);
}
/**
* 判断属性是否手动赋值
*
@@ -30,18 +50,6 @@ public interface PropertyMetadataConstants {
.orElse(false);
}
/**
* 判断属性是否为规则
*
* @param metadata 物模型
* @return 是否规则
*/
static boolean isRule(PropertyMetadata metadata) {
return metadata
.getExpand(id)
.map(rule::equals)
.orElse(false);
}
}
/**
@@ -82,4 +90,34 @@ public interface PropertyMetadataConstants {
.orElse(true);
}
}
interface Metrics {
String id = "metrics";
static Map<String,Object> metricsToExpands(List<PropertyMetric> metrics) {
return Collections.singletonMap(id, metrics);
}
static List<PropertyMetric> getMetrics(PropertyMetadata metadata) {
return metadata
.getExpand(id)
.map(obj -> ConverterUtils.convertToList(obj, PropertyMetric::of))
.orElseGet(Collections::emptyList);
}
static Optional<PropertyMetric> getMetric(PropertyMetadata metadata, String metric) {
return metadata
.getExpand(id)
.map(obj -> {
for (PropertyMetric propertyMetric : ConverterUtils.convertToList(obj, PropertyMetric::of)) {
if(Objects.equals(metric, propertyMetric.getId())){
return propertyMetric;
}
}
return null;
});
}
}
}

View File

@@ -0,0 +1,64 @@
package org.jetlinks.community;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import org.hswebframework.web.bean.FastBeanCopier;
import org.jetlinks.community.utils.ConverterUtils;
import org.springframework.util.StringUtils;
import javax.validation.constraints.NotBlank;
import java.util.Map;
import java.util.function.Function;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PropertyMetric {
@Schema(description = "指标ID")
@NotBlank
private String id;
@Schema(description = "名称")
@NotBlank
private String name;
@Schema(description = "值,范围值使用逗号分隔")
private Object value;
@Schema(description = "是否为范围值")
private boolean range;
@Schema(description = "其他拓展配置")
private Map<String, Object> expands;
public Object castValue() {
if (value == null) {
return null;
}
if (range) {
return ConverterUtils.tryConvertToList(value, Function.identity());
}
return value;
}
public PropertyMetric merge(PropertyMetric another) {
if (!StringUtils.hasText(this.name)) {
this.setValue(another.value);
}
return this;
}
public static PropertyMetric of(String id, String name, Object value) {
PropertyMetric metric = new PropertyMetric();
metric.setId(id);
metric.setName(name);
metric.setValue(value);
return metric;
}
public static PropertyMetric of(Object mapMetric) {
return FastBeanCopier.copy(mapMetric, new PropertyMetric());
}
}

View File

@@ -0,0 +1,10 @@
package org.jetlinks.community;
import java.time.ZonedDateTime;
import java.util.Iterator;
public interface TimerIterable {
Iterator<ZonedDateTime> iterator(ZonedDateTime baseTime);
}

View File

@@ -0,0 +1,861 @@
package org.jetlinks.community;
import com.cronutils.builder.CronBuilder;
import com.cronutils.model.Cron;
import com.cronutils.model.definition.CronConstraintsFactory;
import com.cronutils.model.definition.CronDefinition;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.model.field.expression.FieldExpression;
import com.cronutils.model.field.expression.FieldExpressionFactory;
import com.cronutils.model.time.ExecutionTime;
import com.cronutils.parser.CronParser;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import org.hswebframework.ezorm.core.param.Term;
import org.hswebframework.web.exception.ValidationException;
import org.hswebframework.web.i18n.LocaleUtils;
import org.jetlinks.community.terms.I18nSpec;
import org.reactivestreams.Subscription;
import org.springframework.util.Assert;
import reactor.core.CoreSubscriber;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import javax.annotation.Nonnull;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Getter
@Setter
public class TimerSpec implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "触发方式")
@NotNull
private Trigger trigger;
@Schema(description = "使用日程标签进行触发")
private Set<String> scheduleTags;
//Cron表达式
@Schema(description = "触发方式为[cron]时不能为空")
private String cron;
@Schema(description = "执行的时间.为空则表示每天,触发方式为[week]则为1-7,触发方式为[month]时则为1-31")
private Set<Integer> when;
@Schema(description = "执行模式,一次还是周期执行")
private ExecuteMod mod;
@Schema(description = "执行模式为[period]时不能为空")
private Period period;
@Schema(description = "执行模式为[period]时不能与period同时为空")
private List<Period> periods;
@Schema(description = "执行模式为[once]时不能为空")
private Once once;
@Schema(description = "组合触发配置列表")
private Multi multi;
public static TimerSpec cron(String cron) {
TimerSpec spec = new TimerSpec();
spec.cron = cron;
spec.trigger = Trigger.cron;
return spec;
}
public List<Period> periods() {
List<Period> list = new ArrayList<>(1);
if (periods != null) {
list.addAll(periods);
}
if (period != null) {
list.add(period);
}
return list;
}
public Predicate<LocalDateTime> createRangeFilter() {
if (CollectionUtils.isEmpty(when)) {
return ignore -> true;
}
if (trigger == Trigger.week) {
return date -> when.contains(date.getDayOfWeek().getValue());
} else if (trigger == Trigger.month) {
return date -> when.contains(date.getDayOfMonth());
}
return ignore -> true;
}
public Predicate<LocalDateTime> createTimeFilter() {
Predicate<LocalDateTime> range = createRangeFilter();
if (mod == ExecuteMod.period) {
Predicate<LocalDateTime> predicate = null;
//可能多个周期
for (Period period : periods()) {
//周期执行指定了to,表示只在时间范围段内执行
LocalTime to = period.toLocalTime();
LocalTime from = period.fromLocalTime();
Predicate<LocalDateTime> _predicate = time -> !time.toLocalTime().isBefore(from);
if (to != null) {
_predicate = _predicate.and(time -> !time.toLocalTime().isAfter(to));
}
if (predicate == null) {
predicate = _predicate;
} else {
predicate = _predicate.or(predicate);
}
}
if (predicate == null) {
return range;
}
return predicate.and(range);
}
if (mod == ExecuteMod.once) {
LocalTime onceTime = once.localTime();
Predicate<LocalDateTime> predicate
= time -> compareOnceTime(time.toLocalTime(), onceTime) == 0;
return predicate.and(range);
}
return range;
}
public int compareOnceTime(LocalTime time1, LocalTime time2) {
int cmp = Integer.compare(time1.getHour(), time2.getHour());
if (cmp == 0) {
cmp = Integer.compare(time1.getMinute(), time2.getMinute());
if (cmp == 0) {
cmp = Integer.compare(time1.getSecond(), time2.getSecond());
//不比较纳秒
}
}
return cmp;
}
public String toCronExpression() {
return toCron().asString();
}
private static CronDefinition quartz() {
return CronDefinitionBuilder
.defineCron()
.withSeconds()
.withValidRange(0, 59)
.and()
.withMinutes()
.withValidRange(0, 59)
.and()
.withHours()
.withValidRange(0, 23)
.and()
.withDayOfMonth()
.withValidRange(1, 31)
.supportsL()
.supportsW()
.supportsLW()
.supportsQuestionMark()
.and()
.withMonth()
.withValidRange(1, 12)
.and()
.withDayOfWeek()
.withValidRange(1, 7)
.withMondayDoWValue(1)
.supportsHash()
.supportsL()
.supportsQuestionMark()
.and()
.withYear()
.withValidRange(1970, 2099)
.withStrictRange()
.optional()
.and()
.withCronValidation(CronConstraintsFactory.ensureEitherDayOfWeekOrDayOfMonth())
.instance();
}
public Cron toCron() {
CronDefinition definition = quartz();
if (trigger == Trigger.cron || trigger == null) {
Assert.hasText(cron, "error.scene_rule_timer_cron_cannot_be_empty");
return new CronParser(definition).parse(cron).validate();
}
CronBuilder builder = CronBuilder.cron(definition);
builder.withYear(FieldExpression.always());
builder.withMonth(FieldExpression.always());
FieldExpression range;
if (CollectionUtils.isNotEmpty(when)) {
FieldExpression expr = null;
for (Integer integer : when) {
if (expr == null) {
expr = FieldExpressionFactory.on(integer);
} else {
expr = expr.and(FieldExpressionFactory.on(integer));
}
}
range = expr;
} else {
range = FieldExpressionFactory.questionMark();
}
if (trigger == Trigger.week) {
builder.withDoM(FieldExpressionFactory.questionMark())
.withDoW(range);
} else if (trigger == Trigger.month) {
builder.withDoM(range)
.withDoW(FieldExpressionFactory.questionMark());
}
//执行一次
if (mod == ExecuteMod.once) {
LocalTime time = once.localTime();
builder.withHour(FieldExpressionFactory.on(time.getHour()));
builder.withMinute(FieldExpressionFactory.on(time.getMinute()));
builder.withSecond(FieldExpressionFactory.on(time.getSecond()));
}
//周期执行
if (mod == ExecuteMod.period) {
LocalTime time = period.fromLocalTime();
PeriodUnit unit = period.unit;
if (unit == PeriodUnit.hours) {
builder.withHour(FieldExpressionFactory.every(FieldExpressionFactory.on(time.getHour()), period.every))
.withMinute(FieldExpressionFactory.on(time.getMinute()))
.withSecond(FieldExpressionFactory.on(time.getSecond()));
} else if (unit == PeriodUnit.minutes) {
builder
.withHour(FieldExpressionFactory.always())
.withMinute(FieldExpressionFactory.every(FieldExpressionFactory.on(time.getMinute()), period.every))
.withSecond(FieldExpressionFactory.on(time.getSecond()));
} else if (unit == PeriodUnit.seconds) {
builder
.withHour(FieldExpressionFactory.always())
.withMinute(FieldExpressionFactory.always())
.withSecond(FieldExpressionFactory.every(FieldExpressionFactory.on(time.getSecond()), period.every));
}
}
return builder.instance().validate();
}
public void validate() {
if (trigger == null) {
Assert.hasText(cron, "error.scene_rule_timer_cron_cannot_be_empty");
}
if (trigger == Trigger.cron) {
try {
toCronExpression();
} catch (Throwable e) {
ValidationException exception = new ValidationException("cron", "error.cron_format_error", cron);
exception.addSuppressed(e);
throw exception;
}
} else if (trigger == Trigger.multi) {
List<TimerSpec> multiSpec = multi.getSpec();
if (CollectionUtils.isNotEmpty(multiSpec)) {
for (TimerSpec spec : multiSpec) {
spec.validate();
}
}
} else {
nextDurationBuilder().apply(ZonedDateTime.now());
}
}
@Getter
@Setter
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public static class Once implements Serializable {
private static final long serialVersionUID = 1L;
//时间点
@Schema(description = "时间点.格式:[hh:mm],或者[hh:mm:ss]")
@NotBlank
private String time;
public LocalTime localTime() {
return parsTime(time);
}
}
@Getter
@Setter
public static class Period implements Serializable {
private static final long serialVersionUID = 1L;
//周期执行的时间区间
@Schema(description = "执行时间范围从.格式:[hh:mm],或者[hh:mm:ss]")
private String from;
@Schema(description = "执行时间范围止.格式:[hh:mm],或者[hh:mm:ss]")
private String to;
@Schema(description = "周期值,如:每[every][unit]执行一次")
private int every;
@Schema(description = "周期执行单位")
private PeriodUnit unit;
public LocalTime fromLocalTime() {
return parsTime(from);
}
public LocalTime toLocalTime() {
return parsTime(to);
}
}
@Getter
@Setter
public static class Multi implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "组合触发配置列表")
private List<TimerSpec> spec;
@Schema(description = "多个触发的关系。and、or")
private Term.Type type = Term.Type.or;
@Schema(description = "组合触发时,只触发一次的最小时间间隔")
private int timeSpanSecond = 2;
}
private static LocalTime parsTime(String time) {
return LocalTime.parse(time);
}
/**
* 创建一个下一次执行时间间隔构造器,通过构造器来获取基准时间间隔
* <pre>{@code
*
* Function<ZonedDateTime, Duration> builder = nextDurationBuilder();
*
* Duration duration = builder.apply(ZonedDateTime.now());
*
* }</pre>
*
* @return 构造器
*/
public Function<ZonedDateTime, Duration> nextDurationBuilder() {
return nextDurationBuilder(ZonedDateTime.now());
}
public Function<ZonedDateTime, Duration> nextDurationBuilder(ZonedDateTime baseTime) {
Iterator<ZonedDateTime> it = iterable().iterator(baseTime);
return (time) -> {
Duration duration;
do {
duration = Duration.between(time, time = it.next());
}
while (duration.toMillis() < 0);
return duration;
};
}
/**
* 创建一个时间构造器,通过构造器来获取下一次时间
* <pre>{@code
*
* Function<ZonedDateTime, ZonedDateTime> builder = nextTimeBuilder();
*
* ZonedDateTime nextTime = builder.apply(ZonedDateTime.now());
*
* }</pre>
*
* @return 构造器
*/
public Function<ZonedDateTime, ZonedDateTime> nextTimeBuilder() {
TimerIterable it = iterable();
return time -> it.iterator(time).next();
}
static int MAX_IT_TIMES = 10000;
private TimerIterable cronIterable() {
Cron cron = this.toCron();
ExecutionTime executionTime = ExecutionTime.forCron(cron);
Predicate<LocalDateTime> filter = createTimeFilter();
return baseTime -> new Iterator<ZonedDateTime>() {
ZonedDateTime current = baseTime;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public ZonedDateTime next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
ZonedDateTime dateTime = current;
int i = 0;
do {
dateTime = executionTime
.nextExecution(dateTime)
.orElse(null);
if (dateTime == null) {
i++;
continue;
}
if (filter.test(dateTime.toLocalDateTime())) {
break;
}
} while (i < MAX_IT_TIMES);
return current = dateTime;
}
};
}
private TimerIterable periodIterable() {
List<Period> periods = periods();
Assert.notEmpty(periods, "period or periods can not be null");
Predicate<LocalDateTime> filter = createTimeFilter();
Duration _Duration = null;
LocalTime earliestFrom = LocalTime.MAX;
LocalTime latestTo = LocalTime.MIN;
//使用最小的执行周期进行判断?
for (Period period : periods) {
Duration duration = Duration.of(period.every, period.unit.temporal);
LocalTime from = period.fromLocalTime();
LocalTime to = period.toLocalTime();
// 更新最小的duration
if (_Duration == null || duration.compareTo(_Duration) < 0) {
_Duration = duration;
}
// 更新最早的起始时间
if (from != null && from.isBefore(earliestFrom)) {
earliestFrom = from;
}
// 更新最晚的结束时间
if (to != null && to.isAfter(latestTo)) {
latestTo = to;
}
}
Duration duration = _Duration;
LocalTime firstFrom = earliestFrom.equals(LocalTime.MAX) ? LocalTime.MIDNIGHT : earliestFrom;
LocalTime endTo = latestTo.equals(LocalTime.MIN) ? null : latestTo;
return baseTime -> new Iterator<ZonedDateTime>() {
ZonedDateTime current = baseTime;
@Override
public boolean hasNext() {
return true;
}
@Override
public ZonedDateTime next() {
ZonedDateTime dateTime = current;
int max = MAX_IT_TIMES;
do {
dateTime = dateTime.plus(duration);
// 检查时间是否在 firstFrom 和 endTo 之间
LocalTime time = dateTime.toLocalTime();
if (time.isBefore(firstFrom) || time.isAfter(endTo)) {
// 获取第二天的 firstFrom
ZonedDateTime nextDayFromTime = dateTime.toLocalDate().plusDays(1).atTime(firstFrom).atZone(dateTime.getZone());
// 计算当前时间到 nextDayFromTime 的差值
Duration timeDifference = Duration.between(dateTime, nextDayFromTime);
// 计算可以整除的 duration 数量
long n = timeDifference.toMillis() / duration.toMillis();
// 跳转到下一个 n * duration 的时间点
dateTime = dateTime.plus(n * duration.toMillis(), ChronoUnit.MILLIS);
}
if (filter.test(dateTime.toLocalDateTime())) {
break;
}
max--;
} while (max > 0);
return current = dateTime;
}
};
}
private TimerIterable onceIterable() {
Assert.notNull(once, "once can not be null");
Predicate<LocalDateTime> filter = createTimeFilter();
LocalTime onceTime = once.localTime();
return baseTime -> new Iterator<ZonedDateTime>() {
ZonedDateTime current = baseTime;
@Override
public boolean hasNext() {
return true;
}
@Override
public ZonedDateTime next() {
ZonedDateTime dateTime = current;
int max = MAX_IT_TIMES;
if (!dateTime.toLocalTime().equals(onceTime)) {
dateTime = onceTime.atDate(dateTime.toLocalDate()).atZone(dateTime.getZone());
}
do {
if (filter.test(dateTime.toLocalDateTime()) && current.compareTo(dateTime) <= 0) {
current = dateTime.plusDays(1);
break;
}
dateTime = dateTime.plusDays(1);
max--;
} while (max > 0);
return dateTime;
}
};
}
private TimerIterable multiSpecIterable() {
List<TimerSpec> multiSpec = multi.getSpec();
Assert.notEmpty(multiSpec, "multiSpec can not be empty");
return baseTime -> new Iterator<ZonedDateTime>() {
final List<ZonedDateTime> timeList = new ArrayList<>(multiSpec.size());
final List<Iterator<ZonedDateTime>> iterators = multiSpec
.stream()
.map(spec -> spec.iterable().iterator(baseTime))
.collect(Collectors.toList());
@Override
public boolean hasNext() {
switch (multi.getType()) {
case and:
return iterators.stream().allMatch(Iterator::hasNext);
case or:
return iterators.stream().anyMatch(Iterator::hasNext);
default:
return false;
}
}
@Override
public ZonedDateTime next() {
switch (multi.getType()) {
case and:
return handleNextAnd();
case or:
return handleNextOr();
default:
return baseTime;
}
}
private ZonedDateTime handleNextAnd() {
ZonedDateTime dateTime = null;
int max = MAX_IT_TIMES;
int match = 0;
do {
for (Iterator<ZonedDateTime> iterator : iterators) {
ZonedDateTime next = iterator.next();
if (dateTime == null) {
dateTime = next;
}
// 若生成的时间比当前选中的时间早,则继续生成
while (next.isBefore(dateTime)) {
next = iterator.next();
}
if (next.isEqual(dateTime)) {
// 所有定时器的next时间一致时返回时间
if (++match == iterators.size()) {
return dateTime;
}
} else {
dateTime = next;
}
}
max--;
} while (
max > 0
);
return dateTime;
}
private ZonedDateTime handleNextOr() {
ZonedDateTime earliest = null;
// 每个定时器生成next
fillTimeList();
// 获取最早的一个时间
for (ZonedDateTime zonedDateTime : timeList) {
if (earliest == null || earliest.isAfter(zonedDateTime) || earliest.isEqual(zonedDateTime)) {
earliest = zonedDateTime;
}
}
// 清空被选中的最早时间
for (int i = 0; i < timeList.size(); i++) {
if (timeList.get(i).isEqual(earliest)) {
timeList.set(i, null);
}
}
return earliest;
}
/**
* 遍历所有定时器若有next为空的则生成新的
*/
private void fillTimeList() {
for (int i = 0; i < iterators.size(); i++) {
if (timeList.size() <= i) {
timeList.add(iterators.get(i).next());
} else if (timeList.get(i) == null) {
timeList.set(i, iterators.get(i).next());
}
}
}
};
}
public TimerIterable iterable() {
if (trigger == Trigger.multi) {
return multiSpecIterable();
}
if ((trigger == Trigger.cron || trigger == null) && cron != null) {
return cronIterable();
}
return mod == ExecuteMod.period ? periodIterable() : onceIterable();
}
public List<ZonedDateTime> getNextExecuteTimes(ZonedDateTime from, long times) {
List<ZonedDateTime> timeList = new ArrayList<>((int) times);
Iterator<ZonedDateTime> it = iterable().iterator(from);
for (long i = 0; i < times; i++) {
timeList.add(it.next());
}
return timeList;
}
public Flux<Long> flux() {
return flux(Schedulers.parallel());
}
public Flux<Long> flux(Scheduler scheduler) {
return new TimerFlux(nextDurationBuilder(), scheduler);
}
@Override
public String toString() {
if (getTrigger() == null) {
return null;
}
switch (getTrigger()) {
case week: {
return weekDesc();
}
case month: {
return monthDesc();
}
case cron: {
return getCron();
}
}
return null;
}
private String weekDesc() {
I18nSpec spec = new I18nSpec();
spec.setCode("message.timer_spec_desc");
List<I18nSpec> args = new ArrayList<>();
if (when == null || when.isEmpty()) {
args.add(I18nSpec.of("message.timer_spec_desc_everyday", "每天"));
} else {
String week = when
.stream()
.map(weekNum -> LocaleUtils.resolveMessage("message.timer_spec_desc_week_" + weekNum))
.collect(Collectors.joining(LocaleUtils.resolveMessage("message.timer_spec_desc_seperator")));
args.add(I18nSpec.of(
"message.timer_spec_desc_everyweek",
"每周" + week,
week));
}
args.add(timerModDesc());
spec.setArgs(args);
return spec.resolveI18nMessage();
}
private String monthDesc() {
I18nSpec spec = new I18nSpec();
spec.setCode("message.timer_spec_desc");
List<I18nSpec> args = new ArrayList<>();
if (when == null || when.isEmpty()) {
args.add(I18nSpec.of("message.timer_spec_desc_everyday", "每天"));
} else {
String month = when
.stream()
.map(monthNum -> {
switch (monthNum) {
case 1:
return LocaleUtils.resolveMessage("message.timer_spec_desc_month_1", monthNum);
case 2:
return LocaleUtils.resolveMessage("message.timer_spec_desc_month_2", monthNum);
case 3:
return LocaleUtils.resolveMessage("message.timer_spec_desc_month_3", monthNum);
default:
return LocaleUtils.resolveMessage("message.timer_spec_desc_month", monthNum);
}
})
.collect(Collectors.joining(LocaleUtils.resolveMessage("message.timer_spec_desc_seperator")));
args.add(I18nSpec.of(
"message.timer_spec_desc_everymonth",
"每月" + month,
month));
}
args.add(timerModDesc());
spec.setArgs(args);
return spec.resolveI18nMessage();
}
private I18nSpec timerModDesc() {
switch (getMod()) {
case period: {
if (getPeriod() == null) {
break;
}
return I18nSpec.of(
"message.timer_spec_desc_period",
getPeriod().getFrom() + "-" + getPeriod().getTo() +
"" + getPeriod().getEvery() + getPeriod().getUnit().name(),
I18nSpec.of("message.timer_spec_desc_period_duration",
getPeriod().getFrom() + "-" + getPeriod().getTo(),
getPeriod().getFrom(),
getPeriod().getTo()),
getPeriod().getEvery(),
I18nSpec.of("message.timer_spec_desc_period_" + getPeriod().getUnit().name(),
getPeriod().getUnit().name())
);
}
case once: {
// [time]执行1次
if (getOnce() == null) {
break;
}
return I18nSpec.of("message.timer_spec_desc_period_once", getOnce().getTime(), getOnce().getTime());
}
}
return I18nSpec.of("", "");
}
@AllArgsConstructor
static class TimerFlux extends Flux<Long> {
final Function<ZonedDateTime, Duration> spec;
final Scheduler scheduler;
@Override
public void subscribe(@Nonnull CoreSubscriber<? super Long> coreSubscriber) {
TimerSubscriber subscriber = new TimerSubscriber(spec, scheduler, coreSubscriber);
coreSubscriber.onSubscribe(subscriber);
}
}
static class TimerSubscriber implements Subscription {
final Function<ZonedDateTime, Duration> spec;
final CoreSubscriber<? super Long> subscriber;
final Scheduler scheduler;
long count;
Disposable scheduling;
public TimerSubscriber(Function<ZonedDateTime, Duration> spec,
Scheduler scheduler,
CoreSubscriber<? super Long> subscriber) {
this.scheduler = scheduler;
this.spec = spec;
this.subscriber = subscriber;
}
@Override
public void request(long l) {
trySchedule();
}
@Override
public void cancel() {
if (scheduling != null) {
scheduling.dispose();
}
}
public void onNext() {
if (canSchedule()) {
subscriber.onNext(count++);
}
trySchedule();
}
void trySchedule() {
if (scheduling != null) {
scheduling.dispose();
}
ZonedDateTime now = ZonedDateTime.ofInstant(Instant.ofEpochMilli(scheduler.now(TimeUnit.MILLISECONDS)), ZoneId.systemDefault());
Duration delay = spec.apply(now);
scheduling = scheduler
.schedule(
this::onNext,
delay.toMillis(),
TimeUnit.MILLISECONDS
);
}
protected boolean canSchedule() {
return true;
}
}
public enum Trigger {
//按周
week,
//按月
month,
//cron表达式
cron,
// 多个触发组合
multi
}
public enum ExecuteMod {
period,
once
}
@AllArgsConstructor
public enum PeriodUnit {
seconds(ChronoUnit.SECONDS),
minutes(ChronoUnit.MINUTES),
hours(ChronoUnit.HOURS);
private final TemporalUnit temporal;
}
}

View File

@@ -2,6 +2,7 @@ package org.jetlinks.community;
import org.hswebframework.web.bean.FastBeanCopier;
import org.jetlinks.community.utils.TimeUtils;
import org.jetlinks.reactor.ql.utils.CastUtils;
import org.springframework.util.StringUtils;
import java.time.Duration;
@@ -15,7 +16,7 @@ public interface ValueObject {
default Optional<Object> get(String name) {
return Optional.ofNullable(values())
.map(map -> map.get(name));
.map(map -> map.get(name));
}
default Optional<Integer> getInt(String name) {
@@ -44,9 +45,8 @@ public interface ValueObject {
.map(Interval::of);
}
default Interval getInterval(String name,Interval defaultValue) {
return getString(name)
.map(Interval::of)
default Interval getInterval(String name, Interval defaultValue) {
return getInterval(name)
.orElse(defaultValue);
}
@@ -56,9 +56,14 @@ public interface ValueObject {
}
default Optional<Date> getDate(String name) {
return get(name)
.map(String::valueOf)
.map(TimeUtils::parseDate);
return this
.get(name)
.map(d -> {
if (d instanceof Date) {
return (Date) d;
}
return TimeUtils.parseDate(String.valueOf(d));
});
}
default Date getDate(String name, Date defaultValue) {
@@ -83,7 +88,8 @@ public interface ValueObject {
}
default Optional<Boolean> getBoolean(String name) {
return get(name, Boolean.class);
return get(name)
.map(CastUtils::castBoolean);
}
default boolean getBoolean(String name, boolean defaultValue) {
@@ -95,8 +101,9 @@ public interface ValueObject {
.map(obj -> FastBeanCopier.DEFAULT_CONVERT.convert(obj, type, FastBeanCopier.EMPTY_CLASS_ARRAY));
}
static ValueObject of(Map<String, Object> mapVal) {
return () -> mapVal;
@SuppressWarnings("unchecked")
static ValueObject of(Map<String, ?> mapVal) {
return () -> (Map<String, Object>) mapVal;
}
default <T> T as(Class<T> type) {

View File

@@ -8,6 +8,6 @@ public class Version {
private final String edition = "community";
private final String version = "1.9.0";
private final String version = "2.2.0-SNAPSHOT";
}

View File

@@ -0,0 +1,64 @@
package org.jetlinks.community.annotation.command;
import org.jetlinks.core.annotation.command.CommandHandler;
import org.springframework.stereotype.Indexed;
import java.lang.annotation.*;
/**
* 标记一个类为命令服务支持端点,用于对外提供命令支持
* <pre>{@code
*
* @CommandService("myService")
* public class MyCommandService{
*
* }
*
* }</pre>
*
* @author zhouhao
* @since 1.2.3
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Indexed
public @interface CommandService {
/**
* 服务标识
*
* @return 服务标识
*/
String id();
/**
* 服务名称
*
* @return 服务名称
*/
String name();
/**
* 服务描述
*
* @return 服务描述
*/
String[] description() default {};
/**
* 是否根据注解扫描注册服务
*
* @return 是否注册服务
*/
boolean autoRegistered() default true;
/**
* 命令定义,用于声明支持的命令
*
* @return 命令定义
*/
CommandHandler[] commands() default {};
}

View File

@@ -0,0 +1,66 @@
package org.jetlinks.community.authorize;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.DefaultDimensionType;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
@Getter
@Setter
public class AuthenticationSpec implements Serializable {
private static final long serialVersionUID = 3512105446265694264L;
private RoleSpec role;
private List<PermissionSpec> permissions;
@Getter
@Setter
public static class RoleSpec {
private List<String> idList;
}
@Getter
@Setter
public static class PermissionSpec implements Serializable {
private static final long serialVersionUID = 7188197046015343251L;
private String id;
private List<String> actions;
}
public boolean isGranted(Authentication auth) {
return createFilter().test(auth);
}
public Predicate<Authentication> createFilter() {
RoleSpec role = this.role;
List<PermissionSpec> permissions = this.permissions;
List<Predicate<Authentication>> all = new ArrayList<>();
if (null != role && role.getIdList() != null) {
all.add(auth -> auth.hasDimension(DefaultDimensionType.role.getId(), role.getIdList()));
}
if (null != permissions) {
for (PermissionSpec permission : permissions) {
all.add(auth -> auth.hasPermission(permission.getId(), permission.getActions()));
}
}
Predicate<Authentication> temp = null;
for (Predicate<Authentication> predicate : all) {
if (temp == null) {
temp = predicate;
} else {
temp = temp.and(predicate);
}
}
return temp == null ? auth -> true : temp;
}
}

View File

@@ -0,0 +1,433 @@
package org.jetlinks.community.authorize;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.AllArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.hswebframework.web.authorization.*;
import org.hswebframework.web.authorization.simple.*;
import org.hswebframework.web.bean.FastBeanCopier;
import org.jetlinks.core.metadata.Jsonable;
import org.jetlinks.core.utils.RecyclerUtils;
import org.jetlinks.core.utils.SerializeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Setter
public class FastSerializableAuthentication extends SimpleAuthentication
implements Externalizable, Jsonable {
private static final Logger log = LoggerFactory.getLogger(FastSerializableAuthentication.class);
static {
SerializeUtils.registerSerializer(
0x90,
FastSerializableAuthentication.class,
(ignore) -> new FastSerializableAuthentication());
}
public static void load() {
}
@SuppressWarnings("all")
public static Authentication of(Object jsonOrObject, boolean share) {
if (jsonOrObject == null) {
return null;
}
//json
if (jsonOrObject instanceof String) {
return of((String) jsonOrObject, share);
}
// map
if (jsonOrObject instanceof Map) {
FastSerializableAuthentication fast = new FastSerializableAuthentication();
fast.shared = share;
fast.fromJson(new JSONObject((Map) jsonOrObject));
return fast;
}
//auth
if (jsonOrObject instanceof Authentication) {
return of(((Authentication) jsonOrObject));
}
throw new UnsupportedOperationException();
}
public static Authentication of(String json, boolean share) {
if (StringUtils.isEmpty(json)) {
return null;
}
FastSerializableAuthentication fast = new FastSerializableAuthentication();
fast.shared = share;
fast.fromJson(JSON.parseObject(json));
return fast;
}
public static Authentication of(Authentication auth) {
return of(auth, false);
}
public static Authentication of(Authentication auth, boolean simplify) {
if (auth instanceof FastSerializableAuthentication) {
((FastSerializableAuthentication) auth).simplify = simplify;
return auth;
}
FastSerializableAuthentication fast = new FastSerializableAuthentication();
fast.setUser(auth.getUser());
fast.setSimplify(simplify);
fast.getPermissions().addAll(auth.getPermissions());
fast.getDimensions().addAll(auth.getDimensions());
fast.getAttributes().putAll(auth.getAttributes());
return fast;
}
@Override
protected FastSerializableAuthentication newInstance() {
return new FastSerializableAuthentication();
}
/**
* 是否简化,为true时,不序列化权限名称
*
* @see Permission#getName()
*/
private boolean simplify = false;
private transient boolean shared;
public void makeShared() {
shared = true;
List<Dimension> dimensions = getDimensions()
.stream()
.map(RecyclerUtils::intern)
.collect(Collectors.toList());
setDimensions(dimensions);
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
String userId = null;
try {
out.writeByte(0x01);
//是否简化模式
out.writeBoolean(simplify);
//user
User user = getUser();
out.writeUTF(user.getId() == null ? "" : (userId = user.getId()));
SerializeUtils.writeNullableUTF(user.getName(), out);
out.writeUTF(user.getUsername() == null ? "" : user.getUsername());
SerializeUtils.writeNullableUTF(user.getUserType(), out);
SerializeUtils.writeKeyValue(user.getOptions(), out);
//permission
{
List<Permission> permissions = getPermissions();
if (permissions == null) {
permissions = Collections.emptyList();
}
out.writeInt(permissions.size());
for (Permission permission : permissions) {
write(permission, out);
}
}
//dimension
{
List<Dimension> dimensions = getDimensions();
if (dimensions == null) {
dimensions = Collections.emptyList();
}
out.writeInt(dimensions.size());
for (Dimension permission : dimensions) {
write(permission, out);
}
}
SerializeUtils.writeKeyValue(getAttributes(), out);
} catch (Throwable e) {
log.warn("write FastSerializableAuthentication [{}] error", userId, e);
throw e;
}
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
byte version = in.readByte();
simplify = in.readBoolean();
//user
SimpleUser user = new SimpleUser();
user.setId(in.readUTF());
user.setName(SerializeUtils.readNullableUTF(in));
user.setUsername(in.readUTF());
user.setUserType(SerializeUtils.readNullableUTF(in));
user.setOptions(SerializeUtils.readMap(in, Maps::newHashMapWithExpectedSize));
setUser0(user);
//permission
{
int size = in.readInt();
List<Permission> permissions = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
Permission permission = readPermission(in);
permissions.add(permission);
}
setPermissions(permissions);
}
//dimension
{
int size = in.readInt();
Set<Dimension> dimensions = new HashSet<>(size);
for (int i = 0; i < size; i++) {
Dimension dimension = readDimension(in);
dimensions.add(dimension);
}
setDimensions(dimensions);
}
setAttributes(SerializeUtils.readMap(in, Maps::newHashMapWithExpectedSize));
}
@SneakyThrows
private Dimension readDimension(ObjectInput in) {
SimpleDimension dimension = new SimpleDimension();
dimension.setId(in.readUTF());
dimension.setName(in.readUTF());
dimension.setOptions(SerializeUtils.readMap(
in,
k -> RecyclerUtils.intern(String.valueOf(k)),
Function.identity(),
Maps::newHashMapWithExpectedSize));
boolean known = in.readBoolean();
if (known) {
KnownDimension knownDimension = KnownDimension.ALL[in.readByte()];
dimension.setType(knownDimension.type);
} else {
SimpleDimensionType type = new SimpleDimensionType();
type.setId(in.readUTF());
type.setName(in.readUTF());
dimension.setType(RecyclerUtils.intern(type));
}
return dimension;
}
@SneakyThrows
private void write(Dimension dimension, ObjectOutput out) {
out.writeUTF(dimension.getId());
out.writeUTF(dimension.getName() == null ? "" : dimension.getName());
SerializeUtils.writeKeyValue(dimension.getOptions(), out);
KnownDimension knownDimension = KnownDimension.MAPPING.get(dimension.getType().getId());
out.writeBoolean(knownDimension != null);
if (knownDimension != null) {
out.writeByte(knownDimension.ordinal());
} else {
out.writeUTF(dimension.getType().getId());
out.writeUTF(dimension.getType().getName());
}
}
@SneakyThrows
private Permission readPermission(ObjectInput in) {
SimplePermission permission = new SimplePermission();
permission.setId(in.readUTF());
if (!simplify) {
permission.setName(in.readUTF());
} else {
permission.setName(permission.getId());
}
permission.setOptions(SerializeUtils.readMap(in, Maps::newHashMapWithExpectedSize));
int actionSize = in.readUnsignedShort();
Set<String> actions = Sets.newHashSetWithExpectedSize(actionSize);
for (int i = 0; i < actionSize; i++) {
if (in.readBoolean()) {
actions.add(KnownAction.ALL[in.readByte()].action);
} else {
actions.add(in.readUTF());
}
}
permission.setActions(actions);
return permission;
}
@SneakyThrows
private void write(Permission permission, ObjectOutput out) {
out.writeUTF(permission.getId());
if (!simplify) {
out.writeUTF(permission.getName() == null ? "" : permission.getName());
}
SerializeUtils.writeKeyValue(permission.getOptions(), out);
Set<String> actions = permission.getActions();
out.writeShort(actions.size());
for (String action : actions) {
KnownAction knownAction = KnownAction.ACTION_MAP.get(action);
out.writeBoolean(knownAction != null);
if (null != knownAction) {
out.writeByte(knownAction.ordinal());
} else {
out.writeUTF(action);
}
}
}
@AllArgsConstructor
enum KnownDimension {
user(DefaultDimensionType.user),
role(DefaultDimensionType.role),
org(OrgDimensionType.org),
parentOrg(OrgDimensionType.parentOrg);
private final DimensionType type;
static final KnownDimension[] ALL = values();
static final Map<Object, KnownDimension> MAPPING = new HashMap<>();
static {
for (KnownDimension value : ALL) {
MAPPING.put(value, value);
MAPPING.put(value.ordinal(), value);
MAPPING.put(value.name(), value);
}
}
}
enum KnownAction {
query,
get,
update,
save,
delete,
export,
_import(Permission.ACTION_IMPORT),
enable,
disable;
static final KnownAction[] ALL = values();
static final Map<Object, KnownAction> ACTION_MAP = new HashMap<>();
static {
for (KnownAction value : ALL) {
ACTION_MAP.put(value, value);
ACTION_MAP.put(value.ordinal(), value);
ACTION_MAP.put(value.action, value);
}
}
private final String action;
KnownAction() {
this.action = name();
}
KnownAction(String action) {
this.action = action;
}
}
@Override
public JSONObject toJson() {
JSONObject obj = new JSONObject();
obj.put("user", SerializeUtils.convertToSafelySerializable(getUser()));
obj.put("permissions", SerializeUtils.convertToSafelySerializable(getPermissions()));
//忽略user
obj.put("dimensions", SerializeUtils.convertToSafelySerializable(
Collections2.filter(getDimensions(), i -> !(i instanceof User))
));
obj.put("attributes", new HashMap<>(getAttributes()));
return obj;
}
@Override
public void fromJson(JSONObject json) {
JSONObject user = json.getJSONObject("user");
if (user != null) {
setUser(user.toJavaObject(SimpleUser.class));
}
JSONArray permissions = json.getJSONArray("permissions");
if (permissions != null) {
for (int i = 0, size = permissions.size(); i < size; i++) {
JSONObject permission = permissions.getJSONObject(i);
//不再支持
permission.remove("dataAccesses");
Object actions = permission.remove("actions");
SimplePermission perm = permission.toJavaObject(SimplePermission.class);
if (actions instanceof Collection) {
@SuppressWarnings("all")
Collection<Object> _actions = (Collection<Object>) actions;
Set<String> acts = Sets.newHashSetWithExpectedSize(_actions.size());
for (Object action : _actions) {
KnownAction act = KnownAction.ACTION_MAP.get(action);
if (act == null) {
acts.add(String.valueOf(action));
} else {
acts.add(act.action);
}
}
perm.setActions(acts);
}
getPermissions().add(shared ? RecyclerUtils.intern(perm) : perm);
}
}
JSONArray dimensions = json.getJSONArray("dimensions");
if (dimensions != null) {
for (int i = 0, size = dimensions.size(); i < size; i++) {
JSONObject dimension = dimensions.getJSONObject(i);
Object type = dimension.remove("type");
if (type == null) {
continue;
}
SimpleDimension simpleDimension = dimension.toJavaObject(SimpleDimension.class);
if (type instanceof DimensionType) {
simpleDimension.setType((DimensionType) type);
} else {
KnownDimension knownDimension = KnownDimension.MAPPING.get(type);
if (knownDimension != null) {
simpleDimension.setType(knownDimension.type);
} else {
SimpleDimensionType dimensionType;
if (type instanceof String) {
dimensionType = SimpleDimensionType.of(String.valueOf(type));
} else {
dimensionType = FastBeanCopier.copy(type, new SimpleDimensionType());
}
if (StringUtils.isNoneEmpty(dimensionType.getId())) {
simpleDimension.setType(shared ? RecyclerUtils.intern(dimensionType) : dimensionType);
}
}
}
getDimensions().add(shared ? RecyclerUtils.intern(simpleDimension) : simpleDimension);
}
}
JSONObject attr = json.getJSONObject("attributes");
if (attr != null) {
getAttributes().putAll(Maps.transformValues(attr, Serializable.class::cast));
}
}
}

View File

@@ -0,0 +1,22 @@
package org.jetlinks.community.authorize;
import lombok.AllArgsConstructor;
import lombok.Generated;
import lombok.Getter;
import org.hswebframework.web.authorization.DimensionType;
/**
* @author wangzheng
* @since 1.0
*/
@AllArgsConstructor
@Getter
@Generated
public enum OrgDimensionType implements DimensionType {
org("org","组织"),
parentOrg("parentOrg","上级组织");
private final String id;
private final String name;
}

View File

@@ -0,0 +1,81 @@
package org.jetlinks.community.buffer;
import org.jetlinks.community.Operation;
import org.jetlinks.community.OperationSource;
import org.jetlinks.community.OperationType;
import org.jetlinks.community.event.SystemEventHolder;
import org.jetlinks.community.utils.TimeUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
public abstract class AbstractBufferEviction implements BufferEviction {
public static final OperationType OPERATION_TYPE = OperationType.of("buffer-eviction", "缓冲区数据丢弃");
private static final AtomicLongFieldUpdater<AbstractBufferEviction>
LAST_EVENT_TIME = AtomicLongFieldUpdater.newUpdater(AbstractBufferEviction.class, "lastEventTime");
private static final AtomicIntegerFieldUpdater<AbstractBufferEviction>
LAST_TIMES = AtomicIntegerFieldUpdater.newUpdater(AbstractBufferEviction.class, "lastTimes");
//最大事件推送频率
//可通过java -Djetlinks.buffer.eviction.event.max-interval=10m修改配置
private static final long MAX_EVENT_INTERVAL =
TimeUtils.parse(System.getProperty("jetlinks.buffer.eviction.event.max-interval", "10m")).toMillis();
private volatile long lastEventTime;
private volatile int lastTimes;
abstract boolean doEviction(EvictionContext context);
@Override
public boolean tryEviction(EvictionContext context) {
if (doEviction(context)) {
sendEvent(context);
return true;
}
return false;
}
private String operationCode() {
return getClass().getSimpleName();
}
private void sendEvent(EvictionContext context) {
long now = System.currentTimeMillis();
long time = LAST_EVENT_TIME.get(this);
//记录事件推送周期内总共触发了多少次
LAST_TIMES.incrementAndGet(this);
//超过间隔事件则推送事件,防止推送太多错误事件
if (now - time > MAX_EVENT_INTERVAL) {
LAST_EVENT_TIME.set(this, now);
Map<String, Object> info = new HashMap<>();
//缓冲区数量
info.put("bufferSize", context.size(EvictionContext.BufferType.buffer));
//死数据数量
info.put("deadSize", context.size(EvictionContext.BufferType.dead));
//总计触发次数
info.put("times", LAST_TIMES.getAndSet(this, 0));
//应用自定义的数据,比如磁盘剩余空间等信息
applyEventData(info);
//推送系统事件
SystemEventHolder.warn(
Operation.of(
OperationSource.of(context.getName(), "eviction"),
OPERATION_TYPE),
operationCode(),
info
);
}
}
protected void applyEventData(Map<String, Object> data) {
}
}

View File

@@ -0,0 +1,133 @@
package org.jetlinks.community.buffer;
import org.springframework.util.unit.DataSize;
import java.io.File;
/**
* 缓存淘汰策略
*
* @author zhouhao
* @since 2.0
*/
public interface BufferEviction {
BufferEviction NONE = new BufferEviction() {
@Override
public boolean tryEviction(EvictionContext context) {
return false;
}
@Override
public String toString() {
return "None";
}
};
/**
* 根据磁盘使用率来进行淘汰,磁盘使用率超过阈值时则淘汰旧数据
*
* @param path 文件路径
* @param threshold 使用率阈值 范围为0-1 .如: 0.8 表示磁盘使用率超过80%则丢弃数据
* @return 淘汰策略
*/
static BufferEviction disk(String path, float threshold) {
return new DiskUsageEviction(new File(path), threshold);
}
/**
* 根据磁盘可用空间来进行淘汰,磁盘剩余空间低于阈值时则淘汰旧数据
*
* @param path 文件路径
* @param minUsableDataSize 磁盘最小可用空间阈值,当磁盘可用空间低于此值时则则淘汰旧数据
* @return 淘汰策略
*/
static BufferEviction disk(String path, DataSize minUsableDataSize) {
return new DiskFreeEviction(new File(path), minUsableDataSize.toBytes());
}
/**
* 根据缓冲区数量来淘汰数据,当数量超过指定阈值后则淘汰旧数据
*
* @param bufferLimit 数量阈值
* @return 淘汰策略
*/
static BufferEviction limit(long bufferLimit) {
return limit(bufferLimit, bufferLimit);
}
/**
* 根据缓冲区数量来淘汰数据,当数量超过指定阈值后则淘汰旧数据
*
* @param bufferLimit 缓冲数量阈值
* @param deadLimit 死数据数量阈值
* @return 淘汰策略
*/
static BufferEviction limit(long bufferLimit, long deadLimit) {
return new SizeLimitEviction(bufferLimit, deadLimit);
}
/**
* 根据缓冲区数量来淘汰死数据
*
* @param deadLimit 死数据数量阈值
* @return 淘汰策略
*/
static BufferEviction deadLimit(long deadLimit) {
return new SizeLimitEviction(-1, deadLimit);
}
/**
* 尝试执行淘汰
*
* @param context 上下文
* @return 是否有数据被淘汰
*/
boolean tryEviction(EvictionContext context);
/**
* 组合另外一个淘汰策略,2个策略同时执行.
*
* @param after 后续策略
* @return 淘汰策略
*/
default BufferEviction and(BufferEviction after) {
BufferEviction self = this;
return new BufferEviction() {
@Override
public boolean tryEviction(EvictionContext context) {
return self.tryEviction(context) & after.tryEviction(context);
}
@Override
public String toString() {
return self + " and " + after;
}
};
}
/**
* 组合另外一个淘汰策略,当前策略淘汰了数据才执行另外一个策略
*
* @param after 后续策略
* @return 淘汰策略
*/
default BufferEviction then(BufferEviction after) {
BufferEviction self = this;
return new BufferEviction() {
@Override
public boolean tryEviction(EvictionContext context) {
if (self.tryEviction(context)) {
after.tryEviction(context);
return true;
}
return false;
}
@Override
public String toString() {
return self + " then " + after;
}
};
}
}

View File

@@ -0,0 +1,55 @@
package org.jetlinks.community.buffer;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.unit.DataSize;
@Getter
@Setter
public class BufferEvictionSpec {
public static final BufferEviction DEFAULT = new BufferEvictionSpec().build();
//最大队列数量,超过则淘汰最旧的数据
private int maxSize = -1;
//最大死信数量,超过则淘汰dead数据
private int maxDeadSize = Integer.getInteger("jetlinks.buffer.dead.limit", 100_0000);
//根据磁盘空间淘汰数据
private DataSize diskFree = DataSize.parse(System.getProperty("jetlinks.buffer.disk.free.threshold", "4GB"));
//磁盘最大使用率
private float diskThreshold;
//判断磁盘空间大小的目录
private String diskPath = System.getProperty("jetlinks.buffer.disk.free.path", "./");
public BufferEviction build() {
BufferEviction
eviction = null,
size = BufferEviction.limit(maxSize, maxDeadSize),
disk = null;
if (diskThreshold > 0) {
disk = BufferEviction.disk(diskPath, diskThreshold);
} else if (diskFree != null) {
disk = BufferEviction.disk(diskPath, diskFree);
}
if (disk != null) {
eviction = disk;
}
if (eviction == null) {
eviction = size;
} else {
eviction = eviction.then(size);
}
return eviction;
}
}

View File

@@ -0,0 +1,38 @@
package org.jetlinks.community.buffer;
import lombok.Getter;
import lombok.Setter;
import java.time.Duration;
@Getter
@Setter
public class BufferProperties {
//缓冲文件存储目录
private String filePath;
//缓冲区大小,超过此大小将执行 handler 处理逻辑
private int size = 1000;
//缓冲超时时间
private Duration timeout = Duration.ofSeconds(1);
//并行度,表示支持并行写入的最大线程数.
private int parallelism = Math.max(1, Runtime.getRuntime().availableProcessors());
//最大重试次数,超过此次数的数据将会放入死队列.
private long maxRetryTimes = 64;
//文件操作的最大并行度,默认为1,不建议设置超过4.
private int fileConcurrency = 1;
//消费策略 默认先进先出
private ConsumeStrategy strategy = ConsumeStrategy.FIFO;
//淘汰策略
private BufferEvictionSpec eviction = new BufferEvictionSpec();
public boolean isExceededRetryCount(int count) {
return maxRetryTimes > 0 && count >= maxRetryTimes;
}
}

View File

@@ -0,0 +1,205 @@
package org.jetlinks.community.buffer;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.jetlinks.community.utils.ErrorUtils;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.transaction.CannotCreateTransactionException;
import java.io.IOException;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
@Getter
@AllArgsConstructor
public class BufferSettings {
private static final Predicate<Throwable> DEFAULT_RETRY_WHEN_ERROR =
e -> ErrorUtils.hasException(e, IOException.class,
IllegalStateException.class,
RejectedExecutionException.class,
TimeoutException.class,
DataAccessResourceFailureException.class,
CannotCreateTransactionException.class,
QueryTimeoutException.class);
public static Predicate<Throwable> defaultRetryWhenError() {
return DEFAULT_RETRY_WHEN_ERROR;
}
public static BufferEviction defaultEviction(){
return BufferEvictionSpec.DEFAULT;
}
private final String filePath;
private final String fileName;
//缓存淘汰策略
private final BufferEviction eviction;
private final Predicate<Throwable> retryWhenError;
//缓冲区大小,超过此大小将执行 handler 处理逻辑
private final int bufferSize;
//缓冲超时时间
private final Duration bufferTimeout;
//并行度,表示支持并行写入的最大线程数.
private final int parallelism;
//最大重试次数,超过此次数的数据将会放入死队列.
private final long maxRetryTimes;
private final int fileConcurrency;
private final ConsumeStrategy strategy;
public static BufferSettings create(String filePath, String fileName) {
return new BufferSettings(
filePath,
fileName,
defaultEviction(),
//默认重试逻辑
defaultRetryWhenError(),
1000,
Duration.ofSeconds(1),
Math.max(1, Runtime.getRuntime().availableProcessors() / 2),
5,
1,
ConsumeStrategy.FIFO);
}
public static BufferSettings create(BufferProperties properties) {
return create("buffer.queue", properties);
}
public static BufferSettings create(String fileName, BufferProperties properties) {
return create(properties.getFilePath(), fileName).properties(properties);
}
public BufferSettings eviction(BufferEviction eviction) {
return new BufferSettings(filePath,
fileName,
eviction,
retryWhenError,
bufferSize,
bufferTimeout,
parallelism,
maxRetryTimes,
fileConcurrency,
strategy);
}
public BufferSettings bufferSize(int bufferSize) {
return new BufferSettings(filePath,
fileName,
eviction,
retryWhenError,
bufferSize,
bufferTimeout,
parallelism,
maxRetryTimes,
fileConcurrency,
strategy);
}
public BufferSettings bufferTimeout(Duration bufferTimeout) {
return new BufferSettings(filePath,
fileName,
eviction,
retryWhenError,
bufferSize,
bufferTimeout,
parallelism,
maxRetryTimes,
fileConcurrency,
strategy);
}
public BufferSettings parallelism(int parallelism) {
return new BufferSettings(filePath,
fileName,
eviction,
retryWhenError,
bufferSize,
bufferTimeout,
parallelism,
maxRetryTimes,
fileConcurrency,
strategy);
}
public BufferSettings maxRetry(int maxRetryTimes) {
return new BufferSettings(filePath,
fileName,
eviction,
retryWhenError,
bufferSize,
bufferTimeout,
parallelism,
maxRetryTimes,
fileConcurrency,
strategy);
}
public BufferSettings retryWhenError(Predicate<Throwable> retryWhenError) {
return new BufferSettings(filePath,
fileName,
eviction,
Objects.requireNonNull(retryWhenError),
bufferSize,
bufferTimeout,
parallelism,
maxRetryTimes,
fileConcurrency,
strategy);
}
public BufferSettings fileConcurrency(int fileConcurrency) {
return new BufferSettings(filePath,
fileName,
eviction,
retryWhenError,
bufferSize,
bufferTimeout,
parallelism,
maxRetryTimes,
fileConcurrency,
strategy);
}
public BufferSettings strategy(ConsumeStrategy strategy) {
return new BufferSettings(filePath,
fileName,
eviction,
retryWhenError,
bufferSize,
bufferTimeout,
parallelism,
maxRetryTimes,
fileConcurrency,
strategy);
}
public BufferSettings properties(BufferProperties properties) {
return new BufferSettings(filePath,
fileName,
properties.getEviction().build(),
Objects.requireNonNull(retryWhenError),
properties.getSize(),
properties.getTimeout(),
properties.getParallelism(),
properties.getMaxRetryTimes(),
properties.getFileConcurrency(),
properties.getStrategy()
);
}
}

View File

@@ -0,0 +1,31 @@
package org.jetlinks.community.buffer;
/**
* 已缓冲的数据
*
* @param <T> 数据类型
* @author zhouhao
* @since 2.2
*/
public interface Buffered<T> {
/**
* @return 数据
*/
T getData();
/**
* @return 当前重试次数
*/
int getRetryTimes();
/**
* 标记是否重试此数据
*/
void retry(boolean retry);
/**
* 标记此数据为死信
*/
void dead();
}

View File

@@ -0,0 +1,10 @@
package org.jetlinks.community.buffer;
public enum ConsumeStrategy {
// 先进先出
FIFO,
// 后进先出
LIFO
}

View File

@@ -0,0 +1,59 @@
package org.jetlinks.community.buffer;
import org.jetlinks.community.utils.FormatUtils;
import org.springframework.util.unit.DataSize;
import java.io.File;
import java.util.Map;
class DiskFreeEviction extends AbstractBufferEviction {
private final File path;
private final long minUsableBytes;
public DiskFreeEviction(File path, long minUsableBytes) {
this.path = path;
this.minUsableBytes = minUsableBytes;
}
private volatile long usableSpace = -1;
private volatile long lastUpdateTime;
@Override
public boolean doEviction(EvictionContext context) {
tryUpdate();
if (freeOutOfThreshold()) {
context.removeOldest(EvictionContext.BufferType.buffer);
return true;
}
return false;
}
protected boolean freeOutOfThreshold() {
return usableSpace != -1 && usableSpace <= minUsableBytes;
}
private void tryUpdate() {
long now = System.currentTimeMillis();
//1秒更新一次
if (now - lastUpdateTime <= 1000) {
return;
}
usableSpace = path.getUsableSpace();
lastUpdateTime = now;
}
@Override
protected void applyEventData(Map<String, Object> data) {
data.put("usableSpace", DataSize.ofBytes(usableSpace).toMegabytes());
data.put("minUsableBytes", DataSize.ofBytes(minUsableBytes).toMegabytes());
}
@Override
public String toString() {
return "DiskFree(path=" + path
+ ",space=" + FormatUtils.formatDataSize(usableSpace) + "/" + FormatUtils.formatDataSize(minUsableBytes) + ")";
}
}

View File

@@ -0,0 +1,59 @@
package org.jetlinks.community.buffer;
import java.io.File;
import java.util.Map;
class DiskUsageEviction extends AbstractBufferEviction {
private final File path;
private final float threshold;
public DiskUsageEviction(File path, float threshold) {
this.path = path;
this.threshold = threshold;
}
private volatile float usage;
private volatile long lastUpdateTime;
@Override
public boolean doEviction(EvictionContext context) {
tryUpdate();
if (freeOutOfThreshold()) {
context.removeOldest(EvictionContext.BufferType.buffer);
return true;
}
return false;
}
protected boolean freeOutOfThreshold() {
return usage >= threshold;
}
private void tryUpdate() {
long now = System.currentTimeMillis();
//1秒更新一次
if (now - lastUpdateTime <= 1000) {
return;
}
long total = path.getTotalSpace();
long usable = path.getUsableSpace();
usage = (float) ((total - usable) / (double) total);
lastUpdateTime = now;
}
@Override
protected void applyEventData(Map<String, Object> data) {
data.put("usage", String.format("%.2f%%", usage * 100));
}
@Override
public String toString() {
return "DiskUsage(path=" + path
+ ", threshold=" + String.format("%.2f%%", threshold * 100)
+ ", usage=" + String.format("%.2f%%", usage * 100) + ")";
}
}

View File

@@ -0,0 +1,44 @@
package org.jetlinks.community.buffer;
/**
* 缓冲淘汰上下文
*
* @author zhouhao
* @since 2.0
*/
public interface EvictionContext {
/**
* 获取指定类型的数据量
*
* @param type 类型
* @return 数据量
*/
long size(BufferType type);
/**
* 删除最新的数据
*
* @param type 类型
*/
void removeLatest(BufferType type);
/**
* 删除最旧的数据
*
* @param type 类型
*/
void removeOldest(BufferType type);
/**
* @return 缓冲区名称, 用于区分多个不同的缓冲区
*/
String getName();
enum BufferType {
//缓冲区
buffer,
//死数据
dead
}
}

View File

@@ -0,0 +1,7 @@
package org.jetlinks.community.buffer;
public interface MemoryUsage {
int usage();
}

View File

@@ -0,0 +1,37 @@
package org.jetlinks.community.buffer;
import lombok.AllArgsConstructor;
import java.util.Map;
@AllArgsConstructor
class SizeLimitEviction extends AbstractBufferEviction {
private final long bufferLimit;
private final long deadLimit;
@Override
public boolean doEviction(EvictionContext context) {
boolean anyEviction = false;
if (bufferLimit > 0 && context.size(EvictionContext.BufferType.buffer) >= bufferLimit) {
context.removeOldest(EvictionContext.BufferType.buffer);
anyEviction = true;
}
if (deadLimit > 0 && context.size(EvictionContext.BufferType.dead) >= deadLimit) {
context.removeOldest(EvictionContext.BufferType.dead);
anyEviction = true;
}
return anyEviction;
}
@Override
protected void applyEventData(Map<String, Object> data) {
data.put("bufferLimit", bufferLimit);
data.put("deadLimit", deadLimit);
}
@Override
public String toString() {
return "SizeLimit(buffer=" + bufferLimit + ", dead=" + deadLimit + ")";
}
}

View File

@@ -0,0 +1,15 @@
package org.jetlinks.community.codec;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
public interface ObjectSerializer {
ObjectInput createInput(InputStream stream);
ObjectOutput createOutput(OutputStream stream);
}

View File

@@ -0,0 +1,71 @@
package org.jetlinks.community.codec;
import io.netty.util.concurrent.FastThreadLocal;
import lombok.SneakyThrows;
import org.nustaq.serialization.FSTConfiguration;
import java.io.*;
public class Serializers {
private static final ObjectSerializer JDK = new ObjectSerializer() {
@Override
@SneakyThrows
public ObjectInput createInput(InputStream stream) {
return new ObjectInputStream(stream);
}
@Override
@SneakyThrows
public ObjectOutput createOutput(OutputStream stream) {
return new ObjectOutputStream(stream);
}
};
private static final ObjectSerializer FST = new ObjectSerializer() {
final FastThreadLocal<FSTConfiguration> conf =
new FastThreadLocal<FSTConfiguration>() {
@Override
protected FSTConfiguration initialValue() {
FSTConfiguration configuration = FSTConfiguration.createDefaultConfiguration();
configuration.setForceSerializable(true);
configuration.setClassLoader(FST.getClass().getClassLoader());
return configuration;
}
};
@Override
@SneakyThrows
public ObjectInput createInput(InputStream stream) {
return conf.get().getObjectInput(stream);
}
@Override
@SneakyThrows
public ObjectOutput createOutput(OutputStream stream) {
return conf.get().getObjectOutput(stream);
}
};
private static final ObjectSerializer DEFAULT;
static {
DEFAULT = System.getProperty("jetlinks.object.serializer.type", "fst").equals("fst") ? FST : JDK;
}
public static ObjectSerializer jdk() {
return JDK;
}
public static ObjectSerializer fst() {
return FST;
}
public static ObjectSerializer getDefault() {
return DEFAULT;
}
}

View File

@@ -0,0 +1,102 @@
package org.jetlinks.community.command;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hswebframework.web.bean.FastBeanCopier;
import org.jetlinks.core.command.CommandSupport;
import org.jetlinks.core.utils.SerializeUtils;
import org.jetlinks.community.spi.Provider;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Map;
/**
* 命令支持提供者,用于针对多个基于命令模式的可选模块依赖时的解耦.
* <p>
*
* @author zhouhao
* @see org.jetlinks.sdk.server.SdkServices
* @see InternalSdkServices
* @since 2.1
*/
public interface CommandSupportManagerProvider {
/**
* 所有支持的提供商
*/
Provider<CommandSupportManagerProvider> supports = Provider.create(CommandSupportManagerProvider.class);
/**
* 命令服务提供商标识
*
* @return 唯一标识
*/
String getProvider();
/**
* 获取命令支持,不同的命令管理支持多种命令支持,可能通过id进行区分,具体规则由对应服务实 现
*
* @param id 命令ID标识
* @param options 拓展配置
* @return CommandSupport
* @see CommandSupportManagerProviders#getCommandSupport(String, Map)
*/
Mono<? extends CommandSupport> getCommandSupport(String id, Map<String, Object> options);
/**
* 获取所有支持的信息
*
* @return id
*/
default Flux<CommandSupportInfo> getSupportInfo() {
return Flux.empty();
}
@Getter
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
@Setter
class CommandSupportInfo implements Externalizable {
private String id;
private String name;
private String description;
public CommandSupportInfo copy() {
return FastBeanCopier.copy(this, new CommandSupportInfo());
}
/**
* @param serviceId serviceId
* @return this
* @see CommandSupportManagerProviders#getCommandSupport(String)
*/
public CommandSupportInfo appendService(String serviceId) {
if (this.id == null) {
this.id = serviceId;
} else {
this.id = serviceId + ":" + id;
}
return this;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
SerializeUtils.writeNullableUTF(id, out);
SerializeUtils.writeNullableUTF(name, out);
SerializeUtils.writeNullableUTF(description, out);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
id = SerializeUtils.readNullableUTF(in);
name = SerializeUtils.readNullableUTF(in);
description = SerializeUtils.readNullableUTF(in);
}
}
}

View File

@@ -0,0 +1,121 @@
package org.jetlinks.community.command;
import org.jetlinks.core.command.CommandSupport;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
/**
* 命令支持管理提供商工具类,用于提供对{@link CommandSupportManagerProvider}相关通用操作.
*
* @author zhouhao
* @see CommandSupportManagerProvider
* @see CommandSupportManagerProviders#getCommandSupport(String, Map)
* @since 2.2
*/
public class CommandSupportManagerProviders {
/**
* 根据服务ID获取CommandSupport.
* <pre>{@code
*
* CommandSupportManagerProviders
* .getCommandSupport("deviceService:device",Collections.emptyMap())
*
* }</pre>
*
* @param serviceId serviceId 服务名
* @return CommandSupport
* @see InternalSdkServices
* @see org.jetlinks.sdk.server.SdkServices
*/
public static Mono<CommandSupport> getCommandSupport(String serviceId) {
return getCommandSupport(serviceId, Collections.emptyMap());
}
/**
* 根据服务ID和支持ID获取CommandSupport.
*
* @param serviceId 服务ID
* @param supportId 支持ID
* @return CommandSupport
*/
public static Mono<CommandSupport> getCommandSupport(String serviceId, String supportId) {
return getProviderNow(serviceId)
.getCommandSupport(supportId, Collections.emptyMap())
.cast(CommandSupport.class);
}
/**
* 根据服务ID获取CommandSupport.
* <pre>{@code
*
* CommandSupportManagerProviders
* .getCommandSupport("deviceService:device",Collections.emptyMap())
*
* }</pre>
*
* @param serviceId serviceId 服务名
* @param options options
* @return CommandSupport
* @see InternalSdkServices
* @see org.jetlinks.sdk.server.SdkServices
*/
public static Mono<CommandSupport> getCommandSupport(String serviceId,
Map<String, Object> options) {
//fast path
CommandSupportManagerProvider provider = CommandSupportManagerProvider
.supports
.get(serviceId)
.orElse(null);
if (provider != null) {
return provider
.getCommandSupport(serviceId, options)
.cast(CommandSupport.class);
}
String supportId = serviceId;
// deviceService:product
if (serviceId.contains(":")) {
String[] arr = serviceId.split(":", 2);
serviceId = arr[0];
supportId = arr[1];
}
String finalServiceId = serviceId;
String finalSupportId = supportId;
return Mono.defer(() -> getProviderNow(finalServiceId).getCommandSupport(finalSupportId, options));
}
/**
* 注册命令支持
*
* @param provider {@link CommandSupportManagerProvider#getProvider()}
*/
public static void register(CommandSupportManagerProvider provider) {
CommandSupportManagerProvider.supports.register(provider.getProvider(), provider);
}
/**
* 获取命令支持
*
* @param provider {@link CommandSupportManagerProvider#getProvider()}
* @return Optional
*/
public static Optional<CommandSupportManagerProvider> getProvider(String provider) {
return CommandSupportManagerProvider.supports.get(provider);
}
/**
* 获取命令支持,如果不存在则抛出异常{@link UnsupportedOperationException}
*
* @param provider provider {@link CommandSupportManagerProvider#getProvider()}
* @return CommandSupportManagerProvider
*/
public static CommandSupportManagerProvider getProviderNow(String provider) {
return CommandSupportManagerProvider.supports.getNow(provider);
}
}

View File

@@ -0,0 +1,37 @@
package org.jetlinks.community.command;
import lombok.AllArgsConstructor;
import org.jetlinks.core.command.CommandSupport;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
@AllArgsConstructor
public class CompositeCommandSupportManagerProvider implements CommandSupportManagerProvider {
private final List<CommandSupportManagerProvider> providers;
@Override
public String getProvider() {
return providers.get(0).getProvider();
}
@Override
public Mono<? extends CommandSupport> getCommandSupport(String id, Map<String, Object> options) {
return Flux
.fromIterable(providers)
.flatMap(provider -> provider.getCommandSupport(id, options))
.take(1)
.singleOrEmpty();
}
@Override
public Flux<CommandSupportInfo> getSupportInfo() {
return Flux
.fromIterable(providers)
.flatMap(CommandSupportManagerProvider::getSupportInfo)
.distinct(CommandSupportInfo::getId);
}
}

View File

@@ -0,0 +1,307 @@
package org.jetlinks.community.command;
import lombok.SneakyThrows;
import org.hswebframework.web.api.crud.entity.EntityFactoryHolder;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.exception.AccessDenyException;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.crud.service.ReactiveCrudService;
import org.jetlinks.core.command.AbstractCommandSupport;
import org.jetlinks.core.command.Command;
import org.jetlinks.core.metadata.FunctionMetadata;
import org.jetlinks.core.metadata.SimplePropertyMetadata;
import org.jetlinks.core.metadata.types.ArrayType;
import org.jetlinks.core.metadata.types.IntType;
import org.jetlinks.core.metadata.types.ObjectType;
import org.jetlinks.core.metadata.types.StringType;
import org.jetlinks.core.utils.Reactors;
import org.jetlinks.sdk.server.commons.cmd.*;
import org.jetlinks.supports.official.DeviceMetadataParser;
import org.springframework.core.ResolvableType;
import reactor.bool.BooleanUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 通用增删改查命令支持,基于{@link ReactiveCrudService}来实现增删改查相关命令
*
* @param <T> 实体类型
* @author zhouhao
* @see QueryByIdCommand
* @see QueryPagerCommand
* @see QueryListCommand
* @see CountCommand
* @see SaveCommand
* @see AddCommand
* @see UpdateCommand
* @see DeleteCommand
* @see DeleteByIdCommand
* @since 2.2
*/
public class CrudCommandSupport<T> extends AbstractCommandSupport {
final ReactiveCrudService<T, String> service;
final ResolvableType _entityType;
public CrudCommandSupport(ReactiveCrudService<T, String> service) {
this(service, ResolvableType
.forClass(ReactiveCrudService.class, service.getClass())
.getGeneric(0));
}
public CrudCommandSupport(ReactiveCrudService<T, String> service, ResolvableType _entityType) {
this.service = service;
this._entityType = _entityType;
registerQueries();
registerSaves();
registerDelete();
}
@Override
public Flux<FunctionMetadata> getCommandMetadata() {
return super
.getCommandMetadata()
.filterWhen(func -> commandIsSupported(func.getId()));
}
@Override
public Mono<Boolean> commandIsSupported(String commandId) {
return BooleanUtils.and(
super.commandIsSupported(commandId),
hasPermission(getPermissionId(), getAction(commandId))
);
}
@Override
public Mono<FunctionMetadata> getCommandMetadata(String commandId) {
return super
.getCommandMetadata(commandId)
.filterWhen(func -> commandIsSupported(func.getId()));
}
@Override
public Mono<FunctionMetadata> getCommandMetadata(Command<?> command) {
return super
.getCommandMetadata(command)
.filterWhen(func -> commandIsSupported(func.getId()));
}
@Override
public Mono<FunctionMetadata> getCommandMetadata(@Nonnull String commandId,
@Nullable Map<String, Object> parameters) {
return super
.getCommandMetadata(commandId, parameters)
.filterWhen(func -> commandIsSupported(func.getId()));
}
@SneakyThrows
@SuppressWarnings("all")
private T newInstance0() {
return (T) _entityType.toClass().getConstructor().newInstance();
}
protected T newInstance() {
@SuppressWarnings("all")
Class<T> clazz = (Class<T>) _entityType.toClass();
return EntityFactoryHolder
.newInstance(clazz,
this::newInstance0);
}
protected ResolvableType getResolvableType() {
return _entityType;
}
protected ObjectType createEntityType() {
return (ObjectType) DeviceMetadataParser.withType(_entityType);
}
protected String getPermissionId() {
return null;
}
protected Mono<Void> assetPermission(String action) {
return assetPermission(getPermissionId(), action);
}
protected Mono<Boolean> hasPermission(String permissionId, String action) {
if (permissionId == null) {
return Reactors.ALWAYS_TRUE;
}
return Authentication
.currentReactive()
.map(auth -> auth.hasPermission(permissionId, action))
.defaultIfEmpty(true);
}
protected Mono<Void> assetPermission(String permissionId, String action) {
if (permissionId == null) {
return Mono.empty();
}
return Authentication
.currentReactive()
.flatMap(
auth -> auth.hasPermission(permissionId, action)
? Mono.empty()
: Mono.error(new AccessDenyException.NoStackTrace(permissionId, Collections.singleton(action))));
}
protected String getAction(String commandId) {
if (commandId.startsWith("Delete")) {
return Permission.ACTION_DELETE;
}
if (commandId.startsWith("Update") ||
commandId.startsWith("Save") ||
commandId.startsWith("Add") ||
commandId.startsWith("Disable") ||
commandId.startsWith("Enable")) {
return Permission.ACTION_SAVE;
}
return Permission.ACTION_QUERY;
}
protected void registerQueries() {
//根据id查询
registerHandler(
QueryByIdCommand
.<T>createHandler(
metadata -> {
metadata
.setInputs(Collections.singletonList(
SimplePropertyMetadata
.of("id", "id", new ArrayType()
.elementType(StringType.GLOBAL))
));
metadata.setOutput(createEntityType());
},
cmd -> assetPermission(Permission.ACTION_QUERY)
.then(service.findById(cmd.getId())),
_entityType)
);
//分页查询
registerHandler(
QueryPagerCommand
.<T>createHandler(
metadata -> metadata.setOutput(
QueryPagerCommand
.createOutputType(createEntityType().getProperties())),
cmd -> assetPermission(Permission.ACTION_QUERY)
.then(service.queryPager(cmd.asQueryParam())),
_entityType)
);
//查询列表
registerHandler(
QueryListCommand
.<T>createHandler(
metadata -> metadata.setOutput(createEntityType()),
cmd -> assetPermission(Permission.ACTION_QUERY)
.thenMany(service.query(cmd.asQueryParam())),
_entityType)
);
//查询数量
registerHandler(
CountCommand
.createHandler(
metadata -> metadata.setOutput(new ObjectType()
.addProperty("total", "总数", IntType.GLOBAL)),
cmd -> assetPermission(Permission.ACTION_QUERY)
.then(service.count(cmd.asQueryParam())))
);
//todo 聚合查询?
}
protected void registerSaves() {
//批量保存
registerHandler(
SaveCommand.createHandler(
metadata -> {
metadata
.setInputs(Collections.singletonList(
SimplePropertyMetadata.of("data", "数据列表", new ArrayType().elementType(createEntityType()))
));
metadata.setOutput(createEntityType());
},
cmd -> {
List<T> list = cmd.dataList((data) -> FastBeanCopier.copy(data, newInstance()));
return assetPermission(Permission.ACTION_SAVE)
.then(service.save(list))
.thenMany(Flux.fromIterable(list));
},
_entityType)
);
//新增
registerHandler(
AddCommand
.<T>createHandler(
metadata -> {
metadata
.setInputs(Collections.singletonList(
SimplePropertyMetadata.of("data", "数据列表", new ArrayType().elementType(createEntityType()))
));
metadata.setOutput(createEntityType());
},
cmd -> Flux
.fromIterable(cmd.dataList((data) -> FastBeanCopier.copy(data, newInstance())))
.as(flux -> assetPermission(Permission.ACTION_SAVE)
.then(service.insert(flux))
.thenMany(flux)))
);
//修改
registerHandler(
UpdateCommand
.<T>createHandler(
metadata -> {
metadata.setInputs(
Arrays.asList(
SimplePropertyMetadata.of("data", "数据", createEntityType()),
QueryCommand.getTermsMetadata()
));
metadata.setOutput(IntType.GLOBAL);
},
cmd -> this
.assetPermission(Permission.ACTION_SAVE)
.then(cmd
.applyUpdate(service.createUpdate(), map -> FastBeanCopier.copy(map, newInstance()))
.execute()))
);
}
protected void registerDelete() {
//删除
registerHandler(
DeleteCommand.createHandler(
metadata -> {
metadata.setInputs(Collections.singletonList(
SimplePropertyMetadata.of("terms", "删除条件", QueryCommand.getTermsDataType())));
metadata.setOutput(IntType.GLOBAL);
},
cmd -> this
.assetPermission(Permission.ACTION_DELETE)
.then(cmd.applyDelete(service.createDelete()).execute()))
);
//根据id移除
registerHandler(
DeleteByIdCommand
.<Mono<Void>>createHandler(
metadata -> {
},
cmd -> this
.assetPermission(Permission.ACTION_DELETE)
.then(service.deleteById(cmd.getId()).then()))
);
}
}

View File

@@ -0,0 +1,43 @@
package org.jetlinks.community.command;
/**
* 平台内部的一些服务定义
*
* @author zhouhao
* @see org.jetlinks.sdk.server.SdkServices
* @since 2.2
*/
public interface InternalSdkServices {
/**
* 网络组件服务
*/
String networkService = "networkService";
/**
* 设备接入网关服务
*/
String deviceGatewayService = "deviceGatewayService";
/**
* 采集器服务
*/
String collectorService = "collectorService";
/**
* 规则服务
*/
String ruleService = "ruleService";
/**
* 插件服务
*/
String pluginService = "pluginService";
/**
* 基础服务
*/
String commonService = "commonService";
}

View File

@@ -0,0 +1,75 @@
package org.jetlinks.community.command;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.hswebframework.web.i18n.LocaleUtils;
import org.jetlinks.core.command.AbstractCommandSupport;
import org.jetlinks.core.command.CommandSupport;
import org.jetlinks.community.annotation.command.CommandService;
import org.springframework.core.annotation.AnnotationUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* 通用静态命令管理.
*
* @author zhangji 2024/2/2
* @since 2.2.0
*/
@AllArgsConstructor
public class StaticCommandSupportManagerProvider extends AbstractCommandSupport implements CommandSupportManagerProvider {
@Getter
public String provider;
private final Map<String, CommandSupport> commandSupports = new HashMap<>();
public void register(String id, CommandSupport commandSupport) {
commandSupports.put(id, commandSupport);
}
@Override
public final Mono<? extends CommandSupport> getCommandSupport(String id, Map<String, Object> options) {
CommandSupport cmd = commandSupports.get(id);
if (cmd == null) {
return getUndefined(id, options);
}
return Mono.just(cmd);
}
protected Mono<? extends CommandSupport> getUndefined(String id, Map<String, Object> options) {
return Mono.just(this);
}
@Override
public Flux<CommandSupportInfo> getSupportInfo() {
Flux<CommandSupportInfo> another = Flux
.fromIterable(commandSupports.entrySet())
.map(entry -> createCommandSupport(entry.getKey(), entry.getValue().getClass()));
if (!this.handlers.isEmpty()) {
return Flux.concat(another, Flux.just(createCommandSupport(null, this.getClass())));
}
return another;
}
protected final CommandSupportInfo createCommandSupport(String id, Class<?> clazz) {
String name = id;
String description = null;
Schema schema = AnnotationUtils.findAnnotation(clazz, Schema.class);
if (null != schema) {
name = schema.title();
description = schema.description();
}
CommandService service = AnnotationUtils.findAnnotation(clazz, CommandService.class);
if (null != service) {
name = LocaleUtils.resolveMessage(service.name(), service.name());
description = String.join("", service.description());
}
return CommandSupportInfo.of(id, name, description);
}
}

View File

@@ -0,0 +1,83 @@
package org.jetlinks.community.command.register;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.community.command.CommandSupportManagerProvider;
import org.jetlinks.community.command.CommandSupportManagerProviders;
import org.jetlinks.community.command.CompositeCommandSupportManagerProvider;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.ClassUtils;
import org.jetlinks.community.annotation.command.CommandService;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
public class CommandServiceEndpointRegister implements ApplicationContextAware, SmartInitializingSingleton {
private ApplicationContext context;
@Override
public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
@Override
public void afterSingletonsInstantiated() {
Map<String, Object> beans = context.getBeansWithAnnotation(CommandService.class);
//静态Provider
Map<String, List<CommandSupportManagerProvider>> statics = context
.getBeanProvider(CommandSupportManagerProvider.class)
.stream()
.collect(Collectors.groupingBy(CommandSupportManagerProvider::getProvider));
Map<String, SpringBeanCommandSupportProvider> providers = new HashMap<>();
for (Object value : beans.values()) {
CommandService endpoint =
AnnotatedElementUtils.findMergedAnnotation(ClassUtils.getUserClass(value), CommandService.class);
if (endpoint == null || !endpoint.autoRegistered()) {
continue;
}
String id = endpoint.id();
String support = id;
if (id.contains(":")) {
support = id.substring(id.indexOf(":") + 1);
id = id.substring(0, id.indexOf(":"));
}
SpringBeanCommandSupportProvider provider = providers
.computeIfAbsent(id, SpringBeanCommandSupportProvider::new);
log.debug("register command support:{} -> {}", endpoint.id(), value);
provider.register(support, endpoint, value);
}
for (SpringBeanCommandSupportProvider value : providers.values()) {
if (value.isEmpty()) {
continue;
}
//合并静态Provider
List<CommandSupportManagerProvider> provider = statics.remove(value.getProvider());
if (provider != null) {
provider.forEach(value::register);
}
CommandSupportManagerProviders.register(value);
}
for (List<CommandSupportManagerProvider> value : statics.values()) {
if (value.size() == 1) {
CommandSupportManagerProviders.register(value.get(0));
} else {
CommandSupportManagerProviders.register(new CompositeCommandSupportManagerProvider(value));
}
}
}
}

View File

@@ -0,0 +1,138 @@
package org.jetlinks.community.command.register;
import com.google.common.collect.Lists;
import lombok.Getter;
import org.hswebframework.web.i18n.LocaleUtils;
import org.jetlinks.core.command.CommandSupport;
import org.jetlinks.core.command.CompositeCommandSupport;
import org.jetlinks.community.annotation.command.CommandService;
import org.jetlinks.community.command.CommandSupportManagerProvider;
import org.jetlinks.supports.command.JavaBeanCommandSupport;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.*;
class SpringBeanCommandSupportProvider implements CommandSupportManagerProvider {
private final String provider;
private final Map<String, CompositeSpringBeanCommandSupport> commandSupports = new HashMap<>();
private final List<CommandSupportManagerProvider> statics = new ArrayList<>();
public SpringBeanCommandSupportProvider(String provider) {
this.provider = provider;
}
void register(CommandSupportManagerProvider provider) {
statics.add(provider);
}
void register(String support, CommandService annotation, Object bean) {
Objects.requireNonNull(annotation, "endpoint");
Objects.requireNonNull(bean, "bean");
SpringBeanCommandSupport commandSupport = new SpringBeanCommandSupport(annotation, bean);
if (commandSupport.isEmpty()) {
return;
}
//相同support合并成一个
commandSupports
.computeIfAbsent(support, id -> new CompositeSpringBeanCommandSupport(id,provider))
.register(commandSupport);
}
boolean isEmpty() {
return commandSupports.isEmpty();
}
@Override
public String getProvider() {
return provider;
}
@Override
public Mono<? extends CommandSupport> getCommandSupport(String id, Map<String, Object> options) {
CommandSupport support = commandSupports.get(StringUtils.hasText(id) ? id : provider);
if (support != null) {
return Mono.just(support);
}
if (statics.isEmpty()) {
return Mono.empty();
}
return Flux
.fromIterable(statics)
.flatMap(provider -> provider.getCommandSupport(id, options))
.take(1)
.singleOrEmpty();
}
@Override
public Flux<CommandSupportInfo> getSupportInfo() {
if (statics.isEmpty()) {
return Flux
.fromIterable(commandSupports.values())
.flatMapIterable(CompositeSpringBeanCommandSupport::getInfo);
}
return Flux
.concat(
Flux
.fromIterable(commandSupports.values())
.flatMapIterable(CompositeSpringBeanCommandSupport::getInfo),
Flux.fromIterable(statics)
.flatMap(CommandSupportManagerProvider::getSupportInfo)
)
.distinct(info -> {
String id = info.getId();
return String.valueOf(id);
});
}
static class CompositeSpringBeanCommandSupport extends CompositeCommandSupport {
private final String id;
private final String provider;
public CompositeSpringBeanCommandSupport(String id,String provider) {
super();
this.id = id;
this.provider = provider;
}
public List<CommandSupportInfo> getInfo() {
return Lists
.transform(
getSupports(),
support -> {
SpringBeanCommandSupport commandSupport = support.unwrap(SpringBeanCommandSupport.class);
//兼容为null
String _id = id.equals(provider) ? null : id;
return CommandSupportInfo.of(
_id,
LocaleUtils.resolveMessage(commandSupport.annotation.name(), commandSupport.annotation.name()),
String.join("", commandSupport.annotation.description())
);
});
}
@Override
public String toString() {
return getSupports().toString();
}
}
@Getter
static class SpringBeanCommandSupport extends JavaBeanCommandSupport {
private final CommandService annotation;
boolean isEmpty() {
return handlers.isEmpty();
}
public SpringBeanCommandSupport(CommandService annotation, Object target) {
super(target);
this.annotation = annotation;
}
}
}

View File

@@ -0,0 +1,35 @@
package org.jetlinks.community.command.rule;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hswebframework.web.bean.FastBeanCopier;
import org.jetlinks.core.command.AbstractCommand;
import org.jetlinks.core.command.CommandMetadataResolver;
import org.jetlinks.core.command.CommandUtils;
import org.jetlinks.core.metadata.FunctionMetadata;
import org.jetlinks.core.metadata.SimpleFunctionMetadata;
import org.jetlinks.community.command.rule.data.RelieveInfo;
import org.jetlinks.community.command.rule.data.RelieveResult;
import org.springframework.core.ResolvableType;
import reactor.core.publisher.Mono;
@Schema(title = "解除告警命令")
public class RelievedAlarmCommand extends AbstractCommand<Mono<RelieveResult>,RelievedAlarmCommand> {
@Schema(description = "解除告警传参信息")
public RelieveInfo getRelieveInfo() {
return FastBeanCopier.copy(readable(), new RelieveInfo());
}
public RelievedAlarmCommand setRelieveInfo(RelieveInfo relieveInfo) {
return with(FastBeanCopier.copy(relieveInfo, writable()));
}
public static FunctionMetadata metadata() {
SimpleFunctionMetadata metadata = new SimpleFunctionMetadata();
metadata.setId(CommandUtils.getCommandIdByType(RelievedAlarmCommand.class));
metadata.setName("解除告警命令");
metadata.setInputs(CommandMetadataResolver.resolveInputs(ResolvableType.forClass(RelieveInfo.class)));
metadata.setOutput(CommandMetadataResolver.resolveOutput(ResolvableType.forClass(RelievedAlarmCommand.class)));
return metadata;
}
}

View File

@@ -0,0 +1,34 @@
package org.jetlinks.community.command.rule;
public interface RuleCommandServices {
/**
* 场景
*/
String sceneService = "sceneService";
/**
* 告警配置
*/
String alarmConfigService = "alarmConfigService";
/**
* 告警记录
*/
String alarmRecordService = "alarmRecordService";
/**
* 告警历史
*/
String alarmHistoryService = "alarmHistoryService";
/**
* 告警规则绑定
*/
String alarmRuleBindService = "alarmRuleBindService";
/**
* 告警相关
*/
String alarm = "alarm";
}

View File

@@ -0,0 +1,38 @@
package org.jetlinks.community.command.rule;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hswebframework.web.bean.FastBeanCopier;
import org.jetlinks.core.command.AbstractCommand;
import org.jetlinks.core.command.CommandMetadataResolver;
import org.jetlinks.core.command.CommandUtils;
import org.jetlinks.core.metadata.FunctionMetadata;
import org.jetlinks.core.metadata.SimpleFunctionMetadata;
import org.jetlinks.community.command.rule.data.AlarmInfo;
import org.jetlinks.community.command.rule.data.AlarmResult;
import org.springframework.core.ResolvableType;
import reactor.core.publisher.Mono;
@Schema(title = "触发告警命令")
public class TriggerAlarmCommand extends AbstractCommand<Mono<AlarmResult>,TriggerAlarmCommand> {
private static final long serialVersionUID = 7056867872399432831L;
@Schema(description = "告警传参信息")
public AlarmInfo getAlarmInfo() {
return FastBeanCopier.copy(readable(), new AlarmInfo());
}
public TriggerAlarmCommand setAlarmInfo(AlarmInfo alarmInfo) {
return with(FastBeanCopier.copy(alarmInfo, writable()));
}
public static FunctionMetadata metadata() {
SimpleFunctionMetadata metadata = new SimpleFunctionMetadata();
metadata.setId(CommandUtils.getCommandIdByType(TriggerAlarmCommand.class));
metadata.setName("触发告警命令");
metadata.setInputs(CommandMetadataResolver.resolveInputs(ResolvableType.forClass(AlarmInfo.class)));
metadata.setOutput(CommandMetadataResolver.resolveOutput(ResolvableType.forClass(TriggerAlarmCommand.class)));
return metadata;
}
}

View File

@@ -0,0 +1,74 @@
package org.jetlinks.community.command.rule.data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.jetlinks.community.terms.TermSpec;
import java.io.Serializable;
import java.util.Map;
/**
* 触发告警参数
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class AlarmInfo implements Serializable {
private static final long serialVersionUID = -2316376361116648370L;
@Schema(description = "告警配置ID")
private String alarmConfigId;
@Schema(description = "告警名称")
private String alarmName;
@Schema(description = "告警说明")
private String description;
@Schema(description = "告警级别")
private int level;
@Schema(description = "告警目标类型")
private String targetType;
@Schema(description = "告警目标ID")
private String targetId;
@Schema(description = "告警目标名称")
private String targetName;
@Schema(description = "告警来源类型")
private String sourceType;
@Schema(description = "告警来源ID")
private String sourceId;
@Schema(description = "告警来源的创建人ID")
private String sourceCreatorId;
@Schema(description = "告警来源名称")
private String sourceName;
/**
* 标识告警触发的配置来自什么业务功能
*/
@Schema(description = "告警配置源")
private String alarmConfigSource;
@Schema(description = "告警数据")
private Map<String, Object> data;
/**
* 告警触发条件
*/
private TermSpec termSpec;
@Schema(description = "告警时间")
private Long alarmTime;
}

View File

@@ -0,0 +1,36 @@
package org.jetlinks.community.command.rule.data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
/**
* 告警结果
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class AlarmResult implements Serializable {
private static final long serialVersionUID = -1752497262936740164L;
@Schema(description = "告警ID")
private String recordId;
@Schema(description = "是否重复告警")
private boolean alarming;
@Schema(description = "当前首次触发")
private boolean firstAlarm;
@Schema(description = "上一次告警时间")
private long lastAlarmTime;
@Schema(description = "首次告警或者解除告警后的再一次告警时间")
private long alarmTime;
}

View File

@@ -0,0 +1,32 @@
package org.jetlinks.community.command.rule.data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 解除告警参数
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class RelieveInfo extends AlarmInfo{
@Schema(description = "解除原因")
private String relieveReason;
@Schema(description = "解除时间")
private Long relieveTime;
@Schema(description = "解除说明")
private String describe;
/**
* 告警解除类型人工user、系统system
*/
@Schema(description = "告警解除类型")
private String alarmRelieveType;
}

View File

@@ -0,0 +1,32 @@
package org.jetlinks.community.command.rule.data;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 解除警告结果
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class RelieveResult extends AlarmResult{
@Schema(description = "告警级别")
private int level;
@Schema(description = "告警原因描述")
private String actualDesc;
@Schema(description = "解除原因")
private String relieveReason;
@Schema(description = "解除时间")
private long relieveTime;
@Schema(description = "解除说明")
private String describe;
}

View File

@@ -0,0 +1,56 @@
package org.jetlinks.community.config;
import org.jetlinks.community.ValueObject;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Map;
/**
* 配置管理器,统一管理系统相关配置信息
*
* @author zhouhao
* @since 2.0
*/
public interface ConfigManager {
/**
* 获取全部已经定义的配置作用域
*
* @return 配置作用域
*/
Flux<ConfigScope> getScopes();
/**
* 获取根据作用域ID获取已经定义的配置作用域
*
* @return 配置作用域
*/
Mono<ConfigScope> getScope(String scope);
/**
* 获取指定作用域下的属性定义信息
*
* @param scope 配置作用域
* @return 属性定义信息
*/
Flux<ConfigPropertyDef> getPropertyDef(String scope);
/**
* 获取作用于下的全部配置
*
* @param scope 配置作用域
* @return 配置信息
*/
Mono<ValueObject> getProperties(String scope);
/**
* 设置作用域下的配置
*
* @param scope 作用域
* @param values 配置信息
* @return void
*/
Mono<Void> setProperties(String scope, Map<String, Object> values);
}

View File

@@ -0,0 +1,28 @@
package org.jetlinks.community.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import org.jetlinks.core.metadata.types.StringType;
@Getter
@Setter
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
@EqualsAndHashCode(of = "key")
public class ConfigPropertyDef {
@Schema(description = "配置key")
private String key;
@Schema(description = "配置名称")
private String name;
@Schema(description = "是否只读")
private boolean readonly;
@Schema(description = "配置类型")
private String type = StringType.ID;
@Schema(description = "默认值")
private String defaultValue;
}

View File

@@ -0,0 +1,23 @@
package org.jetlinks.community.config;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
@Getter
@Setter
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
@EqualsAndHashCode(of = "id")
public class ConfigScope {
@Schema(description = "ID")
private String id;
@Schema(description = "名称")
private String name;
@Schema(description = "是否公开访问(不需要登录)")
private boolean publicAccess;
}

View File

@@ -0,0 +1,18 @@
package org.jetlinks.community.config;
/**
* 实现此接口,自定义配置域以及配置定义
*
* @author zhouhao
* @since 2.0
*/
public interface ConfigScopeCustomizer {
/**
* 执行自定义,通过manager来添加自定义作用域
*
* @param manager manager
*/
void custom(ConfigScopeManager manager);
}

View File

@@ -0,0 +1,9 @@
package org.jetlinks.community.config;
import java.util.List;
public interface ConfigScopeManager {
void addScope(ConfigScope scope, List<ConfigPropertyDef> properties);
}

View File

@@ -0,0 +1,31 @@
package org.jetlinks.community.config;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.web.bean.FastBeanCopier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
@ConfigurationProperties(prefix = "system.config")
public class ConfigScopeProperties implements ConfigScopeCustomizer{
@Getter
@Setter
private List<Scope> scopes = new ArrayList<>();
@Override
public void custom(ConfigScopeManager manager) {
for (Scope scope : scopes) {
manager.addScope(FastBeanCopier.copy(scope,new ConfigScope()), scope.properties);
}
}
@Getter
@Setter
public static class Scope extends ConfigScope {
private List<ConfigPropertyDef> properties = new ArrayList<>();
}
}

View File

@@ -0,0 +1,101 @@
package org.jetlinks.community.config;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.MapUtils;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.web.cache.ReactiveCache;
import org.hswebframework.web.cache.ReactiveCacheManager;
import org.jetlinks.community.ValueObject;
import org.jetlinks.community.config.entity.ConfigEntity;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@AllArgsConstructor
public class SimpleConfigManager implements ConfigManager, ConfigScopeManager {
private final Map<ConfigScope, Set<ConfigPropertyDef>> scopes = new ConcurrentHashMap<>();
private final ReactiveRepository<ConfigEntity, String> repository;
private final ReactiveCache<Map<String, Object>> cache;
public SimpleConfigManager(ReactiveRepository<ConfigEntity, String> repository, ReactiveCacheManager cacheManager) {
this.repository = repository;
this.cache = cacheManager.getCache("system-config");
}
@Override
public void addScope(ConfigScope scope,
List<ConfigPropertyDef> properties) {
scopes.computeIfAbsent(scope, ignore -> new LinkedHashSet<>())
.addAll(properties);
}
@Override
public Flux<ConfigScope> getScopes() {
return Flux.fromIterable(scopes.keySet());
}
@Override
public Mono<ConfigScope> getScope(String scope) {
return this
.getScopes()
.filter(configScope -> Objects.equals(configScope.getId(), scope))
.take(1)
.singleOrEmpty();
}
@Override
public Flux<ConfigPropertyDef> getPropertyDef(String scope) {
return Flux.fromIterable(scopes.getOrDefault(
ConfigScope.of(scope, scope, false),
Collections.emptySet()));
}
@Override
public Mono<ValueObject> getProperties(String scope) {
return Mono
.zip(
//默认值
getPropertyDef(scope)
.filter(def -> null != def.getDefaultValue())
.collectMap(ConfigPropertyDef::getKey, ConfigPropertyDef::getDefaultValue),
//数据库配置的值
cache
.getMono(scope, () -> getPropertiesNow(scope)),
(defaults, values) -> {
Map<String, Object> properties = new HashMap<>(values);
defaults.forEach(properties::putIfAbsent);
return properties;
}
)
.map(ValueObject::of);
}
private Mono<Map<String, Object>> getPropertiesNow(String scope) {
return repository
.createQuery()
.where(ConfigEntity::getScope, scope)
.fetch()
.filter(val -> MapUtils.isNotEmpty(val.getProperties()))
.reduce(new LinkedHashMap<>(), (l, r) -> {
l.putAll(r.getProperties());
return l;
});
}
@Override
public Mono<Void> setProperties(String scope, Map<String, Object> values) {
ConfigEntity entity = new ConfigEntity();
entity.setProperties(values);
entity.setScope(scope);
entity.getId();
return repository
.save(entity)
.then(cache.evict(scope));
}
}

View File

@@ -0,0 +1,48 @@
package org.jetlinks.community.config.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
import org.hswebframework.ezorm.rdb.mapping.annotation.JsonCodec;
import org.hswebframework.web.api.crud.entity.GenericEntity;
import org.hswebframework.web.crud.annotation.EnableEntityEvent;
import org.hswebframework.web.utils.DigestUtils;
import org.springframework.util.StringUtils;
import javax.persistence.Column;
import javax.persistence.Index;
import javax.persistence.Table;
import java.sql.JDBCType;
import java.util.Map;
@Table(name = "s_config", indexes = {
@Index(name = "idx_conf_scope", columnList = "scope")
})
@Getter
@Setter
@EnableEntityEvent
public class ConfigEntity extends GenericEntity<String> {
@Column(length = 64, nullable = false, updatable = false)
@Schema(description = "作用域")
private String scope;
@Column(nullable = false)
@Schema
@JsonCodec
@ColumnType(jdbcType = JDBCType.LONGVARCHAR)
private Map<String,Object> properties;
@Override
public String getId() {
if (!StringUtils.hasText(super.getId())) {
setId(generateId(scope));
}
return super.getId();
}
public static String generateId(String scope) {
return DigestUtils.md5Hex(String.join("|", scope));
}
}

View File

@@ -0,0 +1,95 @@
package org.jetlinks.community.config.verification;
import io.swagger.v3.oas.annotations.Operation;
import org.hswebframework.web.crud.events.EntitySavedEvent;
import org.hswebframework.web.exception.BusinessException;
import org.jetlinks.community.config.entity.ConfigEntity;
import org.jetlinks.reactor.ql.utils.CastUtils;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.TimeoutException;
/**
* @author bestfeng
*/
@RestController
public class ConfigVerificationService {
private final WebClient webClient;
private static final String PATH_VERIFICATION_URI = "/system/config/base-path/verification";
public ConfigVerificationService() {
this.webClient = WebClient
.builder()
.build();
}
@GetMapping(value = PATH_VERIFICATION_URI)
@Operation(description = "basePath配置验证接口")
public Mono<String> basePathValidate() {
return Mono.just("auth:"+PATH_VERIFICATION_URI);
}
@EventListener
public void handleConfigSavedEvent(EntitySavedEvent<ConfigEntity> event){
//base-path校验
event.async(
Flux.fromIterable(event.getEntity())
.filter(config -> Objects.equals(config.getScope(), "paths"))
.flatMap(config-> doBasePathValidate(config.getProperties().get("base-path")))
);
}
public Mono<Void> doBasePathValidate(Object basePath) {
if (basePath == null) {
return Mono.empty();
}
URI uri = URI.create(CastUtils.castString(CastUtils.castString(basePath).concat(PATH_VERIFICATION_URI)));
if (Objects.equals(uri.getHost(), "127.0.0.1")){
return Mono.error(new BusinessException("error.base_path_host_error", 500, "127.0.0.1"));
}
if (Objects.equals(uri.getHost(), "localhost")){
return Mono.error(new BusinessException("error.base_path_host_error", 500, "localhost"));
}
return webClient
.get()
.uri(uri)
.exchangeToMono(cr -> {
if (cr.statusCode().is2xxSuccessful()) {
return cr.bodyToMono(String.class)
.filter(r-> r.contains("auth:"+PATH_VERIFICATION_URI))
.switchIfEmpty(Mono.error(()-> new BusinessException("error.base_path_error")));
}
return Mono.defer(() -> Mono.error(new BusinessException("error.base_path_error")));
})
.timeout(Duration.ofSeconds(3), Mono.error(TimeoutException::new))
.onErrorResume(err -> {
while (err != null) {
if (err instanceof TimeoutException) {
return Mono.error(() -> new BusinessException("error.base_path_validate_request_timeout"));
} else if (err instanceof UnknownHostException) {
return Mono.error(() -> new BusinessException("error.base_path_DNS_resolution_failed"));
}
err = err.getCause();
}
return Mono.error(() -> new BusinessException("error.base_path_error"));
})
.then();
}
}

View File

@@ -0,0 +1,131 @@
package org.jetlinks.community.config.web;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.authorization.annotation.QueryAction;
import org.hswebframework.web.authorization.annotation.Resource;
import org.hswebframework.web.authorization.annotation.SaveAction;
import org.hswebframework.web.bean.FastBeanCopier;
import org.jetlinks.community.ValueObject;
import org.jetlinks.community.config.ConfigManager;
import org.jetlinks.community.config.ConfigPropertyDef;
import org.jetlinks.community.config.ConfigScope;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/system/config")
@Resource(id = "system_config", name = "系统配置管理")
@AllArgsConstructor
@Tag(name = "系统配置管理")
public class SystemConfigManagerController {
private final ConfigManager configManager;
@GetMapping("/scopes")
@QueryAction
@Operation(summary = "获取配置作用域")
public Flux<ConfigScope> getConfigScopes() {
return configManager.getScopes();
}
@GetMapping("/{scope}")
@Authorize(ignore = true)
@Operation(summary = "获取作用域下的全部配置信息")
public Mono<Map<String, Object>> getConfigs(@PathVariable String scope) {
return Authentication
.currentReactive()
.hasElement()
.flatMap(hasAuth -> configManager
.getScope(scope)
//公共访问配置或者用户已登录
.map(conf -> conf.isPublicAccess() || hasAuth)
//没有定义配置,则用户登录即可访问
.defaultIfEmpty(hasAuth)
.filter(Boolean::booleanValue)
.flatMap(ignore -> configManager.getProperties(scope))
.map(ValueObject::values))
.defaultIfEmpty(Collections.emptyMap());
}
@GetMapping("/{scope}/_detail")
@QueryAction
@Operation(summary = "获取作用域下的配置信息")
public Flux<ConfigPropertyValue> getConfigDetail(@PathVariable String scope) {
return configManager
.getProperties(scope)
.flatMapMany(values -> configManager
.getPropertyDef(scope)
.map(def -> ConfigPropertyValue.of(def, values.get(def.getKey()).orElse(null))));
}
@PostMapping("/scopes")
@QueryAction
@Operation(summary = "获取作用域下的配置详情")
public Flux<Scope> getConfigDetail(@RequestBody Mono<List<String>> scopeMono) {
return scopeMono
.flatMapMany(scopes -> Flux
.fromIterable(scopes)
.flatMap(scope -> getConfigs(scope)
.map(properties -> new Scope(scope, properties))));
}
@PostMapping("/{scope}")
@SaveAction
@Operation(summary = "保存配置")
public Mono<Void> saveConfig(@PathVariable String scope,
@RequestBody Mono<Map<String, Object>> properties) {
return properties.flatMap(props -> configManager.setProperties(scope, props));
}
@PostMapping("/scope/_save")
@SaveAction
@Operation(summary = "批量保存配置")
@Transactional
public Mono<Void> saveConfig(@RequestBody Flux<Scope> scope) {
return scope
.concatMap(scopeConfig -> configManager.setProperties(scopeConfig.getScope(), scopeConfig.getProperties()))
.then();
}
@Getter
@Setter
public static class ConfigPropertyValue extends ConfigPropertyDef {
private Object value;
public static ConfigPropertyValue of(ConfigPropertyDef def, Object value) {
ConfigPropertyValue val = FastBeanCopier.copy(def, new ConfigPropertyValue());
val.setValue(value);
return val;
}
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public static class Scope {
private String scope;
private Map<String, Object> properties;
}
}

View File

@@ -3,31 +3,72 @@ package org.jetlinks.community.configuration;
import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.Generated;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.Converter;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.cache.ReactiveCacheManager;
import org.hswebframework.web.dict.EnumDict;
import org.hswebframework.web.dict.defaults.DefaultItemDefine;
import org.jetlinks.community.Interval;
import org.jetlinks.community.JvmErrorException;
import org.jetlinks.community.command.register.CommandServiceEndpointRegister;
import org.jetlinks.community.config.ConfigManager;
import org.jetlinks.community.config.ConfigScopeCustomizer;
import org.jetlinks.community.config.ConfigScopeProperties;
import org.jetlinks.community.config.SimpleConfigManager;
import org.jetlinks.community.config.entity.ConfigEntity;
import org.jetlinks.community.dictionary.DictionaryJsonDeserializer;
import org.jetlinks.community.reactorql.aggregation.InternalAggregationSupports;
import org.jetlinks.community.reactorql.function.InternalFunctionSupport;
import org.jetlinks.community.reference.DataReferenceManager;
import org.jetlinks.community.reference.DataReferenceProvider;
import org.jetlinks.community.reference.DefaultDataReferenceManager;
import org.jetlinks.community.resource.DefaultResourceManager;
import org.jetlinks.community.resource.ResourceManager;
import org.jetlinks.community.resource.ResourceProvider;
import org.jetlinks.community.resource.TypeScriptDeclareResourceProvider;
import org.jetlinks.community.resource.initialize.PermissionResourceProvider;
import org.jetlinks.community.service.DefaultUserBindService;
import org.jetlinks.community.utils.TimeUtils;
import org.jetlinks.core.metadata.DataType;
import org.jetlinks.core.metadata.types.DataTypes;
import org.jetlinks.reactor.ql.feature.Feature;
import org.jetlinks.reactor.ql.supports.DefaultReactorQLMetadata;
import org.jetlinks.reactor.ql.utils.CastUtils;
import org.jetlinks.supports.official.JetLinksDataTypeCodecs;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.util.unit.DataSize;
import reactor.core.Exceptions;
import reactor.core.publisher.Hooks;
import javax.annotation.Nonnull;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Map;
@Configuration
@AutoConfiguration
@SuppressWarnings("all")
@EnableConfigurationProperties({ConfigScopeProperties.class})
public class CommonConfiguration {
static {
InternalAggregationSupports.register();
InternalFunctionSupport.register();
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
@Override
public <T> T convert(Class<T> aClass, Object o) {
@@ -94,6 +135,53 @@ public class CommonConfiguration {
return (T)((Long) CastUtils.castNumber(value).longValue());
}
}, Long.class);
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
@Override
@Generated
public <T> T convert(Class<T> type, Object value) {
if (value instanceof String) {
return (T) DefaultItemDefine.builder()
.value(String.valueOf(value))
.build();
}
return (T) FastBeanCopier.copy(value, new DefaultItemDefine());
}
}, EnumDict.class);
BeanUtilsBean.getInstance().getConvertUtils().register(new Converter() {
@Override
@Generated
public <T> T convert(Class<T> type, Object value) {
if (value instanceof Map) {
Map<String, Object> map = ((Map) value);
String typeId = (String) map.get("type");
if (StringUtils.isEmpty(typeId)) {
return null;
}
return (T) JetLinksDataTypeCodecs.decode(DataTypes.lookup(typeId).get(), map);
}
return null;
}
}, DataType.class);
//捕获jvm错误,防止Flux被挂起
Hooks.onOperatorError((err, val) -> {
if (Exceptions.isJvmFatal(err)) {
return new JvmErrorException(err);
}
return err;
});
Hooks.onNextError((err, val) -> {
if (Exceptions.isJvmFatal(err)) {
return new JvmErrorException(err);
}
return err;
});
}
@Bean
@@ -112,8 +200,62 @@ public class CommonConfiguration {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return builder->{
builder.deserializerByType(DataType.class, new DataTypeJSONDeserializer());
builder.deserializerByType(Date.class,new SmartDateDeserializer());
builder.deserializerByType(EnumDict.class, new DictionaryJsonDeserializer());
};
}
@Bean
public ConfigManager configManager(ObjectProvider<ConfigScopeCustomizer> configScopeCustomizers,
ReactiveRepository<ConfigEntity, String> repository,
ReactiveCacheManager cacheManager) {
SimpleConfigManager configManager = new SimpleConfigManager(repository,cacheManager);
for (ConfigScopeCustomizer customizer : configScopeCustomizers) {
customizer.custom(configManager);
}
return configManager;
}
@Bean
public PermissionResourceProvider permissionResourceProvider(){
return new PermissionResourceProvider();
}
@Bean
public TypeScriptDeclareResourceProvider typeScriptDeclareResourceProvider() {
return new TypeScriptDeclareResourceProvider();
}
@Bean
public ResourceManager resourceManager(ObjectProvider<ResourceProvider> providers) {
DefaultResourceManager manager = new DefaultResourceManager();
providers.forEach(manager::addProvider);
return manager;
}
@Bean
public DataReferenceManager dataReferenceManager(ObjectProvider<DataReferenceProvider> provider) {
DefaultDataReferenceManager referenceManager = new DefaultDataReferenceManager();
provider.forEach(referenceManager::addStrategy);
return referenceManager;
}
@Bean
public CommandServiceEndpointRegister commandServiceEndpointRegister() {
return new CommandServiceEndpointRegister();
}
@Configuration
@ConditionalOnClass(ReactiveRedisOperations.class)
static class DefaultUserBindServiceConfiguration {
@Bean
public DefaultUserBindService defaultUserBindService(ReactiveRedisOperations<Object, Object> redis) {
return new DefaultUserBindService(redis);
}
}
}

View File

@@ -0,0 +1,26 @@
package org.jetlinks.community.configuration;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.jetlinks.core.metadata.DataType;
import java.io.IOException;
import java.util.Map;
/**
*
* @author zhangji 2025/1/23
* @since 2.3
*/
public class DataTypeJSONDeserializer extends JsonDeserializer<DataType> {
@Override
public DataType deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Map<String,Object> map= ctxt.readValue(parser, Map.class);
return (DataType) BeanUtilsBean.getInstance().getConvertUtils().convert(map, DataType.class);
}
}

View File

@@ -0,0 +1,24 @@
package org.jetlinks.community.configuration;
import org.jetlinks.community.resource.ui.UiMenuResourceProvider;
import org.jetlinks.community.resource.ui.UiResourceProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnProperty(prefix = "jetlinks.ui", name = "enabled", havingValue = "true", matchIfMissing = true)
public class UiResourceConfiguration {
@Bean
public UiResourceProvider uiResourceProvider() {
return new UiResourceProvider();
}
@Bean
public UiMenuResourceProvider uiMenuResourceProvider() {
return new UiMenuResourceProvider();
}
}

View File

@@ -0,0 +1,88 @@
package org.jetlinks.community.dictionary;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.MapUtils;
import org.hswebframework.web.dict.EnumDict;
import org.hswebframework.web.dictionary.entity.DictionaryItemEntity;
import org.hswebframework.web.dictionary.service.DefaultDictionaryItemService;
import org.springframework.boot.CommandLineRunner;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author bestfeng
*/
@AllArgsConstructor
public class DatabaseDictionaryManager implements DictionaryManager, CommandLineRunner{
private final DefaultDictionaryItemService dictionaryItemService;
private final Map<String, Map<String, DictionaryItemEntity>> itemStore = new ConcurrentHashMap<>();
@Nonnull
@Override
public List<EnumDict<?>> getItems(@Nonnull String dictId) {
Map<String, DictionaryItemEntity> itemEntityMap = itemStore.get(dictId);
if (MapUtils.isEmpty(itemEntityMap)) {
return Collections.emptyList();
}
return new ArrayList<>(itemEntityMap.values());
}
@Nonnull
@Override
public Optional<EnumDict<?>> getItem(@Nonnull String dictId, @Nonnull String itemId) {
Map<String, DictionaryItemEntity> itemEntityMap = itemStore.get(dictId);
if (itemEntityMap == null) {
return Optional.empty();
}
return Optional.ofNullable(itemEntityMap.get(itemId));
}
public void registerItems(List<DictionaryItemEntity> items) {
items.forEach(this::registerItem);
}
public void removeItems(List<DictionaryItemEntity> items) {
items.forEach(this::removeItem);
}
public void removeItem(DictionaryItemEntity item) {
if (item == null || item.getDictId() == null || item.getId() == null) {
return;
}
itemStore.compute(item.getDictId(), (k, v) -> {
if (v != null) {
v.remove(item.getId());
if (!v.isEmpty()) {
return v;
}
}
return null;
});
}
public void registerItem(DictionaryItemEntity item) {
if (item == null || item.getDictId() == null) {
return;
}
itemStore
.computeIfAbsent(item.getDictId(), k -> new ConcurrentHashMap<>())
.put(item.getId(), item);
}
@Override
public void run(String... args) throws Exception {
dictionaryItemService
.createQuery()
.fetch()
.doOnNext(this::registerItem)
.subscribe();
}
}

View File

@@ -0,0 +1,94 @@
package org.jetlinks.community.dictionary;
import org.hswebframework.web.dict.EnumDict;
import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 动态数据字典工具类
*
* @author zhouhao
* @since 2.1
*/
public class Dictionaries {
static DictionaryManager HOLDER = null;
static void setup(DictionaryManager manager) {
HOLDER = manager;
}
/**
* 获取字典的所有选型
*
* @param dictId 字典ID
* @return 字典值
*/
@Nonnull
public static List<EnumDict<?>> getItems(@Nonnull String dictId) {
return HOLDER == null ? Collections.emptyList() : HOLDER.getItems(dictId);
}
/**
* 根据掩码获取枚举选项,通常用于多选时获取选项.
*
* @param dictId 枚举ID
* @param mask 掩码
* @return 选项
* @see Dictionaries#toMask(Collection)
*/
@Nonnull
public static List<EnumDict<?>> getItems(@Nonnull String dictId, long mask) {
if (HOLDER == null) {
return Collections.emptyList();
}
return HOLDER
.getItems(dictId)
.stream()
.filter(item -> item.in(mask))
.collect(Collectors.toList());
}
/**
* 查找枚举选项
*
* @param dictId 枚举ID
* @param value 选项值
* @return 选项
*/
public static Optional<EnumDict<?>> findItem(@Nonnull String dictId, Object value) {
return getItems(dictId)
.stream()
.filter(item -> item.eq(value))
.findFirst();
}
/**
* 获取字段选型
*
* @param dictId 字典ID
* @param itemId 选项ID
* @return 选项值
*/
@Nonnull
public static Optional<EnumDict<?>> getItem(@Nonnull String dictId,
@Nonnull String itemId) {
return HOLDER == null ? Optional.empty() : HOLDER.getItem(dictId, itemId);
}
public static long toMask(Collection<EnumDict<?>> items) {
long value = 0L;
for (EnumDict<?> t1 : items) {
value |= t1.getMask();
}
return value;
}
}

View File

@@ -0,0 +1,71 @@
package org.jetlinks.community.dictionary;
import org.hswebframework.ezorm.rdb.mapping.annotation.Codec;
import org.hswebframework.web.dict.EnumDict;
import java.lang.annotation.*;
import java.util.Collection;
/**
* 定义字段是一个数据字典,和枚举的使用方式类似.
* <p>
* 区别是数据的值通过{@link Dictionaries}进行获取.
*
* <pre>{@code
* public class MyEntity{
*
* //数据库存储的是枚举的值
* @Column(length=32)
* @Dictionary("my_status")
* @ColumnType(javaType=String.class)
* private EnumDict<String> status;
*
* @Column
* @Dictionary("my_types")
* //使用long来存储数据,表示使用字段的序号来进行mask运算进行存储.
* @ColumnType(javaType=Long.class,jdbcType=JDBCType.BIGINT)
* private EnumDict<String>[] types;
* }
* }</pre>
* <b>⚠️注意</b>
* <ul>
* <li>
* 字段类型只支持{@code EnumDict<String>},{@code EnumDict<String>[]},{@code List<EnumDict<String>>}
* </li>
* <li>
* 多选时建议使用位掩码来存储: {@code @ColumnType(javaType=Long.class,jdbcType=JDBCType.BIGINT) },便于查询.
* </li>
* <li>使用位掩码存储字典值时,基于{@link EnumDict#ordinal()}进行计算,因此字段选项数量不能超过64个,修改字典时,请注意序号值变化。</li>
* <li>模块需要引入依赖:<pre>{@code
* <dependency>
* <groupId>org.hswebframework.web</groupId>
* <artifactId>hsweb-system-dictionary</artifactId>
* </dependency>
* }</pre></li>
* </ul>
*
*
* @author zhouhao
* @see EnumDict#getValue()
* @see EnumDict#getMask()
* @see Dictionaries
* @see Dictionaries#toMask(Collection)
* @see DictionaryManager
* @since 2.2
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Codec
public @interface Dictionary {
/**
* 数据字典ID
*
* @return 数据字典ID
* @see Dictionaries#getItem(String, String)
*/
String value();
}

View File

@@ -0,0 +1,57 @@
package org.jetlinks.community.dictionary;
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
import org.hswebframework.ezorm.rdb.metadata.RDBTableMetadata;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.term.EnumFragmentBuilder;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.term.EnumInFragmentBuilder;
import org.hswebframework.web.crud.configuration.TableMetadataCustomizer;
import org.jetlinks.community.form.type.EnumFieldType;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.sql.JDBCType;
import java.util.List;
import java.util.Set;
public class DictionaryColumnCustomizer implements TableMetadataCustomizer {
@Override
public void customColumn(Class<?> entityType,
PropertyDescriptor descriptor,
Field field,
Set<Annotation> annotations,
RDBColumnMetadata column) {
Dictionary dictionary = annotations
.stream()
.filter(Dictionary.class::isInstance)
.findFirst()
.map(Dictionary.class::cast)
.orElse(null);
if (dictionary != null) {
Class<?> type = field.getType();
JDBCType jdbcType = (JDBCType) column.getType().getSqlType();
EnumFieldType codec = new EnumFieldType(
type.isArray() || List.class.isAssignableFrom(type),
dictionary.value(),
jdbcType)
.withArray(type.isArray())
.withFieldValueConverter(e -> e);
column.setValueCodec(codec);
if (codec.isToMask()) {
column.addFeature(EnumFragmentBuilder.eq);
column.addFeature(EnumFragmentBuilder.not);
column.addFeature(EnumInFragmentBuilder.of(column.getDialect()));
column.addFeature(EnumInFragmentBuilder.ofNot(column.getDialect()));
}
}
}
@Override
public void customTable(Class<?> entityType, RDBTableMetadata table) {
}
}

View File

@@ -0,0 +1,48 @@
package org.jetlinks.community.dictionary;
import org.hswebframework.web.dictionary.service.DefaultDictionaryItemService;
import org.hswebframework.web.dictionary.service.DefaultDictionaryService;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DictionaryConfiguration {
@Configuration
@ConditionalOnClass(DefaultDictionaryItemService.class)
//@ConditionalOnBean(DefaultDictionaryItemService.class)
public static class DictionaryManagerConfiguration {
@Bean
public DictionaryEventHandler dictionaryEventHandler(DefaultDictionaryItemService service) {
return new DictionaryEventHandler(service);
}
@Bean
public DatabaseDictionaryManager defaultDictionaryManager(DefaultDictionaryItemService service) {
DatabaseDictionaryManager dictionaryManager = new DatabaseDictionaryManager(service);
Dictionaries.setup(dictionaryManager);
return dictionaryManager;
}
@Bean
public DictionaryColumnCustomizer dictionaryColumnCustomizer() {
return new DictionaryColumnCustomizer();
}
@Bean
@ConfigurationProperties(prefix = "jetlinks.dict")
public DictionaryInitManager dictionaryInitManager(ObjectProvider<DictionaryInitInfo> initInfo,
DefaultDictionaryService defaultDictionaryService,
DefaultDictionaryItemService itemService) {
return new DictionaryInitManager(initInfo, defaultDictionaryService, itemService);
}
}
}

View File

@@ -0,0 +1,9 @@
package org.jetlinks.community.dictionary;
public interface DictionaryConstants {
/**
* 系统分类标识
*/
String CLASSIFIED_SYSTEM = "system";
}

View File

@@ -0,0 +1,95 @@
package org.jetlinks.community.dictionary;
import lombok.AllArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.hswebframework.web.crud.events.*;
import org.hswebframework.web.dictionary.entity.DictionaryEntity;
import org.hswebframework.web.dictionary.entity.DictionaryItemEntity;
import org.hswebframework.web.dictionary.service.DefaultDictionaryItemService;
import org.hswebframework.web.exception.BusinessException;
import org.springframework.context.event.EventListener;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author bestfeng
*/
@AllArgsConstructor
public class DictionaryEventHandler implements EntityEventListenerCustomizer {
private final DefaultDictionaryItemService itemService;
@EventListener
public void handleDictionaryCreated(EntityCreatedEvent<DictionaryEntity> event) {
event.async(
Flux.fromIterable(event.getEntity())
.flatMap(dictionary -> {
if (!CollectionUtils.isEmpty(dictionary.getItems())) {
return Flux
.fromIterable(dictionary.getItems())
.doOnNext(item -> item.setDictId(dictionary.getId()))
.as(itemService::save);
}
return Mono.empty();
})
);
}
@EventListener
public void handleDictionarySaved(EntitySavedEvent<DictionaryEntity> event) {
event.async(
Flux.fromIterable(event.getEntity())
.flatMap(dictionary -> {
if (!CollectionUtils.isEmpty(dictionary.getItems())) {
return Flux
.fromIterable(dictionary.getItems())
.doOnNext(item -> item.setDictId(dictionary.getId()))
.as(itemService::save);
}
return Mono.empty();
})
);
}
@EventListener
public void handleDictionaryDeleted(EntityDeletedEvent<DictionaryEntity> event) {
event.async(
Flux.fromIterable(event.getEntity())
.map(DictionaryEntity::getId)
.collectList()
.flatMap(dictionary -> itemService
.createDelete()
.where()
.in(DictionaryItemEntity::getDictId, dictionary)
.execute()
.then())
);
}
@Override
public void customize(EntityEventListenerConfigure configure) {
configure.enable(DictionaryItemEntity.class);
configure.enable(DictionaryEntity.class);
}
/**
* 监听字典删除前事件,阻止删除分类标识为系统的字典
* @param event 字典删除前事件
*/
@EventListener
public void handleDictionaryBeforeDelete(EntityBeforeDeleteEvent<DictionaryEntity> event) {
event.async(
Flux.fromIterable(event.getEntity())
.any(dictionary ->
StringUtils.equals(dictionary.getClassified(), DictionaryConstants.CLASSIFIED_SYSTEM))
.flatMap(any -> {
if (any) {
return Mono.error(() -> new BusinessException("error.system_dictionary_can_not_delete"));
}
return Mono.empty();
})
);
}
}

View File

@@ -0,0 +1,23 @@
package org.jetlinks.community.dictionary;
import org.apache.commons.collections4.CollectionUtils;
import org.hswebframework.web.dictionary.entity.DictionaryEntity;
import reactor.core.publisher.Flux;
import java.util.Collection;
/**
* @author gyl
* @since 2.2
*/
public interface DictionaryInitInfo {
Collection<DictionaryEntity> getDict();
default Flux<DictionaryEntity> getDictAsync() {
if (CollectionUtils.isEmpty(getDict())) {
return Flux.empty();
}
return Flux.fromIterable(getDict());
}
}

View File

@@ -0,0 +1,81 @@
package org.jetlinks.community.dictionary;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.hswebframework.web.crud.events.EntityEventHelper;
import org.hswebframework.web.dictionary.entity.DictionaryEntity;
import org.hswebframework.web.dictionary.entity.DictionaryItemEntity;
import org.hswebframework.web.dictionary.service.DefaultDictionaryItemService;
import org.hswebframework.web.dictionary.service.DefaultDictionaryService;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.CommandLineRunner;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* @author gyl
* @since 2.2
*/
@Slf4j
public class DictionaryInitManager implements CommandLineRunner {
@Getter
@Setter
private List<DictionaryEntity> inits = new ArrayList<>();
public final ObjectProvider<DictionaryInitInfo> initInfo;
private final DefaultDictionaryService defaultDictionaryService;
private final DefaultDictionaryItemService itemService;
public DictionaryInitManager(ObjectProvider<DictionaryInitInfo> initInfo, DefaultDictionaryService defaultDictionaryService, DefaultDictionaryItemService itemService) {
this.initInfo = initInfo;
this.defaultDictionaryService = defaultDictionaryService;
this.itemService = itemService;
}
@Override
public void run(String... args) {
Flux
.merge(
Flux.fromIterable(inits),
Flux
.fromIterable(initInfo)
.flatMap(DictionaryInitInfo::getDictAsync)
)
.buffer(200)
.filter(CollectionUtils::isNotEmpty)
.flatMap(collection -> {
List<DictionaryItemEntity> items = generateItems(collection);
return defaultDictionaryService
.save(collection)
.mergeWith(itemService.save(items));
})
.as(EntityEventHelper::setDoNotFireEvent)
.subscribe(ignore -> {
},
err -> log.error("init dict error", err));
}
public List<DictionaryItemEntity> generateItems(List<DictionaryEntity> dictionaryList) {
List<DictionaryItemEntity> items = new ArrayList<>();
for (DictionaryEntity dictionary : dictionaryList) {
if (!CollectionUtils.isEmpty(dictionary.getItems())) {
for (DictionaryItemEntity item : dictionary.getItems()) {
item.setDictId(dictionary.getId());
items.add(item);
}
}
}
return items;
}
}

View File

@@ -0,0 +1,40 @@
package org.jetlinks.community.dictionary;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.dict.EnumDict;
import org.hswebframework.web.dict.defaults.DefaultItemDefine;
import java.io.IOException;
import java.util.Map;
public class DictionaryJsonDeserializer extends JsonDeserializer<EnumDict<?>> {
@Override
public EnumDict<?> deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JacksonException {
if (jsonParser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
DefaultItemDefine defaultItemDefine = new DefaultItemDefine();
defaultItemDefine.setOrdinal(jsonParser.getIntValue());
return defaultItemDefine;
}
if (jsonParser.hasToken(JsonToken.VALUE_STRING)) {
String str = jsonParser.getText().trim();
if (!str.isEmpty()) {
DefaultItemDefine defaultItemDefine = new DefaultItemDefine();
defaultItemDefine.setValue(str);
return defaultItemDefine;
}
}
if (jsonParser.hasToken(JsonToken.START_OBJECT)) {
Map<?, ?> map = ctxt.readValue(jsonParser, Map.class);
if (map != null) {
return FastBeanCopier.copy(map, new DefaultItemDefine());
}
}
return null;
}
}

View File

@@ -0,0 +1,41 @@
package org.jetlinks.community.dictionary;
import org.hswebframework.web.dict.EnumDict;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Optional;
/**
* 数据字典管理器,用于获取数据字典的枚举值
*
* @author zhouhao
* @since 2.1
*/
public interface DictionaryManager {
/**
* 获取字典的所有选项
*
* @param dictId 字典ID
* @return 字典值
*/
@Nonnull
List<EnumDict<?>> getItems(@Nonnull String dictId);
/**
* 获取字段选项
*
* @param dictId 字典ID
* @param itemId 选项ID
* @return 选项值
*/
@Nonnull
Optional<EnumDict<?>> getItem(@Nonnull String dictId,
@Nonnull String itemId);
}

View File

@@ -0,0 +1,27 @@
package org.jetlinks.community.doc;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.ezorm.core.param.Term;
import java.util.List;
/**
* 文档专用,描述仅有查询功能的动态查询参数
*
* @author zhouhao
* @since 1.5
* @see org.hswebframework.web.api.crud.entity.QueryParamEntity
*/
@Getter
@Setter
public class QueryConditionOnly {
@Schema(description = "where条件表达式,与terms参数不能共存.语法: name = 张三 and age > 16")
private String where;
@Schema(description = "查询条件集合")
private List<Term> terms;
}

View File

@@ -0,0 +1,15 @@
package org.jetlinks.community.event;
import org.jetlinks.community.Operation;
import org.jetlinks.community.OperationType;
import reactor.core.publisher.Flux;
public interface OperationAssetProvider {
OperationType[] getSupportTypes();
Flux<String> createTopics(Operation operation, String original);
Flux<String> getAssetTypes(String operationType);
}

View File

@@ -0,0 +1,35 @@
package org.jetlinks.community.event;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.community.Operation;
import org.jetlinks.community.OperationType;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class OperationAssetProviders {
private static final Map<String, OperationAssetProvider> providers = new ConcurrentHashMap<>();
public static void register(OperationAssetProvider provider) {
for (OperationType supportType : provider.getSupportTypes()) {
OperationAssetProvider old = providers.put(supportType.getId(), provider);
if (old != null && old != provider) {
log.warn("operation asset provider [{}] already exists,will be replaced by [{}]", old, provider);
}
}
}
public static Optional<OperationAssetProvider> lookup(Operation operation) {
return lookup(operation.getType().getId());
}
public static Optional<OperationAssetProvider> lookup(String operationType) {
return Optional.ofNullable(providers.get(operationType));
}
}

View File

@@ -0,0 +1,77 @@
package org.jetlinks.community.event;
import lombok.Getter;
import lombok.Setter;
import org.jetlinks.core.utils.SerializeUtils;
import org.jetlinks.community.Operation;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
@Getter
@Setter
public class SystemEvent implements Externalizable {
private static final long serialVersionUID = 1L;
private Level level;
private String code;
private Operation operation;
/**
* 描述详情,不同的类型详情内容不同
*
* @see org.jetlinks.community.monitor.ExecutionMonitorInfo
*/
private Object detail;
private long timestamp;
public SystemEvent(Level level, String code, Operation operation, Object detail) {
this.level = level;
this.code = code;
this.operation = operation;
this.detail = detail;
this.timestamp = System.currentTimeMillis();
}
public SystemEvent() {
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeByte(level.ordinal());
out.writeUTF(code);
operation.writeExternal(out);
SerializeUtils.writeObject(detail, out);
out.writeLong(timestamp);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
level = Level.values()[in.readByte()];
code = in.readUTF();
operation = new Operation();
operation.readExternal(in);
detail = SerializeUtils.readObject(in);
timestamp = in.readLong();
}
public enum Level {
info,
warn,
error
}
}

View File

@@ -0,0 +1,48 @@
package org.jetlinks.community.event;
import lombok.AllArgsConstructor;
import org.jetlinks.core.event.EventBus;
import org.springframework.context.ApplicationEventPublisher;
/**
* 推送系统事件到事件总线topic: /sys-event/{operationType}/{operationId}/{level}
*
* @author zhouhao
* @since 2.0
*/
@AllArgsConstructor
public class SystemEventDispatcher implements SystemEventHandler {
private final EventBus eventBus;
private final ApplicationEventPublisher eventPublisher;
@Override
public final void handle(SystemEvent event) {
String topic = SystemEventHandler
.topic(event.getOperation().getType().getId(),
event.getOperation().getSource().getId(),
event.getLevel().name());
eventPublisher.publishEvent(event);
OperationAssetProvider provider = OperationAssetProviders
.lookup(event.getOperation())
.orElse(null);
//对数据权限控制的支持
if (provider != null) {
provider
.createTopics(event.getOperation(), topic)
.flatMap(_topic -> eventBus.publish(_topic, event))
.subscribe();
} else {
eventBus.publish(topic, event)
.subscribe();
}
}
}

View File

@@ -0,0 +1,22 @@
package org.jetlinks.community.event;
import org.jetlinks.core.utils.StringBuilderUtils;
public interface SystemEventHandler {
static String topic(String operationType, String operationId, String level) {
return StringBuilderUtils
.buildString(operationType, operationId, level, (a, b, c, builder) -> {
// /sys-event/{operationType}/{operationId}/{level}
builder.append("/sys-event/")
.append(a)
.append('/')
.append(b)
.append('/')
.append(c);
});
}
void handle(SystemEvent event);
}

View File

@@ -0,0 +1,13 @@
package org.jetlinks.community.event;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;
@Component
public class SystemEventHandlerRegister {
public SystemEventHandlerRegister(ObjectProvider<SystemEventHandler> handlers){
handlers.forEach(SystemEventHolder::register);
}
}

View File

@@ -0,0 +1,54 @@
package org.jetlinks.community.event;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.community.Operation;
import reactor.core.Disposable;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@Slf4j
public class SystemEventHolder {
private static final List<SystemEventHandler> eventHandlers = new CopyOnWriteArrayList<>();
public static Disposable register(SystemEventHandler handler) {
eventHandlers.add(handler);
return () -> eventHandlers.remove(handler);
}
public static void error(Operation operation, String code, Object detail) {
log.error("{} {} :{}", operation, code, detail);
if (eventHandlers.isEmpty()) {
return;
}
fireEvent(new SystemEvent(SystemEvent.Level.error, code, operation, detail));
}
public static void warn(Operation operation, String code, Object detail) {
log.warn("{} {} :{}", operation, code, detail);
if (eventHandlers.isEmpty()) {
return;
}
fireEvent(new SystemEvent(SystemEvent.Level.warn, code, operation, detail));
}
public static void info(Operation operation, String code, Object detail) {
log.info("{} {} :{}", operation, code, detail);
if (eventHandlers.isEmpty()) {
return;
}
fireEvent(new SystemEvent(SystemEvent.Level.info, code, operation, detail));
}
private static void fireEvent(SystemEvent event) {
for (SystemEventHandler eventHandler : eventHandlers) {
try {
eventHandler.handle(event);
} catch (Throwable e) {
log.warn("handle system log error", e);
}
}
}
}

View File

@@ -0,0 +1,204 @@
package org.jetlinks.community.form.type;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.hswebframework.ezorm.core.ValueCodec;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.dict.EnumDict;
import org.jetlinks.core.metadata.DataType;
import org.jetlinks.core.metadata.types.EnumType;
import org.jetlinks.community.dictionary.Dictionaries;
import org.jetlinks.community.utils.ConverterUtils;
import org.jetlinks.reactor.ql.utils.CastUtils;
import java.sql.JDBCType;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @since 2.1
*/
@Getter
@RequiredArgsConstructor
public class EnumFieldType implements FieldType, ValueCodec<Object, Object> {
public static final String TYPE = "Enum";
private final boolean multiple;
private final String dictId;
private final JDBCType jdbcType;
private boolean array = false;
private Function<EnumDict<?>,Object> fieldValueConverter = EnumDict::getWriteJSONObject;;
public EnumFieldType withArray(boolean array) {
this.array = array;
return this;
}
public EnumFieldType withFieldValueConverter(Function<EnumDict<?>,Object> converter) {
this.fieldValueConverter = converter;
return this;
}
@Override
public final String getId() {
return TYPE;
}
public boolean isToMask() {
return multiple && jdbcType == JDBCType.BIGINT;
}
@Override
public Class<?> getJavaType() {
return isToMask() ? Long.class : String.class;
}
@Override
public JDBCType getJdbcType() {
return jdbcType;
}
@Override
public int getLength() {
return 64;
}
@Override
public DataType getDataType() {
EnumType enumType = new EnumType();
for (EnumDict<?> item : Dictionaries.getItems(dictId)) {
enumType.addElement(EnumType.Element.of(String.valueOf(item.getValue()), item.getText()));
}
enumType.setMulti(multiple);
return enumType;
}
@Override
public ValueCodec<?, ?> getCodec() {
return this;
}
@Override
public Object encode(Object value) {
//转为位掩码
if (isToMask()) {
return Dictionaries.toMask(
ConverterUtils
.convertToList(value, val -> Dictionaries.findItem(dictId, val).orElse(null))
.stream()
.filter(Objects::nonNull)
.collect(Collectors.toSet()));
}
//多选,使用逗号分隔
if (multiple) {
return ConverterUtils
.convertToList(value, val -> Dictionaries.findItem(dictId, val).orElse(null))
.stream()
.filter(Objects::nonNull)
.map(e -> String.valueOf(e.getValue()))
.collect(Collectors.joining(","));
}
return Dictionaries
.findItem(dictId, value)
.<Object>map(EnumDict::getValue)
.orElseGet(() -> {
if (value instanceof EnumDict) {
return ((EnumDict<?>) value).getValue();
}
return value;
});
}
@Override
public Object decode(Object data) {
if (multiple) {
List<Object> list;
if (isToMask()) {
list =
Dictionaries
.getItems(dictId, CastUtils.castNumber(data).longValue())
.stream()
.map(fieldValueConverter)
.collect(Collectors.toList());
} else {
list = ConverterUtils
.convertToList(data, val -> Dictionaries.findItem(dictId, val).orElse(null))
.stream()
.filter(Objects::nonNull)
.map(fieldValueConverter)
.collect(Collectors.toList());
}
if (isArray()) {
return list.toArray(new EnumDict[0]);
}
return list;
}
if(isToMask()){
return Dictionaries
.getItems(dictId, CastUtils.castNumber(data).longValue())
.stream()
.map(fieldValueConverter)
.findFirst()
.orElse(null);
}
return Dictionaries
.findItem(dictId, data)
.map(fieldValueConverter)
.orElse(fieldValueConverter.apply(EnumDict.create(String.valueOf(data))));
}
public static class Provider implements FieldTypeProvider {
@Override
public String getProvider() {
return EnumFieldType.TYPE;
}
@Override
public String getProviderName() {
return "枚举";
}
@Override
public Set<JDBCType> getSupportJdbcTypes() {
return new HashSet<>(Arrays.asList(JDBCType.VARCHAR, JDBCType.BIGINT));
}
@Override
public int getDefaultLength() {
return 64;
}
@Override
public FieldType create(FieldTypeSpec configuration) {
DictionarySpec spec = Optional
.ofNullable(configuration.getConfiguration())
.map(configSpec -> FastBeanCopier.copy(configSpec, new DictionarySpec()))
.orElse(new DictionarySpec());
return new EnumFieldType(
spec.isMultiple(),
Optional
.ofNullable(spec.getDictionaryId())
.orElseThrow(() -> new IllegalArgumentException("dictionaryId can not be null")),
configuration.getJdbcType() == null ? JDBCType.VARCHAR : configuration.getJdbcType()
);
}
}
//数据字典
@Getter
@Setter
public static class DictionarySpec {
//字典ID
private String dictionaryId;
//多选
private boolean multiple;
}
}

View File

@@ -0,0 +1,28 @@
package org.jetlinks.community.form.type;
import org.hswebframework.ezorm.core.ValueCodec;
import org.jetlinks.core.metadata.DataType;
import java.sql.JDBCType;
public interface FieldType {
String getId();
Class<?> getJavaType();
JDBCType getJdbcType();
ValueCodec<?, ?> getCodec();
default int getLength() {
return 255;
}
default int getScale() {
return 2;
}
DataType getDataType();
}

View File

@@ -0,0 +1,31 @@
package org.jetlinks.community.form.type;
import org.jetlinks.community.spi.Provider;
import java.sql.JDBCType;
import java.util.Set;
public interface FieldTypeProvider {
Provider<FieldTypeProvider> supports = Provider.create(FieldTypeProvider.class);
String getProvider();
default String getProviderName() {
return getProvider();
}
default int getDefaultLength() {
return 255;
}
Set<JDBCType> getSupportJdbcTypes();
FieldType create(FieldTypeSpec configuration);
static FieldType createType(FieldTypeSpec spec) {
return supports
.getNow(spec.getName())
.create(spec);
}
}

View File

@@ -0,0 +1,26 @@
package org.jetlinks.community.form.type;
import lombok.Getter;
import lombok.Setter;
import org.jetlinks.community.ValueObject;
import java.sql.JDBCType;
import java.util.Map;
@Getter
@Setter
public class FieldTypeSpec implements ValueObject {
private String name;
private int length;
private int scale;
private JDBCType jdbcType;
private Map<String,Object> configuration;
@Override
public Map<String, Object> values() {
return configuration;
}
}

View File

@@ -0,0 +1,296 @@
package org.jetlinks.community.lock;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.Disposable;
import reactor.core.publisher.*;
import reactor.core.scheduler.Schedulers;
import reactor.util.context.Context;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.Duration;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Consumer;
class DefaultReactiveLock implements ReactiveLock {
@SuppressWarnings("all")
static final AtomicReferenceFieldUpdater<DefaultReactiveLock, LockingSubscriber>
PENDING = AtomicReferenceFieldUpdater
.newUpdater(DefaultReactiveLock.class, LockingSubscriber.class, "pending");
final Deque<LockingSubscriber<?>> queue = new ConcurrentLinkedDeque<>();
volatile LockingSubscriber<?> pending;
@Override
public <T> Flux<T> lock(Flux<T> job) {
return new LockingFlux<>(this, job);
}
@Override
public <T> Flux<T> lock(Flux<T> flux, Duration timeout) {
return new LockingFlux<>(this, flux, timeout);
}
@Override
public <T> Flux<T> lock(Flux<T> flux, Duration timeout, Flux<? extends T> fallback) {
return new LockingFlux<>(this, flux, timeout, fallback);
}
@Override
public <T> Mono<T> lock(Mono<T> job) {
return new LockingMono<>(this, job);
}
@Override
public <T> Mono<T> lock(Mono<T> mono, Duration timeout) {
return new LockingMono<>(this, mono, timeout);
}
@Override
public <T> Mono<T> lock(Mono<T> mono, Duration timeout, Mono<? extends T> fallback) {
return new LockingMono<>(this, mono, timeout, fallback);
}
protected void drain() {
if (PENDING.get(this) != null) {
return;
}
LockingSubscriber<?> locking;
for (; ; ) {
locking = queue.pollFirst();
if (locking == null) {
return;
}
if (locking.isDisposed()) {
continue;
}
if (PENDING.compareAndSet(this, null, locking)) {
//使用单独的线程池来调度,防止参与锁太多导致栈溢出.
Schedulers.parallel().schedule(locking::subscribe);
} else {
queue.addLast(locking);
}
break;
}
}
<T> void registerSubscriber(CoreSubscriber<? super T> actual,
Consumer<CoreSubscriber<? super T>> subscribeCallback,
@Nullable Duration timeout,
@Nullable Publisher<? extends T> timeoutFallback) {
registerSubscriber(new LockingSubscriber<>(
this,
actual,
subscribeCallback,
timeout,
timeoutFallback));
}
void registerSubscriber(LockingSubscriber<?> subscriber) {
if (PENDING.compareAndSet(this, null, subscriber)) {
subscriber.subscribe();
return;
}
queue.addLast(subscriber);
drain();
}
static class LockingFlux<T> extends FluxOperator<T, T> {
private final DefaultReactiveLock main;
private Duration timeout;
private Publisher<? extends T> timeoutFallback;
protected LockingFlux(DefaultReactiveLock main, Flux<? extends T> source) {
super(source);
this.main = main;
}
protected LockingFlux(DefaultReactiveLock main, Flux<? extends T> source, Duration timeout) {
super(source);
this.main = main;
this.timeout = timeout;
}
protected LockingFlux(DefaultReactiveLock main, Flux<? extends T> source, Duration timeout, Flux<? extends T> timeoutFallback) {
super(source);
this.main = main;
this.timeout = timeout;
this.timeoutFallback = timeoutFallback;
}
@Override
public void subscribe(@Nonnull CoreSubscriber<? super T> actual) {
Consumer<CoreSubscriber<? super T>> subscribeCallback = source::subscribe;
main.registerSubscriber(actual, subscribeCallback, timeout, timeoutFallback);
}
}
static class LockingMono<T> extends MonoOperator<T, T> {
private final DefaultReactiveLock main;
private Duration timeout;
private Publisher<? extends T> fallback;
protected LockingMono(DefaultReactiveLock main, Mono<? extends T> source) {
super(source);
this.main = main;
}
protected LockingMono(DefaultReactiveLock main, Mono<? extends T> source, Duration timeout) {
super(source);
this.main = main;
this.timeout = timeout;
}
protected LockingMono(DefaultReactiveLock main, Mono<? extends T> source, Duration timeout, Mono<? extends T> fallback) {
super(source);
this.main = main;
this.timeout = timeout;
this.fallback = fallback;
}
@Override
public void subscribe(@Nonnull CoreSubscriber<? super T> actual) {
Consumer<CoreSubscriber<? super T>> subscribeCallback = source::subscribe;
main.registerSubscriber(actual, subscribeCallback, timeout, fallback);
}
}
static class LockingSubscriber<T> extends BaseSubscriber<T> {
protected final DefaultReactiveLock main;
protected final CoreSubscriber<? super T> actual;
private final Consumer<CoreSubscriber<? super T>> subscriber;
private Disposable timeoutTask;
private final Publisher<? extends T> timeoutFallback;
@SuppressWarnings("all")
protected static final AtomicIntegerFieldUpdater<LockingSubscriber> statusUpdater =
AtomicIntegerFieldUpdater.newUpdater(LockingSubscriber.class, "status");
private volatile int status;
//初始
private static final int INIT = 0;
//订阅备用流
private static final int SUB_TIMEOUT_FALLBACK = -1;
//订阅原本上游流
private static final int SUB_SOURCE = 1;
//流结束
private static final int UN_SUB = -2;
public LockingSubscriber(DefaultReactiveLock main,
CoreSubscriber<? super T> actual,
Consumer<CoreSubscriber<? super T>> subscriber,
@Nullable Duration timeout,
@Nullable Publisher<? extends T> timeoutFallback) {
this.actual = actual;
this.main = main;
this.subscriber = subscriber;
this.timeoutFallback = timeoutFallback;
if (timeout != null) {
this.timeoutTask = Schedulers
.parallel()
.schedule(this::onTimeout, timeout.toMillis(), TimeUnit.MILLISECONDS);
}
}
private void onTimeout() {
if (statusUpdater.compareAndSet(this, INIT, SUB_TIMEOUT_FALLBACK)) {
//不代理订阅,直接取消流及释放当前锁,以免并发时等待备用流释放锁
doComplete();
if (timeoutFallback != null) {
timeoutFallback.subscribe(actual);
} else {
this.onError(new TimeoutException("Lock timed out"));
}
}
}
protected void subscribe() {
if (statusUpdater.compareAndSet(this, INIT, SUB_SOURCE)) {
if (timeoutTask != null && !timeoutTask.isDisposed()) {
timeoutTask.dispose();
}
subscriber.accept(this);
}
}
protected void complete() {
if (statusUpdater.compareAndSet(this, INIT, UN_SUB) || statusUpdater.compareAndSet(this, SUB_SOURCE, UN_SUB)) {
if (timeoutTask != null && !timeoutTask.isDisposed()) {
timeoutTask.dispose();
}
doComplete();
}
}
protected void doComplete() {
//防止非hookFinally触发的结束
if (!this.isDisposed()) {
this.cancel();
}
if (PENDING.compareAndSet(main, this, null)) {
main.drain();
}
}
@Override
protected final void hookOnError(@Nonnull Throwable throwable) {
actual.onError(throwable);
}
@Override
protected final void hookOnNext(@Nonnull T value) {
actual.onNext(value);
}
@Override
protected final void hookOnSubscribe(@Nonnull Subscription subscription) {
actual.onSubscribe(this);
}
@Override
protected final void hookOnComplete() {
actual.onComplete();
}
@Override
protected final void hookOnCancel() {
super.hookOnCancel();
}
@Override
protected final void hookFinally(@Nonnull SignalType type) {
complete();
}
@Override
@Nonnull
public Context currentContext() {
return actual.currentContext();
}
}
}

View File

@@ -0,0 +1,20 @@
package org.jetlinks.community.lock;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.time.Duration;
import java.util.Map;
class DefaultReactiveLockManager implements ReactiveLockManager {
private final Map<String, ReactiveLock> cache = Caffeine
.newBuilder()
.expireAfterAccess(Duration.ofMinutes(30))
.<String, ReactiveLock>build()
.asMap();
@Override
public ReactiveLock getLock(String name) {
return cache.computeIfAbsent(name, ignore -> new DefaultReactiveLock());
}
}

View File

@@ -0,0 +1,76 @@
package org.jetlinks.community.lock;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
* 响应式锁
*
* @author zhouhao
* @since 2.2
*/
public interface ReactiveLock {
/**
* 对Mono进行加锁,将等待之前的lock完成后再执行.
*
* @param mono 要加锁的Mono
* @param <T> T
* @return 加锁后的Mono
*/
<T> Mono<T> lock(Mono<T> mono);
/**
* 对Mono进行加锁,将等待之前的lock完成后再执行,若等待锁时间超过{@link Duration},则报错{@link java.util.concurrent.TimeoutException}
*
* @param mono 要加锁的Mono
* @param timeout 等待锁的时间
* @param <T> T
* @return 加锁后的Mono
*/
<T> Mono<T> lock(Mono<T> mono, Duration timeout);
/**
* 对Mono进行加锁,将等待之前的lock完成后再执行,若等待锁时间超过{@link Duration},则切换到回退流
*
* @param mono 要加锁的Mono
* @param timeout 等待锁的时间
* @param fallback 发生超时时要订阅的回退流
* @param <T> T
* @return 加锁后的Mono
*/
<T> Mono<T> lock(Mono<T> mono, Duration timeout, Mono<? extends T> fallback);
/**
* 对Flux进行加锁,将等待之前的lock完成后再执行.
*
* @param flux 要加锁的Flux
* @param <T> T
* @return 加锁后的Flux
*/
<T> Flux<T> lock(Flux<T> flux);
/**
* 对Flux进行加锁,将等待之前的lock完成后再执行,若等待锁时间超过{@link Duration},则报错{@link java.util.concurrent.TimeoutException}
*
* @param flux 要加锁的Flux
* @param timeout 等待锁的时间
* @param <T> T
* @return 加锁后的Mono
*/
<T> Flux<T> lock(Flux<T> flux, Duration timeout);
/**
* 对Flux进行加锁,将等待之前的lock完成后再执行,若等待锁时间超过{@link Duration},则切换到回退流
*
* @param flux 要加锁的Flux
* @param timeout 等待锁的时间
* @param fallback 发生超时时要订阅的回退流
* @param <T> T
* @return 加锁后的Mono
*/
<T> Flux<T> lock(Flux<T> flux, Duration timeout, Flux<? extends T> fallback);
}

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