Compare commits

...

729 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
zhou-hao 84a3418521 1.10.0 2021-08-02 11:09:45 +08:00
zhou-hao bd03dbd72a 1.10.0 2021-08-02 11:08:06 +08:00
zhou-hao 9280f7b1c4 add spring-webflux 2021-07-30 19:28:09 +08:00
zhou-hao 331f31a62b 优化设备指令发送规则执行器 2021-07-28 14:45:18 +08:00
zhou-hao 311166b983 优化异常处理 2021-07-27 15:35:54 +08:00
zhou-hao 8b9d4b00f3 redis设置密码 2021-07-27 15:35:44 +08:00
zhou-hao 76f29e6040 调整日志级别 2021-07-27 15:35:26 +08:00
zhou-hao 226ede0ac9 upgrade reactorQL 1.0.12 2021-07-27 10:02:56 +08:00
zhou-hao 68af4adc4b fixed #79 2021-07-27 10:00:40 +08:00
zhou-hao aefcbec026 生成告警记录时生成ID,方便下游做处理。 2021-07-26 10:27:02 +08:00
zhou-hao 0b897833f9 忽略产品物模型中没有的事件 2021-07-19 13:40:25 +08:00
zhou-hao a577a3270d 增加newBuffer()方法 2021-07-15 18:26:58 +08:00
zhou-hao 3ce7343f07 设置默认语言环境为zh 2021-07-13 17:57:39 +08:00
zhou-hao 4ccb2d2ec7 修复qos错误 2021-07-13 16:24:49 +08:00
zhou-hao fe5b364a94 mqtt client网关支持qos 2021-07-08 16:16:26 +08:00
zhou-hao c9d39663eb change maven download url 2021-07-02 13:46:20 +08:00
zhou-hao 750418a92a i18n part 0 2021-07-02 13:36:56 +08:00
zhou-hao fdf6d232f9 优化 2021-06-21 18:42:32 +08:00
zhou-hao 3193bd2ad4 增加DeviceDataManager实现 2021-06-21 18:42:24 +08:00
zhou-hao 3dcc94ed04 add max-idle-time 2021-06-21 18:22:38 +08:00
zhou-hao 47152361e8 hswebframework 4.0.11-SNAPSHOT 2021-06-11 10:29:51 +08:00
zhou-hao 891a900ab2 屏蔽es警告信息 2021-06-09 14:16:11 +08:00
zhou-hao 308994a634 openjdk:8u272-jdk 2021-06-08 18:25:41 +08:00
zhou-hao 5f24c32bd5 easyorm 4.0.11-SNAPSHOT 2021-06-08 16:48:43 +08:00
zhou-hao 08b879eb5e Merge remote-tracking branch 'origin/master' 2021-06-08 10:07:58 +08:00
zhou-hao 40d6a3ba41 设备导入导出支持机构:需要先将用户绑定到机构中. 2021-06-08 10:07:47 +08:00
老周 f04e02590d
Merge pull request #62 from qq517634644/master
docs(网络组件&设备网关):
2021-06-07 09:25:52 +08:00
zhou-hao 63f6812c27 docker 使用adoptopenjdk:8-jdk 2021-06-04 19:03:06 +08:00
Tensai bd0b936a56 docs(网络组件&设备网关): 网络组件(TCP)启动、管理、收发数据&解析
Signed-off-by: Tensai <517634644@qq.com>
2021-06-04 16:41:35 +08:00
zhou-hao 1585916776 增加属性源时间支持 2021-06-04 08:51:12 +08:00
zhou-hao 078542d98e Merge remote-tracking branch 'origin/master'
# Conflicts:
#	README.md
2021-06-02 15:16:08 +08:00
zhou-hao ab40055a6e 1.10.0 2021-06-02 15:07:09 +08:00
zhou-hao c1ee8618ae 优化 2021-06-02 15:04:38 +08:00
zhou-hao 3433eabd87 升级依赖 2021-06-02 13:46:29 +08:00
zhou-hao 5666c23af1 1.10.0-SNAPSHOT 2021-06-02 13:46:18 +08:00
老周 3962ca9bb7
Merge pull request #60 from codacy-badger/codacy-badge
Add a Codacy badge to README.md
2021-06-01 10:51:45 +08:00
The Codacy Badger 4c63ebc8e5 Add Codacy badge 2021-06-01 02:50:14 +00:00
zhou-hao e47bfcb439 1.9 2021-05-31 10:32:00 +08:00
zhou-hao 3fa9c47af3 1.9.0 2021-05-31 09:31:03 +08:00
zhou-hao c93df4715b Merge remote-tracking branch 'origin/master' 2021-05-28 17:43:41 +08:00
zhou-hao 1919e563d8 升级依赖 2021-05-28 14:41:31 +08:00
老周 b409932ac9
Create pull_request.yml 2021-05-28 14:33:55 +08:00
老周 b18698196e
Merge pull request #59 from qq517634644/master
增加数据流入库的代码注释
2021-05-28 14:30:11 +08:00
Tensai 9c65346a6c docs(数据流【设备网关-->物模型转换->时序入库】): 增加数据流入库的代码注释 优化代码结构
Signed-off-by: Tensai <517634644@qq.com>
2021-05-28 14:06:12 +08:00
Tensai 6a8a11338e Merge branch 'master' of https://github.com/jetlinks/jetlinks-community
 Conflicts:
	jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/service/data/AbstractDeviceDataStoragePolicy.java
2021-05-28 11:52:50 +08:00
Tensai f991c63403 Merge branch 'master' of https://github.com/qq517634644/jetlinks-community 2021-05-28 11:00:11 +08:00
Tensai 84ba163992 docs(数据流【设备网关-->物模型转换->时序入库】): 增加数据流入库的代码注释 优化代码结构
Signed-off-by: Tensai <517634644@qq.com>
2021-05-28 10:57:38 +08:00
zhou-hao 68bed142fe 增加用户详情 2021-05-27 16:52:28 +08:00
zhou-hao c1363bd555 支持指定属性查询记录 2021-05-26 11:14:42 +08:00
zhou-hao d8d4668da1 优化每日在线数量统计 2021-05-26 10:14:04 +08:00
zhou-hao efe03f7318 websocket支持ping pong 2021-05-25 16:51:44 +08:00
zhou-hao 4326ea01ae 增加机构绑定,解绑用户接口。 2021-05-25 11:06:02 +08:00
zhou-hao a3e46f128c 优化参数命名 2021-05-24 16:37:45 +08:00
zhou-hao 4c53c2cd80 优化自状态管理 2021-05-24 15:53:27 +08:00
zhou-hao f6cbc93baa 优化指令发送处理 2021-05-20 17:41:38 +08:00
zhou-hao f19c3421b4 忽略自动启动规则错误 2021-05-20 17:27:51 +08:00
zhou-hao e945969963 use jakarta.mail 2021-05-20 16:52:30 +08:00
zhou-hao 07faaf21eb hsweb-utils 3.0.3 2021-05-20 16:52:10 +08:00
zhou-hao 98e89d8c9b 属性增加手动写值,当物模型配置了手动写值时,下发修改属性指令将不再发送到设备,而是直接入库并返回成功。 2021-05-20 16:29:43 +08:00
zhou-hao a1cc54696e 开启全新过滤配置 2021-05-14 16:59:58 +08:00
zhou-hao dc46a0374c 赋权时,增加越权处理 2021-05-14 16:15:55 +08:00
zhou-hao b5875a9675 增加权限过滤相关配置 2021-05-10 17:15:09 +08:00
zhou-hao 20e06b844f Merge remote-tracking branch 'origin/master' 2021-04-29 14:57:59 +08:00
zhou-hao 5f1548a663 ignore application-local.yml 2021-04-29 14:57:39 +08:00
zhou-hao f37600d45c database 2021-04-29 14:57:05 +08:00
zhou-hao ae34ef70f8 org.elasticsearch: error 2021-04-29 14:56:25 +08:00
老周 f3cefa155a
Merge pull request #53 from jetlinks/dependabot/maven/jetlinks-components/io-component/commons-io-commons-io-2.7
Bump commons-io from 2.6 to 2.7 in /jetlinks-components/io-component
2021-04-27 13:34:12 +08:00
zhou-hao 9996403e86 优化说明 2021-04-27 11:30:05 +08:00
dependabot[bot] 1425a5a598
Bump commons-io from 2.6 to 2.7 in /jetlinks-components/io-component
Bumps commons-io from 2.6 to 2.7.

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-26 20:04:07 +00:00
zhou-hao babc2d7711 tcp 支持 onClientConnect 2021-04-26 16:45:33 +08:00
zhou-hao 342c59cdb0 优化子设备状态管理 2021-04-25 18:48:34 +08:00
zhou-hao 127028d32a 优化子设备状态管理 2021-04-25 18:44:35 +08:00
zhou-hao e06f1d2608 修复topics错误 2021-04-22 09:31:05 +08:00
zhou-hao ac51b0f77c 优化物模型拓展配置作用域 2021-04-19 17:00:48 +08:00
zhou-hao e0b9c4f170 优化websocket消息订阅 2021-04-19 15:41:56 +08:00
zhou-hao b97fa3d70b upgrade elasticsearch version to 7.11.2 2021-04-19 10:21:19 +08:00
zhou-hao 74084f057b upgrade elasticsearch version to 7.10.2 2021-04-19 10:16:27 +08:00
zhou-hao 9e8efece31 优化强制在线 2021-04-14 11:54:41 +08:00
zhou-hao d449aad80d 优化设备消息存储 2021-04-06 18:05:41 +08:00
zhou-hao 62edadc979 Merge remote-tracking branch 'origin/master' 2021-04-02 14:30:16 +08:00
zhou-hao 67ffacffe0 增加物模型更新 2021-04-02 14:29:47 +08:00
老周 faaadf949b
Update pom.xml 2021-04-01 20:43:59 +08:00
老周 ca183e00ef
Merge pull request #44 from jetlinks/dependabot/maven/elasticsearch.version-7.11.2
Bump elasticsearch.version from 7.9.2 to 7.11.2
2021-04-01 20:36:24 +08:00
zhou-hao ab8564447d 机构增加新增和保存接口 2021-04-01 16:50:22 +08:00
zhou-hao 5b16faab29 Merge remote-tracking branch 'origin/master' 2021-04-01 09:17:19 +08:00
zhou-hao 1895118c6a 优化说明 2021-04-01 09:17:09 +08:00
老周 e4f093b7a4
Merge pull request #45 from jetlinks/dependabot/maven/com.google.guava-guava-29.0-jre
Bump guava from 28.0-jre to 29.0-jre
2021-04-01 08:59:27 +08:00
dependabot[bot] 8f590bb115
Bump guava from 28.0-jre to 29.0-jre
Bumps [guava](https://github.com/google/guava) from 28.0-jre to 29.0-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-31 21:40:08 +00:00
zhou-hao 4ee4c92d5e 优化tcp KeepAliveTimeout 2021-03-30 18:20:34 +08:00
zhou-hao c6fe214ae4 ②群 2021-03-30 09:17:55 +08:00
zhou-hao 6baf0d61aa 优化transport转换 2021-03-30 09:15:46 +08:00
zhou-hao 4c51ca55e5 增加查询子结构API 2021-03-30 09:10:28 +08:00
zhou-hao 02a0a72080 增加QQ②群 2021-03-30 09:09:20 +08:00
zhouhao 195e4ea695 增加dictionary依赖 2021-03-23 21:00:46 +08:00
zhou-hao abb9077106 Merge remote-tracking branch 'origin/master' 2021-03-23 17:11:27 +08:00
zhou-hao 16787093b7 对单次上报的属性进行排序 2021-03-23 17:11:08 +08:00
zhou-hao 0cf85eb6dc 查询全部属性 2021-03-23 17:10:40 +08:00
zhouhao 51b786646b 优化网络组件 2021-03-19 20:09:03 +08:00
dependabot[bot] acc9b3f475
Bump elasticsearch.version from 7.9.2 to 7.11.2
Bumps `elasticsearch.version` from 7.9.2 to 7.11.2.

Updates `elasticsearch-rest-high-level-client` from 7.9.2 to 7.11.2
- [Release notes](https://github.com/elastic/elasticsearch/releases)
- [Commits](https://github.com/elastic/elasticsearch/compare/v7.9.2...v7.11.2)

Updates `elasticsearch` from 7.9.2 to 7.11.2
- [Release notes](https://github.com/elastic/elasticsearch/releases)
- [Commits](https://github.com/elastic/elasticsearch/compare/v7.9.2...v7.11.2)

Updates `elasticsearch-rest-client` from 7.9.2 to 7.11.2
- [Release notes](https://github.com/elastic/elasticsearch/releases)
- [Commits](https://github.com/elastic/elasticsearch/compare/v7.9.2...v7.11.2)

Updates `transport-netty4-client` from 7.9.2 to 7.11.2
- [Release notes](https://github.com/elastic/elasticsearch/releases)
- [Commits](https://github.com/elastic/elasticsearch/compare/v7.9.2...v7.11.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-18 19:26:57 +00:00
zhou-hao d42f9e6638 Merge remote-tracking branch 'origin/master' 2021-03-16 17:20:22 +08:00
zhou-hao 270c1e70ee 优化物模型发布 2021-03-16 14:46:05 +08:00
zhouhao 181da62f50 remove spring repository 2021-03-13 18:44:24 +08:00
zhouhao 4a0f0780a2 Merge remote-tracking branch 'origin/master' 2021-03-13 18:44:04 +08:00
zhouhao bf3a8e3d73 修复ReactorQL节点有上游节点时,header无法传递的问题 2021-03-13 18:43:49 +08:00
zhou-hao 951e1f1aaa 优化协议 2021-03-11 15:31:10 +08:00
zhou-hao 57d2f5fdc9 删除已经废弃的功能 2021-03-11 14:30:11 +08:00
zhou-hao 32d96332bc Merge remote-tracking branch 'origin/master' 2021-03-11 13:36:26 +08:00
zhou-hao 6a1f475eac add dictionary module 2021-03-11 13:36:13 +08:00
FCG 22df336c50 jetlinks-ui-antd:1.9.0镜像更新。前端持续更新中,即使拉取最新代码。 2021-03-11 10:57:04 +08:00
zhou-hao 72ebae0a9a 增加重置物模型接口 2021-03-04 17:52:03 +08:00
zhou-hao 5b59315283 hsweb framework version 4.0.10-SNAPSHOT 2021-02-25 10:37:58 +08:00
zhou-hao e4c4b9fe36 onErrorResume 2021-02-24 19:01:52 +08:00
zhou-hao fd102bd793 1.9.0-SNAPSHOT 2021-02-24 18:46:25 +08:00
zhou-hao a9fe82aa06 Merge remote-tracking branch 'origin/master' 2021-02-24 18:39:28 +08:00
zhou-hao 28101b31eb 设备日志增加消息ID 2021-02-24 18:38:46 +08:00
zhou-hao 2cd7f6f861 修复文档错误 2021-02-24 18:30:20 +08:00
zhou-hao 2b9146e301 jetlinks 1.1.6-SNAPSHOT 2021-02-24 18:29:31 +08:00
zhou-hao 48132369d9 增加更新设备物模型接口 2021-02-24 18:29:13 +08:00
zhou-hao 586473a386 easyorm 4.0.10-SNAPSHOT 2021-02-24 18:28:25 +08:00
zhou-hao 1fee2614a8 1.9.0-SNAPSHOT 2021-02-24 18:28:06 +08:00
zhou-hao 5be35712de 增加是否独立物模型属性 2021-02-20 11:20:46 +08:00
zhouhao 15d5fd9229 修复规则功能调用参数表达式错误 2021-02-09 14:38:46 +08:00
zhouhao 11abcb6834 Merge remote-tracking branch 'origin/master' 2021-02-02 14:47:16 +08:00
zhouhao b27f84909b upgrade doc dependency 2021-02-02 09:23:33 +08:00
zhou-hao d317f37ee4 设备标签增加类型转换 2021-01-28 15:35:03 +08:00
zhou-hao 18b25a05c1 增加发送指令到设备API 2021-01-27 17:03:49 +08:00
zhou-hao 8b6bc34970 import Values 2021-01-22 09:36:02 +08:00
FCG e385f73b6c
修复无法获取在线状态等信息的问题 2021-01-21 19:24:03 +08:00
zhou-hao feb5a1f46b state length = 16 2021-01-21 16:38:05 +08:00
zhou-hao cc175e5fcc 1.8 2021-01-20 10:51:01 +08:00
zhou-hao 4291808fd1 upgrade spring-boot 2.3.8.RELEASE 2021-01-20 10:48:04 +08:00
zhou-hao b6a642a1c3 优化文件下载 2021-01-20 10:04:02 +08:00
zhou-hao 1b3bd713ec remove docker-maven-plugin 2021-01-19 20:29:20 +08:00
zhou-hao dff49ff359 upgrade reactor-netty 2021-01-19 20:28:54 +08:00
zhou-hao ed5355ad53 增加默认物模型接口 2021-01-14 14:12:00 +08:00
zhou-hao db91c5a98e 优化es7.2以上版本的聚合查询 2021-01-13 16:22:18 +08:00
zhou-hao f7d030e69a 修复interval无法反序列化 2021-01-13 16:21:56 +08:00
1340 changed files with 88698 additions and 15097 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)

27
.github/workflows/pull_request.yml vendored Normal file
View File

@ -0,0 +1,27 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven
on:
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Cache Maven Repository
uses: actions/cache@v4.2.3
with:
path: ~/.m2
key: jetlinks-community-maven-repository
- name: Build with Maven
run: ./mvnw package -Dmaven.test.skip=true -Pbuild

7
.gitignore vendored
View File

@ -20,10 +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
!demo-protocol-1.0.jar
application-local.yml
dev/
.DS_Store

View File

@ -1 +1 @@
distributionUrl=http://mirrors.hust.edu.cn/apache/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,9 +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.7--RELEASE-brightgreen)
![QQ群2021514](https://img.shields.io/badge/QQ群-2021514-brightgreen)
![jetlinks](https://visitor-badge.glitch.me/badge?page_id=jetlinks)
![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)
[![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)
[![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等开发,
是一个开箱即用,可二次开发的企业级物联网基础平台。平台实现了物联网相关的众多基础功能,
@ -12,36 +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`
#### 数据权限控制
灵活的非侵入数据权限控制。可实现菜单、按钮、数据三维维度的数据权限控制。可控制单条数据的操作权限。
## 技术栈
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)
## 模块
@ -51,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

@ -1,51 +0,0 @@
version: '2'
services:
redis:
image: redis:5.0.4
container_name: jetlinks-ce-redis
# ports:
# - "6379:6379"
volumes:
- "redis-volume:/data"
command: redis-server --appendonly yes
environment:
- TZ=Asia/Shanghai
ui:
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-antd:1.4.0
container_name: jetlinks-ce-ui
ports:
- 9000:80
environment:
- "API_BASE_PATH=http://jetlinks:8848/" #API根路径
volumes:
- "jetlinks-upload-volume:/usr/share/nginx/html/upload"
links:
- jetlinks:jetlinks
jetlinks:
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-standalone:1.4.0
container_name: jetlinks-ce
ports:
- 8848:8848 # API端口
- 1883:1883 # MQTT端口
- 8000:8000 # 预留
- 8001:8001 # 预留
- 8002:8002 # 预留
- 9000:9000 # elasticsearch
- 6379:6379 # redis
volumes:
- "jetlinks-upload-volume:/static/upload" # 持久化上传的文件
- "jetlinks-data-volume:/data"
environment:
# - "JAVA_OPTS=-Xms4g -Xmx18g -XX:+UseG1GC"
- "spring.profiles.active=dev,embedded" #使用dev和embedded环境.
- "hsweb.file.upload.static-location=http://127.0.0.1:8848/upload" #上传的静态文件访问根地址,为ui的地址.
- "logging.level.io.r2dbc=warn"
- "spring.redis.host=redis"
- "logging.level.org.springframework.data=warn"
- "logging.level.org.springframework=warn"
- "logging.level.org.jetlinks=warn"
- "logging.level.org.hswebframework=warn"
- "logging.level.org.springframework.data.r2dbc.connectionfactory=warn"
volumes:
jetlinks-upload-volume:
jetlinks-data-volume:

View File

@ -6,8 +6,8 @@ services:
# ports:
# - "6379:6379"
volumes:
- "redis-volume:/data"
command: redis-server --appendonly yes
- "./data/redis:/data"
command: redis-server --appendonly yes --requirepass "JetLinks@redis"
environment:
- TZ=Asia/Shanghai
elasticsearch:
@ -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,43 +48,64 @@ services:
POSTGRES_DB: jetlinks
TZ: Asia/Shanghai
ui:
image: registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-antd:1.8.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.8.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 # 预留
- "8848:8848" # API端口
- "1883-1890:1883-1890" # 预留
- "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:
# - "JAVA_OPTS=-Xms4g -Xmx18g -XX:+UseG1GC"
# - "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"
- "elasticsearch.client.host=elasticsearch"
- "elasticsearch.client.post=9200"
- "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"
- "logging.level.org.springframework=warn"
- "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
@ -93,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.8.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>
@ -34,9 +40,60 @@
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>org.jetlinks</groupId>
<artifactId>reactor-ql</artifactId>
</dependency>
<dependency>
<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,8 +1,14 @@
package org.jetlinks.community;
import org.jetlinks.core.config.ConfigKey;
import org.jetlinks.core.metadata.MergeOption;
import java.util.Map;
/**
* 数据验证配置常量类
*
* @author zhouhao
* @see ConfigKey
*/
public interface ConfigMetadataConstants {
@ -15,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,78 +1,201 @@
package org.jetlinks.community;
import lombok.AllArgsConstructor;
import lombok.Getter;
import com.alibaba.fastjson.annotation.JSONType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
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
@NoArgsConstructor
@JsonDeserialize(using = Interval.IntervalJSONDeserializer.class)
@JSONType(deserializer = Interval.IntervalJSONDeserializer.class)
public class Interval {
public static String year = "y";
public static String quarter = "q";
public static String month = "M";
public static String weeks = "w";
public static String days = "d";
public static String hours = "h";
public static String minutes = "m";
public static String seconds = "s";
public static final String year = "y";
public static final String quarter = "q";
public static final String month = "M";
public static final String weeks = "w";
public static final String days = "d";
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 final BigDecimal number;
private BigDecimal number;
private final 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);
}
private String expression;
@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);
}
public static Interval of(String expr) {
char[] number = new char[32];
char[] chars = expr.toCharArray();
int numIndex = 0;
for (char c : expr.toCharArray()) {
if (c == '-' || c == '.' || (c >= '0' && c <= '9')) {
number[numIndex++] = c;
continue;
numIndex++;
} else {
BigDecimal val = new BigDecimal(chars, 0, numIndex);
return new Interval(val, expr.substring(numIndex));
}
BigDecimal val = new BigDecimal(number, 0, numIndex);
return new Interval(val, expr.substring(numIndex));
}
throw new IllegalArgumentException("can not parse interval expression:" + expr);
}
public String getDefaultFormat() {
switch (getExpression()) {
case year:
return "yyyy";
case quarter:
case month:
return "yyyy-MM";
case days:
return "yyyy-MM-dd";
case hours:
return "MM-dd HH";
case minutes:
return "MM-dd HH:mm";
case seconds:
return "HH:mm:ss";
default:
return "yyyy-MM-dd HH:mm:ss";
}
}
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
@SneakyThrows
public Interval deserialize(JsonParser jp, DeserializationContext ctxt) {
JsonNode node = jp.getCodec().readTree(jp);
String currentName = jp.currentName();
Object currentValue = jp.getCurrentValue();
if (currentName == null || currentValue == null) {
return null;
}
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,31 +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();
@ -45,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

@ -0,0 +1,123 @@
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);
}
/**
* 判断属性是否手动赋值
*
* @param metadata 属性物模型
* @return 是否手动赋值
*/
static boolean isManual(PropertyMetadata metadata) {
return metadata.getExpand(id)
.map(manual::equals)
.orElse(false);
}
}
/**
* 属性读写模式
*/
interface AccessMode {
String id = "accessMode";
//
String read = "r";
//
String write = "w";
//上报
String report = "u";
static boolean isRead(PropertyMetadata property) {
return property
.getExpand(id)
.map(val -> val.toString().contains(read))
.orElse(true);
}
static boolean isWrite(PropertyMetadata property) {
return property
.getExpand(id)
.map(val -> val.toString().contains(write))
.orElseGet(() -> property
.getExpand("readOnly")
.map(readOnly -> !CastUtils.castBoolean(readOnly))
.orElse(true)
);
}
static boolean isReport(PropertyMetadata property) {
return property
.getExpand(id)
.map(val -> val.toString().contains(report))
.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.8.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

@ -9,6 +9,11 @@ import org.jetlinks.community.utils.TimeUtils;
import java.util.Date;
/**
* 时间反序列化配置
*
* @author zhouhao
*/
public class SmartDateDeserializer extends JsonDeserializer<Date> {
@Override
@SneakyThrows

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();
}
}
}

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