init
@@ -0,0 +1,41 @@
|
||||
# ================================
|
||||
# 数据库配置
|
||||
# ================================
|
||||
MYSQL_HOST=mysql
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_DATABASE=univ
|
||||
MYSQL_USERNAME=root
|
||||
MYSQL_PASSWORD=Haha@2025
|
||||
|
||||
# ================================
|
||||
# Redis 配置
|
||||
# ================================
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=Nexiot2025
|
||||
REDIS_DATABASE=11
|
||||
|
||||
# ================================
|
||||
# MQTT 配置
|
||||
# ================================
|
||||
MQTT_HOST=tcp://emqx:1883
|
||||
MQTT_USERNAME=universal_iot
|
||||
MQTT_PASSWORD=Abc@2025
|
||||
MQTT_CLIENT_ID_PREFIX=univ_iot_docker
|
||||
|
||||
# ================================
|
||||
# 应用配置
|
||||
# ================================
|
||||
SPRING_PROFILES_ACTIVE=docker
|
||||
SERVER_PORT=8080
|
||||
|
||||
# ================================
|
||||
# 端口映射 (可选,用于外部访问)
|
||||
# ================================
|
||||
BACKEND_PORT=8080
|
||||
FRONTEND_PORT=80
|
||||
MYSQL_EXTERNAL_PORT=3306
|
||||
REDIS_EXTERNAL_PORT=6379
|
||||
MQTT_EXTERNAL_PORT=1883
|
||||
EMQX_DASHBOARD_PORT=18083
|
||||
ADMINER_PORT=8081
|
||||
@@ -0,0 +1,7 @@
|
||||
# 环境相关配置文件 - 合并时保留目标分支版本
|
||||
cn-universal-web/src/main/resources/application-docker.properties merge=ours
|
||||
cn-universal-web/src/main/resources/application-huo.properties merge=ours
|
||||
cn-universal-web/src/main/resources/application-redisson-examples merge=ours
|
||||
cn-universal-web/src/main/resources/application-test.properties merge=ours
|
||||
cn-universal-web/src/main/resources/application-test2.properties merge=ours
|
||||
cn-universal-web/src/main/java/cn/universal/web/controller/VersionController.java merge=ours
|
||||
@@ -0,0 +1,65 @@
|
||||
git######################################################################
|
||||
|
||||
classpath.txt
|
||||
|
||||
# 1. 清除所有缓存(保留本地文件)
|
||||
#git rm -r --cached .
|
||||
# 或清除单个文件:git rm --cached <文件名>
|
||||
|
||||
# 2. 重新添加所有文件(应用新规则)
|
||||
#git add .
|
||||
|
||||
# 3. 提交更改
|
||||
#git commit -m "更新 .gitignore 规则"
|
||||
|
||||
###########################################
|
||||
.DS_Store
|
||||
.DS_Store/
|
||||
.gradle
|
||||
/build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
######################################################################
|
||||
# IDE
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
uploadPath/
|
||||
|
||||
### NetBeans ###
|
||||
nbproject/private/
|
||||
build/*
|
||||
nbbuild/
|
||||
dist/
|
||||
nbdist/
|
||||
.nb-gradle/
|
||||
######################################################################
|
||||
# Others
|
||||
*.log
|
||||
*.xml.versionsBackup
|
||||
|
||||
!*/build/*.java
|
||||
!*/build/*.html
|
||||
!*/build/*.xml
|
||||
/.vscode/settings.json
|
||||
/logs/
|
||||
/.specstory/
|
||||
/cn-universal-web/logs/cn-universal/sys-error.*.gz
|
||||
/cn-universal-web/logs/cn-universal/sys-info.*.gz
|
||||
/cn-universal-web/logs/cn-universal/tcp.2025-*.gz
|
||||
/cn-universal-web/logs/cn-universal/api.2025-*.gz
|
||||
/.cursorindexingignore
|
||||
@@ -0,0 +1,128 @@
|
||||
# 📝 更新日志
|
||||
|
||||
> **最近更新时间**:2025年10月8日
|
||||
|
||||
本文档记录了 NexIoT 物联网平功能变更。
|
||||
|
||||
## 🚀 提交历史
|
||||
|
||||
### 📅 2025年10月第1周(9.28-10.8)
|
||||
|
||||
- 🌉 【数据桥接功能】
|
||||
- ✅ 桥接databridge功能,设计支持数据输入、数据输出和双向数据
|
||||
- ✅ 优化依赖调整,明确模块职责
|
||||
- ✅ 数据桥接分类为输入和输出,计划适配阿里云、腾讯云和华为云
|
||||
- ✅ 完成数据桥接的数据源前端页面
|
||||
- ✅ 开启数据桥接处理,目前只实现后端的JDBC输出
|
||||
- ✅ 修复JDBC的数据桥接问题
|
||||
- ✅ 修复JDBC的数据库保存问题
|
||||
- ✅ 数据桥接增加详细的日志记录
|
||||
- ✅ 验证桥接配置功能
|
||||
- ✅ 修复桥接弹窗过大的问题
|
||||
|
||||
- 📹 【乐橙监控优化】
|
||||
- ✅ 增加监控可能多通道的适配
|
||||
- ✅ 乐橙增加多个通道的适配
|
||||
- ✅ 页面增加多通道选择功能
|
||||
|
||||
- 🔧 【系统优化与修复】
|
||||
- ✅ OpenAPI增加网关子设备列表分页查询
|
||||
- ✅ 开源规范要求,标记物模型的代码来源
|
||||
- ✅ ip2region调整打包路径
|
||||
- ✅ 增加ip2region_v4本地文件,日志地区不查询外部接口,实现自我闭环
|
||||
- ✅ 记录日志不再异步执行,提升系统稳定性
|
||||
|
||||
### 📅 2025年9月第4周(9.20-9.27)
|
||||
|
||||
- 🔧 【产品管理优化】
|
||||
- ✅ MQTT和TCP产品默认开启自动注册功能
|
||||
- ✅ 完善产品管理连接信息显示,解决无法找到入口问题
|
||||
- ✅ 系统支持使用内置连接和自建连接
|
||||
- ✅ 优化TCP连接信息显示,支持SNI信息和概念
|
||||
- ✅ TCP服务默认绑定网络服务,支持绑定状态管理
|
||||
|
||||
- 🏠 【网关设备管理】
|
||||
- ✅ 网关与网关子设备功能调整,支持一个网关绑定多个不同产品的子设备
|
||||
- ✅ 网关子设备显示绑定网关设备信息
|
||||
- ✅ 乐橙设备增删改查增加验证机制
|
||||
|
||||
- 📹 【视频模块优化】
|
||||
- ✅ 乐橙播放器界面优化
|
||||
- ✅ 视频模块权限调整
|
||||
- ✅ 真实设备播放摄像头功能修复
|
||||
- ✅ 暂时移除音频检查功能
|
||||
|
||||
- 🛠️ 【系统优化与修复】
|
||||
- ✅ 移除fastjson2依赖,减少系统间强依赖
|
||||
- ✅ 调整依赖注入方式,修复无效bean注入问题
|
||||
- ✅ 优化查询条件与索引匹配
|
||||
- ✅ 修复test环境显示问题
|
||||
- ✅ 清理无用代码和文档
|
||||
|
||||
- 📝 【文档与描述】
|
||||
- ✅ 删除部分markdown设计过程文档
|
||||
- ✅ 调整描述文字和界面显示
|
||||
|
||||
### 📅 2025年9月第3周(9.12-9.19)
|
||||
|
||||
- 🧩 【设备协议扩展】
|
||||
- ✅ 新增 UDP 通信模块(默认开启),适配 UDP 协议传输
|
||||
- ✅ 新增 Ezviz(萤石)接入模块,支持萤石设备接入
|
||||
- ✅ 优化 imoulife(乐橙)模块,支持自有开放平台账号
|
||||
|
||||
- 🧠 【设备影子系统】
|
||||
- ✅ 增加设备影子强制刷盘机制,支持异常状态恢复
|
||||
- ✅ 解析流程可获取历史影子数据(零代码侵入联动判断)
|
||||
- ✅ Web IDE 支持直接读取设备影子数据
|
||||
|
||||
- 🔌 【连接管理优化】
|
||||
- ✅ 设备重连时自动替换旧连接,解决僵尸连接问题
|
||||
- ✅ 重构 TCP 通信逻辑(tcpdown 模块),提升可维护性
|
||||
|
||||
- ⚙️ 【性能与稳定性】
|
||||
- ✅ 修复产品缓存不失效问题,优化数据库查询效率
|
||||
- ✅ 日志表结构升级,支持自动分区
|
||||
- ✅ 完善集群消息日志存储逻辑,避免指令路由异常
|
||||
|
||||
- 🧰 【工具与配置】
|
||||
- ✅ 封装统一工具类(uniqueId 生成、时间格式化等)
|
||||
- ✅ 公共配置支持 boolean 类型适配
|
||||
- ✅ 修复时间格式化、枚举命名重复等问题
|
||||
|
||||
### 📅 2025年9月第2周(9.8-9.12)
|
||||
|
||||
- 🔧 【零代码侵入-TCP接入】解决电表DT645使用netty协议半包全包的问题,增加一个codec和2个工具类。
|
||||
|
||||
- 🛠️ 【WEB-IDE调试器】增加16进制、ASCII和Modbus相关等20余个公共方法,供IDE的直接调用。
|
||||
|
||||
- 🔄 【cluster集群】集群环境低概率会遇到指令路由错误的问题。
|
||||
|
||||
- ⚡ 【优化若干】重构iotId的生成逻辑,极大较少数据库的非必要查询。
|
||||
|
||||
- 📸 【产品管理】增加产品图片上传,增加七牛云。
|
||||
|
||||
### 📅 2025年9月第1周(9.1-9.7)
|
||||
|
||||
- 🚀 【设备影子】设备影子热数据不再实时更新数据库,用redis缓存,定时刷库,提高吞吐量。
|
||||
|
||||
- 🌐 【北向应用】HTTP北向应用数据推送问题修复若干、推送安全鉴权功能增强。
|
||||
|
||||
- 🛡️ 【TCP接入服务】增加异常IP的封禁策略;集群环境的控制重启、关闭、启动优化。
|
||||
|
||||
- 📊 【流程编排】增加可视化的集成,为工业场景及特殊情况的接入、采集和推送作为补充。
|
||||
|
||||
- 🔧 【其他】修复错误若干;优化日志;
|
||||
|
||||
## 📞 联系我们
|
||||
|
||||
- **项目地址
|
||||
**:[https://gitee.com/nexiotplatform/Universal-IoT-Java](https://gitee.com/nexiotplatform/Universal-IoT-Java)
|
||||
- **文档地址
|
||||
**:[https://nexiotplatform.github.io/universal-iot-docs/](https://nexiotplatform.github.io/universal-iot-docs/)
|
||||
- **在线演示**:[http://iot.192886.xyz:81/](http://iot.192886.xyz:81/)
|
||||
- **调试IDE**:[http://iot.192886.xyz:81/magic/debug/index.html](http://iot.192886.xyz:81/magic/debug/index.html)
|
||||
|
||||
---
|
||||
|
||||
|
||||
**最后更新时间:2025年10月8日**
|
||||
@@ -0,0 +1,65 @@
|
||||
# IoT 平台 Docker 使用说明
|
||||
|
||||
## 📁 Docker 文件位置
|
||||
|
||||
所有 Docker 相关文件已整理到 `docker/` 文件夹中:
|
||||
|
||||
```bash
|
||||
# 进入 Docker 目录
|
||||
cd docker
|
||||
|
||||
# 查看所有文件
|
||||
ls -la
|
||||
```
|
||||
|
||||
## 🚀 快速部署
|
||||
|
||||
### 1. 构建镜像
|
||||
|
||||
```bash
|
||||
cd docker
|
||||
|
||||
# 构建默认版本 (推荐)
|
||||
./build-docker.sh default local
|
||||
```
|
||||
|
||||
### 2. 启动服务
|
||||
|
||||
```bash
|
||||
# 仅启动应用服务 (当前推荐)
|
||||
./start-apps.sh
|
||||
|
||||
# 或启动完整服务 (需要解决网络问题)
|
||||
./start-docker.sh
|
||||
```
|
||||
|
||||
## 🌐 访问地址
|
||||
|
||||
- **前端界面**: http://localhost:80
|
||||
- **后端 API**: http://localhost:9092
|
||||
- **数据库管理**: http://localhost:8081 (Adminer)
|
||||
|
||||
## 📋 常用命令
|
||||
|
||||
```bash
|
||||
cd docker
|
||||
|
||||
# 查看服务状态
|
||||
docker-compose -f docker-compose.apps.yml ps
|
||||
|
||||
# 查看日志
|
||||
docker-compose -f docker-compose.apps.yml logs -f
|
||||
|
||||
# 停止服务
|
||||
docker-compose -f docker-compose.apps.yml down
|
||||
```
|
||||
|
||||
## 📚 详细文档
|
||||
|
||||
更多详细信息请查看 `docker/README.md` 和 `docker/` 文件夹中的其他文档。
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **端口配置**: 后端端口已更新为 9092
|
||||
2. **数据库初始化**: MySQL 启动后会自动执行 `02-iot.sql` 初始化数据
|
||||
3. **网络问题**: 如遇镜像拉取失败,请使用 `./build-docker.sh default local`
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,16 @@
|
||||
NOTICE file for cn.universal.core.metadata
|
||||
|
||||
This project includes code derived from the JetLinks core library,
|
||||
which is licensed under the Apache License 2.0.
|
||||
|
||||
Original source code:
|
||||
- Repository: https://github.com/jetlinks/jetlinks-core
|
||||
- Original package: org.jetlinks.core.metadata
|
||||
- Original copyright: Copyright 2019-2024 JetLinks Community
|
||||
|
||||
Modifications made to the original code:
|
||||
- The package name has been changed from "org.jetlinks.core.metadata" to "cn.universal.core.metadata".
|
||||
- 因IDE批量修改包名操作,可能导致部分文件头部的原始作者信息被误删,原始作者信息可参考原项目代码库。
|
||||
- 为确保代码在当前项目环境中编译通过,对部分方法进行了必要的调整(如依赖类引用路径修正、语法兼容处理等),未改变核心功能逻辑。
|
||||
|
||||
For the full text of the Apache License 2.0, see the LICENSE file in this directory.
|
||||
@@ -0,0 +1,2 @@
|
||||
健康检查:http://localhost:9091/actuator/health
|
||||
安全状态:http://localhost:9091/actuator/health/security
|
||||
@@ -0,0 +1,297 @@
|
||||
# 🤖 NexIoT AI物联网平台
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 🎯 创新的"真·零代码侵入"物联网平台
|
||||
|
||||
**突破传统物联网平台设计思路 · 设备驱动完全外置 · 一键导出即用 · 零代码侵入**
|
||||
|
||||
[📖 文档地址](https://nexiotplatform.github.io/universal-iot-docs/) | [🌐 在线演示](http://iot.192886.xyz:81/) | [🔧 AI调试IDE](http://iot.192886.xyz:81/magic/debug/index.html)
|
||||
|
||||
</div>
|
||||
|
||||
## 📋 目录
|
||||
|
||||
- [✨ 平台简介](#-平台简介)
|
||||
- [🌟 平台亮点](#-平台亮点)
|
||||
- [🚀 快速开始](#-快速开始)
|
||||
- [📦 运行部署](#-运行部署)
|
||||
- [🎯 核心功能](#-核心功能)
|
||||
- [🏗️ 技术架构](#️-技术架构)
|
||||
- [📈 正在推进](#-正在推进)
|
||||
- [💡 最佳实践](#-最佳实践)
|
||||
- [🎥 视频教程](#-视频教程)
|
||||
- [📸 界面展示](#-界面展示)
|
||||
- [📝 更新日志](#-更新日志)
|
||||
- [🤝 贡献指南](#-贡献指南)
|
||||
- [🛠️ 技术支持](#️-技术支持与服务)
|
||||
- [📄 开源协议](#-开源协议)
|
||||
|
||||
## ✨ 平台简介
|
||||
|
||||
**NexIoT AI物联网平台** 是一款采用创新架构设计的**真·零代码侵入**企业级物联网平台。
|
||||
|
||||
### 🎯 核心亮点
|
||||
|
||||
**🚀 这个项目能为你做什么?**
|
||||
告别繁重的设备驱动开发,告别重复造轮子。通过解耦设计,你可以专注业务逻辑,复用他人成果,快速构建物联网应用。
|
||||
|
||||
**🔧 解耦设计,共建共享**
|
||||
基于实际项目经验,将设备驱动与平台核心完全解耦。用户可导出完整产品包(物模型+驱动源码),NexIoT用户之间可直接共享使用。你对接的设备,我可直接导入;我开发的驱动,你拿来即用。
|
||||
|
||||
**⚡ 零代码侵入,热插拔架构**
|
||||
一键导入,无需修改平台代码,无需重新编译部署。设备驱动在线热更新,新增设备类型零停机。稳定运行几年无需重启维护。
|
||||
|
||||
### 🎯 核心价值
|
||||
- **🚀 零代码侵入**:设备驱动完全外置,一键导出源码直接使用,无任何代码侵入
|
||||
- **🔧 解耦架构**:驱动与平台完全分离,热插拔式设备接入,生态共享
|
||||
- **🏗️ 全协议支持**:TCP、MQTT、Modbus、天翼物联、HTTP等主流协议
|
||||
- **⚡ 企业级性能**:支持`集群`部署,支持千万级设备接入,高可用、高性能、国际化
|
||||
|
||||
## 🌟 平台亮点
|
||||
|
||||
- ✅ **设备驱动外置**:编解码逻辑在浏览器IDE中实现,与平台核心代码零耦合
|
||||
- ✅ **一键导出**:产品物模型 + 设备驱动源码,打包导出直接注册使用
|
||||
- ✅ **零代码侵入**:无需修改平台代码,无需重新编译部署,真正的零侵入
|
||||
- ✅ **热插拔架构**:设备驱动在线热更新,新增设备类型零停机
|
||||
- ✅ **可视化配置**:Web界面完成所有配置,基于浏览器的调试IDE
|
||||
- ✅ **全协议支持**:Modbus RTU/TCP、MQTT、HTTP等工业协议和物联网协议
|
||||
- ✅ **云平台对接**:天翼物联、移动OneNet等云平台集成
|
||||
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 🎯 演示地址
|
||||
|
||||
- **演示地址**: <http://iot.192886.xyz:81/>
|
||||
- **调试IDE**: <http://iot.192886.xyz:81/magic/debug/index.html>
|
||||
- **演示账号**: test
|
||||
- **演示密码**: Haha@2025
|
||||
- **文档地址**: <https://nexiotplatform.github.io/universal-iot-docs/>
|
||||
- **文档地址(国内)**: <http://docs.192886.xyz:81/>
|
||||
|
||||
## 🏗️ 技术架构
|
||||
|
||||
### 🛠️ 技术栈
|
||||
|
||||
#### 🚀 核心框架(极简轻量)
|
||||
- **后端框架**:`Java 21` `SpringBoot 3.5` `Tk.Mybatis 5.0.1`
|
||||
- **前端技术**:基于`RuoYi-Antdv`构建,感谢开源社区!
|
||||
|
||||
#### 💾 数据存储
|
||||
- **关系数据库**:MySQL 5.8+
|
||||
- **缓存系统**:Redis 6.0+
|
||||
- **时序数据库**:ClickHouse / IoTDB(配置开启)
|
||||
|
||||
## 📈 正在推进
|
||||
|
||||
### 🚀 近期规划
|
||||
- **🤖 AI能力增强**:智能故障诊断
|
||||
- **📱 移动端应用**:付费图鸟定小程序,具备指令控制、属性、告警查看,轻量化、多管理员
|
||||
|
||||
### 🌟 长期愿景
|
||||
- **🧠 深度学习**:设备行为学习、预测性维护、智能优化
|
||||
- **🌐 生态建设**:插件市场、第三方集成、开发者社区
|
||||
- **🏭 行业方案**:智能制造、智慧城市、智慧农业等垂直解决方案
|
||||
|
||||
|
||||
## 📸 实战案例与视频教程
|
||||
|
||||
### 📊 支持设备类型
|
||||
|
||||
| | |
|
||||
|-------------------------------------------------|-----------------------------------------------|
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
### 📡 天翼物联AI接入教程
|
||||
|
||||
#### 🌊 NB水浸传感器
|
||||
|
||||
1. **【手把手实战:水浸传感器开箱:初见与硬件解析】**
|
||||
- 📺 [B站观看](https://www.bilibili.com/video/BV1kLY5z5EMH/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
|
||||
- 📝 内容:设备开箱、硬件介绍、AI智能识别功能测试
|
||||
|
||||
2. **【手把手实战:NexIoT接入Ctwing参数配置】**
|
||||
- 📺 [B站观看](https://www.bilibili.com/video/BV1jVY7zVEjM/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
|
||||
- 📝 内容:天翼物联平台参数配置、智能设备注册流程
|
||||
|
||||
3. **【手把手实战:NB水浸传感器CTWing接入全流程】**
|
||||
- 📺 [B站观看](https://www.bilibili.com/video/BV1sJaZzBEfr/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
|
||||
- 📝 内容:外置驱动的完整设备接入流程,从智能配置到数据上报
|
||||
|
||||
### 🔌 TCP直连AI接入教程
|
||||
|
||||
#### ⚡ DL/T645电表
|
||||
|
||||
1. **【NexIoT开源】手把手实战:DL/T645电表TCP直连接入】**
|
||||
- 📺 [B站观看](https://www.bilibili.com/video/BV1x4pBzZEVN/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
|
||||
- 📝 内容:TCP协议解析、设备直连配置、智能数据解析
|
||||
|
||||
2. **【NexIoT开源】手把手实战:产品创建与SNI解读】**
|
||||
- 📺 [B站观看](https://www.bilibili.com/video/BV1zSWNzoETF/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
|
||||
- 📝 内容:创建流程、智能SNI设备标识解析、AI协议调试
|
||||
|
||||
3. **【NexIoT开源】手把手实战:如何写TCP接入的DL/T645电表解析脚本】**
|
||||
- 📺 [B站观看](https://www.bilibili.com/video/BV1SAWVzwEZE/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
|
||||
- 📝 内容:数据流介绍、智能调试IDE的编写和调试、645电表的AI解析
|
||||
|
||||
#### ⚡ 接入成果展示
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 📸 平台界面展示
|
||||
|
||||
### 🏗️ 驱动的系统架构
|
||||
|
||||

|
||||
|
||||
### 🚀 云原生部署架构
|
||||
|
||||

|
||||
|
||||
### 🖥️ 智能化功能展示
|
||||
|
||||
#### 🌐 国际双语支持
|
||||
|
||||

|
||||
|
||||
#### 🌐 驱动的天翼产品接入
|
||||
|
||||

|
||||
|
||||
#### 🔄 流程编排引擎
|
||||
|
||||

|
||||
|
||||
#### 🛠️ 协议统一管理与智能IDE
|
||||
|
||||

|
||||
|
||||
### 🎯 功能深度展示
|
||||
|
||||
| | |
|
||||
|------------------------------------------|---------------------------------------|
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
我们维护详细的更新日志,记录每个版本的重要变更和新功能。
|
||||
|
||||
### 📖 完整更新日志
|
||||
|
||||
查看完整的更新历史,请访问:[📝 CHANGELOG.md](./CHANGELOG.md)
|
||||
|
||||
---
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
我们热烈欢迎所有形式的贡献!无论是代码、文档、问题反馈还是功能建议,您的参与将推动物联网平台的发展。
|
||||
|
||||
### 🚀 如何贡献
|
||||
|
||||
#### 📋 贡献流程
|
||||
|
||||
1. **Fork 项目**
|
||||
```bash
|
||||
# 点击右上角 Fork 按钮,创建您的项目副本
|
||||
```
|
||||
|
||||
2. **创建功能分支**
|
||||
```bash
|
||||
git checkout -b feature/ai-enhanced-protocol-support
|
||||
git checkout -b fix/zero-code-debugging-issue
|
||||
git checkout -b docs/improve-ai-tutorials
|
||||
```
|
||||
|
||||
3. **提交代码**
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: add AI protocol recognition for Modbus devices"
|
||||
git push origin feature/ai-enhanced-protocol-support
|
||||
```
|
||||
|
||||
4. **创建 Pull Request**
|
||||
- 在 Gitee 上创建 Pull Request
|
||||
- 详细描述您的修改内容和增强功能
|
||||
- 等待代码审查和功能验证
|
||||
|
||||
#### 🌟 社区价值观
|
||||
- **🤝 相互尊重**:欢迎所有开发者加入,有问题我们第一时间回复
|
||||
- **⭐ 真诚支持**:喜欢请点star,不喜欢也请尊重,不要恶意行为
|
||||
- **🚀 共同成长**:分享技术经验,推动物联网智能化发展
|
||||
- **💡 开放创新**:鼓励功能创新和零代码技术改进
|
||||
|
||||
#### 📢 重要声明
|
||||
- **自媒体转发**:欢迎转发项目信息,请遵循AGPL3.0开源协议
|
||||
- **企业授权**:商业使用需获得授权,保留企业授权信息
|
||||
- **法律追责**:如单位误用引起法律后果,保留追责责任
|
||||
|
||||
#### 🌐 社区联系方式
|
||||
|
||||
| QQ群 | 微信 | B站 | 抖音 |
|
||||
|-------------------------------|-------------------------------|----------------------------------|----------------------------------|
|
||||
|  |  |  |  |
|
||||
|
||||
## 🛠️ 技术支持与服务
|
||||
|
||||
我们提供全方位的技术支持服务,从基础答疑到企业级解决方案,满足不同层次的需求。
|
||||
|
||||
### 📋 服务类型对比
|
||||
|
||||
| 服务类型 | 服务内容 | 价格 | 适用场景 | 联系方式 |
|
||||
|---------|---------|--------------|----------|----------|
|
||||
| **🆓 社区技术答疑** | • 基础问题答疑<br>• 使用指导<br>• 社区交流 | **免费** | 学习、使用 | QQ群、微信群 |
|
||||
| **📚 技术文档** | • 技术文档<br>• 实践指南<br>• 视频教程 | **免费** | 自学、参考 | [文档地址](https://nexiotplatform.github.io/universal-iot-docs/) |
|
||||
| **☁️ 线上部署服务** | • 客户提供硬件<br>• 完成系统部署<br>• 云服务部署 | **¥169** | 快速上线、云部署 | 联系客服 |
|
||||
| **🔧 专属技术支持** | • 各类对接支持<br>• 使用指导<br>• 部署方案<br>• 解决方案 | **¥99/天** | 项目对接、技术咨询 | 联系客服 |
|
||||
| **⚡ 设备接入服务** | • IDE解析源码<br>• 产品配置导出<br>• 定制化接入 | **¥1000/日** | 复杂设备接入、定制开发 | 联系客服 |
|
||||
| **🏢 企业省心服务** | • 商业版本地部署<br>• 代运维服务<br>• 5×8技术支持 | **¥5,999/年** | 仅剩5名,企业级应用、长期运维 | 联系客服 |
|
||||
|
||||
---
|
||||
|
||||
## 📄 开源协议与授权
|
||||
|
||||
### 📜 详细授权条款
|
||||
|
||||
#### ✅ 授权用户权益
|
||||
1. **内部使用**:授权用户可无限期在其内部使用
|
||||
- 企业授权:不含分公司、子公司及第三方公司
|
||||
- 个人授权:仅限个人使用,不可用于在职公司或第三方
|
||||
|
||||
2. **开发权限**:授权用户可通过项目形式进行源码二次开发
|
||||
- 定制化软件必须加密打包后交付客户
|
||||
- 如需交付源码,必须为客户购买对应商业授权
|
||||
|
||||
#### ❌ 授权限制
|
||||
1. **禁止转让**:不可向第三方发行或转让授权源码
|
||||
- 无论有偿或无偿转让
|
||||
- 不可申请包含本项目的软件著作权
|
||||
|
||||
### 🔒 开源和商业
|
||||
|
||||
开源版本暂不包含 **TCP**、**天翼物联ctaiot** 模块。可自行二次开发扩展,商业用途需赞助项目,获得授权。
|
||||
|
||||
### 🙏 致谢
|
||||
|
||||
感谢以下开源项目和技术平台:
|
||||
|
||||
- **开源框架**:若依、Antdv、jetlink、ssssssss-team
|
||||
- **云平台**:阿里云、华为云、腾讯云、AEP、OneNet 等物联网平台
|
||||
- **社区支持**:所有贡献者和用户的支持与反馈
|
||||
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 472 KiB |
|
After Width: | Height: | Size: 578 KiB |
|
After Width: | Height: | Size: 543 KiB |
|
After Width: | Height: | Size: 453 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 640 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 951 KiB |
|
After Width: | Height: | Size: 728 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 565 KiB |
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 525 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 721 KiB |
|
After Width: | Height: | Size: 317 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 27 MiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 333 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 730 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 956 KiB |
|
After Width: | Height: | Size: 16 MiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 4.1 MiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 536 KiB |
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>cn-universal</artifactId>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<version>1.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>cn-universal-admin</artifactId>
|
||||
<dependencies>
|
||||
|
||||
<!--velocity代码生成使用模板 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-persistence</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-rule</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-dm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-oss</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<!--排除这个slf4j-log4j12-->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Validation依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<!-- OAuth2 Resource Server (JWT support) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.vaadin.external.google</groupId>
|
||||
<artifactId>android-json</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-mqtt-protocol</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-tcp-protocol</artifactId>
|
||||
<scope>compile</scope>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-udp-protocol</artifactId>
|
||||
<scope>compile</scope>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-notice</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-security</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.common.aspectj;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.universal.common.annotation.DataScope;
|
||||
import cn.universal.common.domain.BaseEntity;
|
||||
import cn.universal.common.utils.ReflectUtils;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.security.service.IoTUserService;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.Signature;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 数据过滤处理 */
|
||||
@Aspect
|
||||
@Component
|
||||
public class DataScopeAspect {
|
||||
|
||||
/** 数据范围 */
|
||||
public static final String SCOPE = "scope";
|
||||
|
||||
@Resource private IoTUserService ioTUserService;
|
||||
|
||||
// 配置织入点
|
||||
@Pointcut("@annotation(cn.universal.common.annotation.DataScope)")
|
||||
public void dataScopePointCut() {}
|
||||
|
||||
@Before("dataScopePointCut()")
|
||||
public void doBefore(JoinPoint point) throws Throwable {
|
||||
clearDataScope(point);
|
||||
handleDataScope(point);
|
||||
}
|
||||
|
||||
protected void handleDataScope(final JoinPoint joinPoint) {
|
||||
// 获得注解
|
||||
DataScope controllerDataScope = getAnnotationLog(joinPoint);
|
||||
if (controllerDataScope == null) {
|
||||
return;
|
||||
}
|
||||
IoTUser iotUser = ioTUserService.selectUserByUnionId(SecurityUtils.getUnionId());
|
||||
if (!iotUser.isAdmin()) {
|
||||
dataScopeFilter(joinPoint, controllerDataScope, iotUser.getUnionId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据范围过滤
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param dataScope 数据范围
|
||||
* @param unionId 用户unionId
|
||||
*/
|
||||
public static void dataScopeFilter(JoinPoint joinPoint, DataScope dataScope, String unionId) {
|
||||
|
||||
StringBuilder sqlString = new StringBuilder();
|
||||
String alias = StrUtil.isNotBlank(dataScope.alias()) ? dataScope.alias() + "." : "";
|
||||
sqlString.append(StrUtil.format(" {}union_id = '{}' ", alias, unionId));
|
||||
|
||||
if (StrUtil.isNotBlank(dataScope.created())) {
|
||||
sqlString.append(StrUtil.format(" or {} = '{}' ", dataScope.created(), unionId));
|
||||
}
|
||||
|
||||
putDataScope(joinPoint, sqlString.toString(), SCOPE);
|
||||
}
|
||||
|
||||
/** 是否存在注解,如果存在就获取 */
|
||||
private DataScope getAnnotationLog(JoinPoint joinPoint) {
|
||||
Signature signature = joinPoint.getSignature();
|
||||
MethodSignature methodSignature = (MethodSignature) signature;
|
||||
Method method = methodSignature.getMethod();
|
||||
|
||||
if (method != null) {
|
||||
return method.getAnnotation(DataScope.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 拼接权限sql前先清空params.dataScope参数防止注入 */
|
||||
private void clearDataScope(final JoinPoint joinPoint) {
|
||||
Object params = joinPoint.getArgs()[0];
|
||||
if (Validator.isNotNull(params)) {
|
||||
putDataScope(joinPoint, "", SCOPE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void putDataScope(JoinPoint joinPoint, String sql, String queryKey) {
|
||||
Object params = joinPoint.getArgs()[0];
|
||||
if (Validator.isNotNull(params)) {
|
||||
if (params instanceof BaseEntity) {
|
||||
BaseEntity baseEntity = (BaseEntity) params;
|
||||
baseEntity.getParams().put(queryKey, sql);
|
||||
} else {
|
||||
Map<String, Object> invoke = ReflectUtils.invokeGetter(params, "params");
|
||||
invoke.put(queryKey, sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.common.aspectj;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.common.annotation.Log;
|
||||
import cn.universal.common.enums.BusinessStatus;
|
||||
import cn.universal.common.enums.HttpMethod;
|
||||
import cn.universal.common.utils.LocationUtils;
|
||||
import cn.universal.common.utils.ServletUtils;
|
||||
import cn.universal.common.utils.SpringUtils;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.entity.SysOperLog;
|
||||
import cn.universal.security.service.AdminLogService;
|
||||
import cn.universal.security.service.IoTUserService;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.Signature;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/** 操作日志记录处理 */
|
||||
@Aspect
|
||||
@Component
|
||||
public class LogAspect {
|
||||
|
||||
@Resource private IoTUserService ioTUserService;
|
||||
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
|
||||
|
||||
// 配置织入点
|
||||
@Pointcut("@annotation(cn.universal.common.annotation.Log)")
|
||||
public void logPointCut() {}
|
||||
|
||||
/**
|
||||
* 处理完请求后执行
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
*/
|
||||
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
|
||||
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
|
||||
handleLog(joinPoint, null, jsonResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截异常操作
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param e 异常
|
||||
*/
|
||||
@AfterThrowing(value = "logPointCut()", throwing = "e")
|
||||
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
|
||||
handleLog(joinPoint, e, null);
|
||||
}
|
||||
|
||||
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
|
||||
try {
|
||||
// 获得注解
|
||||
Log controllerLog = getAnnotationLog(joinPoint);
|
||||
if (controllerLog == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前的用户
|
||||
IoTUser iotUser = ioTUserService.selectUserByUnionId(SecurityUtils.getUnionId());
|
||||
|
||||
// *========数据库日志=========*//
|
||||
SysOperLog operLog = new SysOperLog();
|
||||
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
||||
// 请求的地址
|
||||
String ip = ServletUtils.getClientIP();
|
||||
operLog.setOperIp(ip);
|
||||
operLog.setOperLocation(LocationUtils.getLocationByIp(ip));
|
||||
// 返回参数
|
||||
operLog.setJsonResult(JSONUtil.toJsonStr(jsonResult));
|
||||
|
||||
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
|
||||
if (iotUser != null) {
|
||||
operLog.setOperName(iotUser.getUsername());
|
||||
}
|
||||
|
||||
if (e != null) {
|
||||
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
||||
operLog.setErrorMsg(StrUtil.sub(e.getMessage(), 0, 2000));
|
||||
}
|
||||
// 设置方法名称
|
||||
String className = joinPoint.getTarget().getClass().getName();
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
operLog.setMethod(className + "." + methodName + "()");
|
||||
// 设置请求方式
|
||||
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
|
||||
// 处理设置注解上的参数
|
||||
getControllerMethodDescription(joinPoint, controllerLog, operLog);
|
||||
// 保存数据库
|
||||
SpringUtils.getBean(AdminLogService.class).recordOper(operLog);
|
||||
} catch (Exception exp) {
|
||||
// 记录本地异常日志
|
||||
log.error("==前置通知异常==");
|
||||
log.error("异常信息", exp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注解中对方法的描述信息 用于Controller层注解
|
||||
*
|
||||
* @param log 日志
|
||||
* @param operLog 操作日志
|
||||
*/
|
||||
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog)
|
||||
throws Exception {
|
||||
// 设置action动作
|
||||
operLog.setBusinessType(log.businessType().ordinal());
|
||||
// 设置标题
|
||||
operLog.setTitle(log.title());
|
||||
// 设置操作人类别
|
||||
operLog.setOperatorType(log.operatorType().ordinal());
|
||||
// 是否需要保存request,参数和值
|
||||
if (log.isSaveRequestData()) {
|
||||
// 获取参数的信息,传入到数据库中。
|
||||
setRequestValue(joinPoint, operLog);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的参数,放到log中
|
||||
*
|
||||
* @param operLog 操作日志
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception {
|
||||
String requestMethod = operLog.getRequestMethod();
|
||||
if (HttpMethod.PUT.name().equals(requestMethod)
|
||||
|| HttpMethod.POST.name().equals(requestMethod)) {
|
||||
String params = argsArrayToString(joinPoint.getArgs());
|
||||
operLog.setOperParam(StrUtil.sub(params, 0, 2000));
|
||||
} else {
|
||||
Map<?, ?> paramsMap =
|
||||
(Map<?, ?>)
|
||||
ServletUtils.getRequest()
|
||||
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
operLog.setOperParam(StrUtil.sub(paramsMap.toString(), 0, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否存在注解,如果存在就获取 */
|
||||
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
|
||||
Signature signature = joinPoint.getSignature();
|
||||
MethodSignature methodSignature = (MethodSignature) signature;
|
||||
Method method = methodSignature.getMethod();
|
||||
|
||||
if (method != null) {
|
||||
return method.getAnnotation(Log.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 参数拼装 */
|
||||
private String argsArrayToString(Object[] paramsArray) {
|
||||
StringBuilder params = new StringBuilder();
|
||||
if (paramsArray != null && paramsArray.length > 0) {
|
||||
for (Object o : paramsArray) {
|
||||
if (Validator.isNotNull(o) && !isFilterObject(o)) {
|
||||
params.append(JSONUtil.toJsonStr(o)).append(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
return params.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要过滤的对象。
|
||||
*
|
||||
* @param o 对象信息。
|
||||
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean isFilterObject(final Object o) {
|
||||
Class<?> clazz = o.getClass();
|
||||
if (clazz.isArray()) {
|
||||
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||
Collection collection = (Collection) o;
|
||||
for (Object value : collection) {
|
||||
return value instanceof MultipartFile;
|
||||
}
|
||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||
Map map = (Map) o;
|
||||
for (Object value : map.entrySet()) {
|
||||
Map.Entry entry = (Map.Entry) value;
|
||||
return entry.getValue() instanceof MultipartFile;
|
||||
}
|
||||
}
|
||||
return o instanceof MultipartFile
|
||||
|| o instanceof HttpServletRequest
|
||||
|| o instanceof HttpServletResponse
|
||||
|| o instanceof BindingResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.common.service;
|
||||
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
|
||||
public interface BaseService {
|
||||
|
||||
IoTUser queryIotUser(String unionId);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.common.service;
|
||||
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.security.service.IoTUserService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
|
||||
public class BaseServiceImpl implements BaseService {
|
||||
|
||||
@Resource private IoTUserService ioTUserService;
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = "user_parent_entity", key = "''+#unionId", unless = "#result == null")
|
||||
public IoTUser queryIotUser(String unionId) {
|
||||
IoTUser iotUser = ioTUserService.selectUserByUnionId(unionId);
|
||||
if (iotUser.getParentUnionId() != null) {
|
||||
iotUser = ioTUserService.selectUserByUnionId(iotUser.getParentUnionId());
|
||||
return iotUser;
|
||||
}
|
||||
return iotUser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.common.utils;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.universal.common.constant.Constants;
|
||||
import cn.universal.persistence.entity.admin.SysDictData;
|
||||
import java.util.List;
|
||||
|
||||
/** 字典工具类 */
|
||||
public class DictUtils {
|
||||
|
||||
/** 分隔符 */
|
||||
public static final String SEPARATOR = ",";
|
||||
|
||||
/**
|
||||
* 设置字典缓存
|
||||
*
|
||||
* @param key 参数键
|
||||
* @param dictDatas 字典数据列表
|
||||
*/
|
||||
public static void setDictCache(String key, List<SysDictData> dictDatas) {
|
||||
// SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典缓存
|
||||
*
|
||||
* @param key 参数键
|
||||
* @return dictDatas 字典数据列表
|
||||
*/
|
||||
public static List<SysDictData> getDictCache(String key) {
|
||||
// Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
|
||||
// if (Validator.isNotNull(cacheObj)) {
|
||||
// List<SysDictData> dictDatas = (List<SysDictData>) cacheObj;
|
||||
// return dictDatas;
|
||||
// }
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典类型和字典值获取字典标签
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @param dictValue 字典值
|
||||
* @return 字典标签
|
||||
*/
|
||||
public static String getDictLabel(String dictType, String dictValue) {
|
||||
return getDictLabel(dictType, dictValue, SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典类型和字典标签获取字典值
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @param dictLabel 字典标签
|
||||
* @return 字典值
|
||||
*/
|
||||
public static String getDictValue(String dictType, String dictLabel) {
|
||||
return getDictValue(dictType, dictLabel, SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典类型和字典值获取字典标签
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @param dictValue 字典值
|
||||
* @param separator 分隔符
|
||||
* @return 字典标签
|
||||
*/
|
||||
public static String getDictLabel(String dictType, String dictValue, String separator) {
|
||||
StringBuilder propertyString = new StringBuilder();
|
||||
List<SysDictData> datas = getDictCache(dictType);
|
||||
|
||||
if (StrUtil.containsAny(dictValue, separator) && CollUtil.isNotEmpty(datas)) {
|
||||
for (SysDictData dict : datas) {
|
||||
for (String value : dictValue.split(separator)) {
|
||||
if (value.equals(dict.getDictValue())) {
|
||||
propertyString.append(dict.getDictLabel() + separator);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (SysDictData dict : datas) {
|
||||
if (dictValue.equals(dict.getDictValue())) {
|
||||
return dict.getDictLabel();
|
||||
}
|
||||
}
|
||||
}
|
||||
return StrUtil.strip(propertyString.toString(), null, separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典类型和字典标签获取字典值
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @param dictLabel 字典标签
|
||||
* @param separator 分隔符
|
||||
* @return 字典值
|
||||
*/
|
||||
public static String getDictValue(String dictType, String dictLabel, String separator) {
|
||||
StringBuilder propertyString = new StringBuilder();
|
||||
List<SysDictData> datas = getDictCache(dictType);
|
||||
|
||||
if (StrUtil.containsAny(dictLabel, separator) && CollUtil.isNotEmpty(datas)) {
|
||||
for (SysDictData dict : datas) {
|
||||
for (String label : dictLabel.split(separator)) {
|
||||
if (label.equals(dict.getDictLabel())) {
|
||||
propertyString.append(dict.getDictValue() + separator);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (SysDictData dict : datas) {
|
||||
if (dictLabel.equals(dict.getDictLabel())) {
|
||||
return dict.getDictValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return StrUtil.strip(propertyString.toString(), null, separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定字典缓存
|
||||
*
|
||||
* @param key 字典键
|
||||
*/
|
||||
public static void removeDictCache(String key) {
|
||||
// SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key));
|
||||
}
|
||||
|
||||
/** 清空字典缓存 */
|
||||
public static void clearDictCache() {
|
||||
// Collection<String> keys = SpringUtils.getBean(RedisCache.class)
|
||||
// .keys(Constants.SYS_DICT_KEY + "*");
|
||||
// SpringUtils.getBean(RedisCache.class).deleteObject(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置cache key
|
||||
*
|
||||
* @param configKey 参数键
|
||||
* @return 缓存键key
|
||||
*/
|
||||
public static String getCacheKey(String configKey) {
|
||||
return Constants.SYS_DICT_KEY + configKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.common.utils;
|
||||
|
||||
import cn.universal.common.config.UnivUPloadConfig;
|
||||
import cn.universal.common.constant.Constants;
|
||||
import cn.universal.common.utils.StringUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Arrays;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** 图片工具类 */
|
||||
public class ImageUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
|
||||
|
||||
public static byte[] getImage(String imagePath) {
|
||||
InputStream is = getFile(imagePath);
|
||||
try {
|
||||
return IOUtils.toByteArray(is);
|
||||
} catch (Exception e) {
|
||||
log.error("加载异常", e);
|
||||
return null;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
}
|
||||
}
|
||||
|
||||
public static InputStream getFile(String imagePath) {
|
||||
try {
|
||||
byte[] result = readFile(imagePath);
|
||||
result = Arrays.copyOf(result, result.length);
|
||||
return new ByteArrayInputStream(result);
|
||||
} catch (Exception e) {
|
||||
log.error("图片异常", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文件为字节数据
|
||||
*
|
||||
* @param url 地址
|
||||
* @return 字节数据
|
||||
*/
|
||||
public static byte[] readFile(String url) {
|
||||
InputStream in = null;
|
||||
ByteArrayOutputStream baos = null;
|
||||
try {
|
||||
if (url.startsWith("http")) {
|
||||
// 网络地址
|
||||
URL urlObj = new URL(url);
|
||||
URLConnection urlConnection = urlObj.openConnection();
|
||||
urlConnection.setConnectTimeout(30 * 1000);
|
||||
urlConnection.setReadTimeout(60 * 1000);
|
||||
urlConnection.setDoInput(true);
|
||||
in = urlConnection.getInputStream();
|
||||
} else {
|
||||
// 本机地址
|
||||
String localPath = UnivUPloadConfig.getProfile();
|
||||
String downloadPath =
|
||||
localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX);
|
||||
in = new FileInputStream(downloadPath);
|
||||
}
|
||||
return IOUtils.toByteArray(in);
|
||||
} catch (Exception e) {
|
||||
log.error("获取文件路径异常", e);
|
||||
return null;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(in);
|
||||
IOUtils.closeQuietly(baos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package cn.universal.admin.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 连接配置管理 复用现有配置,减少重复配置项 */
|
||||
@Data
|
||||
@Component
|
||||
public class ConnectionConfig {
|
||||
|
||||
// TCP配置 - 复用现有配置
|
||||
@Value("${tcp.protocol.sni.host.out:#productKey.tcp.nexiot.xyz}")
|
||||
private String tcpSniHostTemplate;
|
||||
|
||||
@Value("${tcp.protocol.sni.port.out:38883}")
|
||||
private Integer tcpSniPort;
|
||||
|
||||
@Value("${connection.tcp.connection-type:TLS SNI方式}")
|
||||
private String tcpConnectionType;
|
||||
|
||||
@Value("${connection.tcp.description:}")
|
||||
private String tcpDescription;
|
||||
|
||||
// MQTT配置 - 复用现有配置
|
||||
@Value("${mqtt.cfg.host.out:tcp://localhost:1883}")
|
||||
private String mqttHostOut;
|
||||
|
||||
@Value("${mqtt.cfg.host:tcp://localhost:1883}")
|
||||
private String mqttHost;
|
||||
|
||||
@Value("${mqtt.cfg.clientIdPrefix:univ_iot_test}")
|
||||
private String mqttClientIdPrefix;
|
||||
|
||||
@Value("${connection.mqtt.connection-type:内置MQTT Broker}")
|
||||
private String mqttConnectionType;
|
||||
|
||||
@Value("${connection.mqtt.description:}")
|
||||
private String mqttDescription;
|
||||
|
||||
// MQTT主题配置 - 直接在代码中定义默认值,避免Spring占位符解析问题
|
||||
@Value("${connection.mqtt.default-topics.thing-topics.property-up:}")
|
||||
private String mqttThingPropertyUp;
|
||||
|
||||
@Value("${connection.mqtt.default-topics.thing-topics.event-up:}")
|
||||
private String mqttThingEventUp;
|
||||
|
||||
@Value("${connection.mqtt.default-topics.thing-topics.command-down:}")
|
||||
private String mqttThingCommandDown;
|
||||
|
||||
@Value("${connection.mqtt.default-topics.passthrough-topics.data-up:}")
|
||||
private String mqttPassthroughDataUp;
|
||||
|
||||
@Value("${connection.mqtt.default-topics.passthrough-topics.command-down:}")
|
||||
private String mqttPassthroughCommandDown;
|
||||
|
||||
// 默认主题模板常量
|
||||
private static final String DEFAULT_THING_PROPERTY_UP =
|
||||
"$thing/up/property/{productKey}/{deviceId}";
|
||||
private static final String DEFAULT_THING_EVENT_UP = "$thing/up/event/{productKey}/{deviceId}";
|
||||
private static final String DEFAULT_THING_COMMAND_DOWN = "$thing/down/{productKey}/{deviceId}";
|
||||
private static final String DEFAULT_PASSTHROUGH_DATA_UP = "$thing/up/{productKey}/{deviceId}";
|
||||
private static final String DEFAULT_PASSTHROUGH_COMMAND_DOWN =
|
||||
"$thing/down/{productKey}/{deviceId}";
|
||||
|
||||
// UDP配置
|
||||
@Value("${connection.udp.connection-type:}")
|
||||
private String udpConnectionType;
|
||||
|
||||
@Value("${connection.udp.description:}")
|
||||
private String udpDescription;
|
||||
|
||||
@Value("${udp.cfg.port:1884}")
|
||||
private Integer udpPort;
|
||||
|
||||
/** 获取TCP主机地址 */
|
||||
public String getTcpHost(String productKey) {
|
||||
return tcpSniHostTemplate.replace("#productKey", productKey);
|
||||
}
|
||||
|
||||
/** 获取MQTT主机地址(优先使用外部配置) */
|
||||
public String getMqttHost() {
|
||||
return mqttHostOut != null && !mqttHostOut.isEmpty() ? mqttHostOut : mqttHost;
|
||||
}
|
||||
|
||||
/** 从主机地址中提取端口 */
|
||||
public Integer extractPortFromHost(String host) {
|
||||
if (host == null || host.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (host.contains(":")) {
|
||||
String[] parts = host.split(":");
|
||||
if (parts.length >= 2) {
|
||||
try {
|
||||
return Integer.parseInt(parts[parts.length - 1]);
|
||||
} catch (NumberFormatException e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 获取MQTT物模型属性上报主题 */
|
||||
public String getMqttThingPropertyUp() {
|
||||
return mqttThingPropertyUp.isEmpty() ? DEFAULT_THING_PROPERTY_UP : mqttThingPropertyUp;
|
||||
}
|
||||
|
||||
/** 获取MQTT物模型事件上报主题 */
|
||||
public String getMqttThingEventUp() {
|
||||
return mqttThingEventUp.isEmpty() ? DEFAULT_THING_EVENT_UP : mqttThingEventUp;
|
||||
}
|
||||
|
||||
/** 获取MQTT物模型指令下发主题 */
|
||||
public String getMqttThingCommandDown() {
|
||||
return mqttThingCommandDown.isEmpty() ? DEFAULT_THING_COMMAND_DOWN : mqttThingCommandDown;
|
||||
}
|
||||
|
||||
/** 获取MQTT透传数据上报主题 */
|
||||
public String getMqttPassthroughDataUp() {
|
||||
return mqttPassthroughDataUp.isEmpty() ? DEFAULT_PASSTHROUGH_DATA_UP : mqttPassthroughDataUp;
|
||||
}
|
||||
|
||||
/** 获取MQTT透传指令下发主题 */
|
||||
public String getMqttPassthroughCommandDown() {
|
||||
return mqttPassthroughCommandDown.isEmpty()
|
||||
? DEFAULT_PASSTHROUGH_COMMAND_DOWN
|
||||
: mqttPassthroughCommandDown;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/** 读取代码生成相关配置 @Author ruoyi */
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "gen")
|
||||
@PropertySource(value = {"classpath:generator.yml"})
|
||||
@Profile({"beta"})
|
||||
public class GenConfig {
|
||||
|
||||
/** 作者 */
|
||||
public static String Author;
|
||||
|
||||
/** 生成包路径 */
|
||||
public static String packageName;
|
||||
|
||||
/** 自动去除表前缀,默认是false */
|
||||
public static boolean autoRemovePre;
|
||||
|
||||
/** 表前缀(类名不会包含表前缀) */
|
||||
public static String tablePrefix;
|
||||
|
||||
public static String getAuthor() {
|
||||
return Author;
|
||||
}
|
||||
|
||||
@Value("${Author}")
|
||||
public void setAuthor(String Author) {
|
||||
GenConfig.Author = Author;
|
||||
}
|
||||
|
||||
public static String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
@Value("${packageName}")
|
||||
public void setPackageName(String packageName) {
|
||||
GenConfig.packageName = packageName;
|
||||
}
|
||||
|
||||
public static boolean getAutoRemovePre() {
|
||||
return autoRemovePre;
|
||||
}
|
||||
|
||||
@Value("${autoRemovePre}")
|
||||
public void setAutoRemovePre(boolean autoRemovePre) {
|
||||
GenConfig.autoRemovePre = autoRemovePre;
|
||||
}
|
||||
|
||||
public static String getTablePrefix() {
|
||||
return tablePrefix;
|
||||
}
|
||||
|
||||
@Value("${tablePrefix}")
|
||||
public void setTablePrefix(String tablePrefix) {
|
||||
GenConfig.tablePrefix = tablePrefix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.constant;
|
||||
|
||||
/** 代码生成通用常量 @Author ruoyi */
|
||||
public class GenConstants {
|
||||
|
||||
/** 单表(增删改查) */
|
||||
public static final String TPL_CRUD = "crud";
|
||||
|
||||
/** 树表(增删改查) */
|
||||
public static final String TPL_TREE = "tree";
|
||||
|
||||
/** 主子表(增删改查) */
|
||||
public static final String TPL_SUB = "sub";
|
||||
|
||||
/** 树编码字段 */
|
||||
public static final String TREE_CODE = "treeCode";
|
||||
|
||||
/** 树父编码字段 */
|
||||
public static final String TREE_PARENT_CODE = "treeParentCode";
|
||||
|
||||
/** 树名称字段 */
|
||||
public static final String TREE_NAME = "treeName";
|
||||
|
||||
/** 上级菜单ID字段 */
|
||||
public static final String PARENT_MENU_ID = "parentMenuId";
|
||||
|
||||
/** 上级菜单名称字段 */
|
||||
public static final String PARENT_MENU_NAME = "parentMenuName";
|
||||
|
||||
/** 数据库字符串类型 */
|
||||
public static final String[] COLUMNTYPE_STR = {"char", "varchar", "nvarchar", "varchar2"};
|
||||
|
||||
/** 数据库文本类型 */
|
||||
public static final String[] COLUMNTYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext"};
|
||||
|
||||
/** 数据库时间类型 */
|
||||
public static final String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp"};
|
||||
|
||||
/** 数据库数字类型 */
|
||||
public static final String[] COLUMNTYPE_NUMBER = {
|
||||
"tinyint",
|
||||
"smallint",
|
||||
"mediumint",
|
||||
"int",
|
||||
"number",
|
||||
"integer",
|
||||
"bit",
|
||||
"bigint",
|
||||
"float",
|
||||
"double",
|
||||
"decimal"
|
||||
};
|
||||
|
||||
/** 页面不需要编辑字段 */
|
||||
public static final String[] COLUMNNAME_NOT_EDIT = {"id", "create_by", "create_time", "del_flag"};
|
||||
|
||||
/** 页面不需要显示的列表字段 */
|
||||
public static final String[] COLUMNNAME_NOT_LIST = {
|
||||
"id", "create_by", "create_time", "del_flag", "update_by", "update_time"
|
||||
};
|
||||
|
||||
/** 页面不需要查询字段 */
|
||||
public static final String[] COLUMNNAME_NOT_QUERY = {
|
||||
"id", "create_by", "create_time", "del_flag", "update_by", "update_time", "remark"
|
||||
};
|
||||
|
||||
/** Entity基类字段 */
|
||||
public static final String[] BASE_ENTITY = {
|
||||
"createBy", "createTime", "updateBy", "updateTime", "remark"
|
||||
};
|
||||
|
||||
/** Tree基类字段 */
|
||||
public static final String[] TREE_ENTITY = {
|
||||
"parentName", "parentId", "orderNum", "ancestors", "children"
|
||||
};
|
||||
|
||||
/** 文本框 */
|
||||
public static final String HTML_INPUT = "input";
|
||||
|
||||
/** 文本域 */
|
||||
public static final String HTML_TEXTAREA = "textarea";
|
||||
|
||||
/** 下拉框 */
|
||||
public static final String HTML_SELECT = "select";
|
||||
|
||||
/** 单选框 */
|
||||
public static final String HTML_RADIO = "radio";
|
||||
|
||||
/** 复选框 */
|
||||
public static final String HTML_CHECKBOX = "checkbox";
|
||||
|
||||
/** 日期控件 */
|
||||
public static final String HTML_DATETIME = "datetime";
|
||||
|
||||
/** 图片上传控件 */
|
||||
public static final String HTML_IMAGE_UPLOAD = "imageUpload";
|
||||
|
||||
/** 文件上传控件 */
|
||||
public static final String HTML_FILE_UPLOAD = "fileUpload";
|
||||
|
||||
/** 富文本控件 */
|
||||
public static final String HTML_EDITOR = "editor";
|
||||
|
||||
/** 字符串类型 */
|
||||
public static final String TYPE_STRING = "String";
|
||||
|
||||
/** 整型 */
|
||||
public static final String TYPE_INTEGER = "Integer";
|
||||
|
||||
/** 长整型 */
|
||||
public static final String TYPE_LONG = "Long";
|
||||
|
||||
/** 浮点型 */
|
||||
public static final String TYPE_DOUBLE = "Double";
|
||||
|
||||
/** 高精度计算类型 */
|
||||
public static final String TYPE_BIGDECIMAL = "BigDecimal";
|
||||
|
||||
/** 时间类型 */
|
||||
public static final String TYPE_DATE = "Date";
|
||||
|
||||
/** 模糊查询 */
|
||||
public static final String QUERY_LIKE = "LIKE";
|
||||
|
||||
/** 需要 */
|
||||
public static final String REQUIRE = "1";
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.domain;
|
||||
|
||||
import cn.universal.admin.generator.constant.GenConstants;
|
||||
import cn.universal.common.domain.BaseEntity;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/** 业务表 gen_table @Author ruoyi */
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class GenTable extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 编号 */
|
||||
private Long tableId;
|
||||
|
||||
/** 表名称 */
|
||||
@NotBlank(message = "表名称不能为空")
|
||||
private String tableName;
|
||||
|
||||
/** 表描述 */
|
||||
@NotBlank(message = "表描述不能为空")
|
||||
private String tableComment;
|
||||
|
||||
/** 关联父表的表名 */
|
||||
private String subTableName;
|
||||
|
||||
/** 本表关联父表的外键名 */
|
||||
private String subTableFkName;
|
||||
|
||||
/** 实体类名称(首字母大写) */
|
||||
@NotBlank(message = "实体类名称不能为空")
|
||||
private String className;
|
||||
|
||||
/** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */
|
||||
private String tplCategory;
|
||||
|
||||
/** 生成包路径 */
|
||||
@NotBlank(message = "生成包路径不能为空")
|
||||
private String packageName;
|
||||
|
||||
/** 生成模块名 */
|
||||
@NotBlank(message = "生成模块名不能为空")
|
||||
private String moduleName;
|
||||
|
||||
/** 生成业务名 */
|
||||
@NotBlank(message = "生成业务名不能为空")
|
||||
private String businessName;
|
||||
|
||||
/** 生成功能名 */
|
||||
@NotBlank(message = "生成功能名不能为空")
|
||||
private String functionName;
|
||||
|
||||
/** 生成作者 */
|
||||
@NotBlank(message = "作者不能为空")
|
||||
private String functionAuthor;
|
||||
|
||||
/** 生成代码方式(0zip压缩包 1自定义路径) */
|
||||
private String genType;
|
||||
|
||||
/** 生成路径(不填默认项目路径) */
|
||||
private String genPath;
|
||||
|
||||
/** 主键信息 */
|
||||
private GenTableColumn pkColumn;
|
||||
|
||||
/** 子表信息 */
|
||||
private GenTable subTable;
|
||||
|
||||
/** 表列信息 */
|
||||
@Valid private List<GenTableColumn> columns;
|
||||
|
||||
/** 其它生成选项 */
|
||||
private String options;
|
||||
|
||||
/** 树编码字段 */
|
||||
private String treeCode;
|
||||
|
||||
/** 树父编码字段 */
|
||||
private String treeParentCode;
|
||||
|
||||
/** 树名称字段 */
|
||||
private String treeName;
|
||||
|
||||
/** 上级菜单ID字段 */
|
||||
private String parentMenuId;
|
||||
|
||||
/** 上级菜单名称字段 */
|
||||
private String parentMenuName;
|
||||
|
||||
public boolean isSub() {
|
||||
return isSub(this.tplCategory);
|
||||
}
|
||||
|
||||
public static boolean isSub(String tplCategory) {
|
||||
return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory);
|
||||
}
|
||||
|
||||
public boolean isTree() {
|
||||
return isTree(this.tplCategory);
|
||||
}
|
||||
|
||||
public static boolean isTree(String tplCategory) {
|
||||
return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory);
|
||||
}
|
||||
|
||||
public boolean isCrud() {
|
||||
return isCrud(this.tplCategory);
|
||||
}
|
||||
|
||||
public static boolean isCrud(String tplCategory) {
|
||||
return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory);
|
||||
}
|
||||
|
||||
public boolean isSuperColumn(String javaField) {
|
||||
return isSuperColumn(this.tplCategory, javaField);
|
||||
}
|
||||
|
||||
public static boolean isSuperColumn(String tplCategory, String javaField) {
|
||||
if (isTree(tplCategory)) {
|
||||
return StringUtils.equalsAnyIgnoreCase(
|
||||
javaField, ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY));
|
||||
}
|
||||
return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.domain;
|
||||
|
||||
import cn.universal.common.domain.BaseEntity;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/** 代码生成业务字段表 gen_table_column @Author ruoyi */
|
||||
public class GenTableColumn extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 编号 */
|
||||
private Long columnId;
|
||||
|
||||
/** 归属表编号 */
|
||||
private Long tableId;
|
||||
|
||||
/** 列名称 */
|
||||
private String columnName;
|
||||
|
||||
/** 列描述 */
|
||||
private String columnComment;
|
||||
|
||||
/** 列类型 */
|
||||
private String columnType;
|
||||
|
||||
/** JAVA类型 */
|
||||
private String javaType;
|
||||
|
||||
/** JAVA字段名 */
|
||||
@NotBlank(message = "Java属性不能为空")
|
||||
private String javaField;
|
||||
|
||||
/** 是否主键(1是) */
|
||||
private String isPk;
|
||||
|
||||
/** 是否自增(1是) */
|
||||
private String isIncrement;
|
||||
|
||||
/** 是否必填(1是) */
|
||||
private String isRequired;
|
||||
|
||||
/** 是否为插入字段(1是) */
|
||||
private String isInsert;
|
||||
|
||||
/** 是否编辑字段(1是) */
|
||||
private String isEdit;
|
||||
|
||||
/** 是否列表字段(1是) */
|
||||
private String isList;
|
||||
|
||||
/** 是否查询字段(1是) */
|
||||
private String isQuery;
|
||||
|
||||
/** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */
|
||||
private String queryType;
|
||||
|
||||
/**
|
||||
* 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件)
|
||||
*/
|
||||
private String htmlType;
|
||||
|
||||
/** 字典类型 */
|
||||
private String dictType;
|
||||
|
||||
/** 排序 */
|
||||
private Integer sort;
|
||||
|
||||
public void setColumnId(Long columnId) {
|
||||
this.columnId = columnId;
|
||||
}
|
||||
|
||||
public Long getColumnId() {
|
||||
return columnId;
|
||||
}
|
||||
|
||||
public void setTableId(Long tableId) {
|
||||
this.tableId = tableId;
|
||||
}
|
||||
|
||||
public Long getTableId() {
|
||||
return tableId;
|
||||
}
|
||||
|
||||
public void setColumnName(String columnName) {
|
||||
this.columnName = columnName;
|
||||
}
|
||||
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
public void setColumnComment(String columnComment) {
|
||||
this.columnComment = columnComment;
|
||||
}
|
||||
|
||||
public String getColumnComment() {
|
||||
return columnComment;
|
||||
}
|
||||
|
||||
public void setColumnType(String columnType) {
|
||||
this.columnType = columnType;
|
||||
}
|
||||
|
||||
public String getColumnType() {
|
||||
return columnType;
|
||||
}
|
||||
|
||||
public void setJavaType(String javaType) {
|
||||
this.javaType = javaType;
|
||||
}
|
||||
|
||||
public String getJavaType() {
|
||||
return javaType;
|
||||
}
|
||||
|
||||
public void setJavaField(String javaField) {
|
||||
this.javaField = javaField;
|
||||
}
|
||||
|
||||
public String getJavaField() {
|
||||
return javaField;
|
||||
}
|
||||
|
||||
public String getCapJavaField() {
|
||||
return StringUtils.capitalize(javaField);
|
||||
}
|
||||
|
||||
public void setIsPk(String isPk) {
|
||||
this.isPk = isPk;
|
||||
}
|
||||
|
||||
public String getIsPk() {
|
||||
return isPk;
|
||||
}
|
||||
|
||||
public boolean isPk() {
|
||||
return isPk(this.isPk);
|
||||
}
|
||||
|
||||
public boolean isPk(String isPk) {
|
||||
return isPk != null && StringUtils.equals("1", isPk);
|
||||
}
|
||||
|
||||
public String getIsIncrement() {
|
||||
return isIncrement;
|
||||
}
|
||||
|
||||
public void setIsIncrement(String isIncrement) {
|
||||
this.isIncrement = isIncrement;
|
||||
}
|
||||
|
||||
public boolean isIncrement() {
|
||||
return isIncrement(this.isIncrement);
|
||||
}
|
||||
|
||||
public boolean isIncrement(String isIncrement) {
|
||||
return isIncrement != null && StringUtils.equals("1", isIncrement);
|
||||
}
|
||||
|
||||
public void setIsRequired(String isRequired) {
|
||||
this.isRequired = isRequired;
|
||||
}
|
||||
|
||||
public String getIsRequired() {
|
||||
return isRequired;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return isRequired(this.isRequired);
|
||||
}
|
||||
|
||||
public boolean isRequired(String isRequired) {
|
||||
return isRequired != null && StringUtils.equals("1", isRequired);
|
||||
}
|
||||
|
||||
public void setIsInsert(String isInsert) {
|
||||
this.isInsert = isInsert;
|
||||
}
|
||||
|
||||
public String getIsInsert() {
|
||||
return isInsert;
|
||||
}
|
||||
|
||||
public boolean isInsert() {
|
||||
return isInsert(this.isInsert);
|
||||
}
|
||||
|
||||
public boolean isInsert(String isInsert) {
|
||||
return isInsert != null && StringUtils.equals("1", isInsert);
|
||||
}
|
||||
|
||||
public void setIsEdit(String isEdit) {
|
||||
this.isEdit = isEdit;
|
||||
}
|
||||
|
||||
public String getIsEdit() {
|
||||
return isEdit;
|
||||
}
|
||||
|
||||
public boolean isEdit() {
|
||||
return isInsert(this.isEdit);
|
||||
}
|
||||
|
||||
public boolean isEdit(String isEdit) {
|
||||
return isEdit != null && StringUtils.equals("1", isEdit);
|
||||
}
|
||||
|
||||
public void setIsList(String isList) {
|
||||
this.isList = isList;
|
||||
}
|
||||
|
||||
public String getIsList() {
|
||||
return isList;
|
||||
}
|
||||
|
||||
public boolean isList() {
|
||||
return isList(this.isList);
|
||||
}
|
||||
|
||||
public boolean isList(String isList) {
|
||||
return isList != null && StringUtils.equals("1", isList);
|
||||
}
|
||||
|
||||
public void setIsQuery(String isQuery) {
|
||||
this.isQuery = isQuery;
|
||||
}
|
||||
|
||||
public String getIsQuery() {
|
||||
return isQuery;
|
||||
}
|
||||
|
||||
public boolean isQuery() {
|
||||
return isQuery(this.isQuery);
|
||||
}
|
||||
|
||||
public boolean isQuery(String isQuery) {
|
||||
return isQuery != null && StringUtils.equals("1", isQuery);
|
||||
}
|
||||
|
||||
public void setQueryType(String queryType) {
|
||||
this.queryType = queryType;
|
||||
}
|
||||
|
||||
public String getQueryType() {
|
||||
return queryType;
|
||||
}
|
||||
|
||||
public String getHtmlType() {
|
||||
return htmlType;
|
||||
}
|
||||
|
||||
public void setHtmlType(String htmlType) {
|
||||
this.htmlType = htmlType;
|
||||
}
|
||||
|
||||
public void setDictType(String dictType) {
|
||||
this.dictType = dictType;
|
||||
}
|
||||
|
||||
public String getDictType() {
|
||||
return dictType;
|
||||
}
|
||||
|
||||
public void setSort(Integer sort) {
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
public Integer getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
public boolean isSuperColumn() {
|
||||
return isSuperColumn(this.javaField);
|
||||
}
|
||||
|
||||
public static boolean isSuperColumn(String javaField) {
|
||||
return StringUtils.equalsAnyIgnoreCase(
|
||||
javaField,
|
||||
// BaseEntity
|
||||
"createBy",
|
||||
"createTime",
|
||||
"updateBy",
|
||||
"updateTime",
|
||||
"remark",
|
||||
// TreeEntity
|
||||
"parentName",
|
||||
"parentId",
|
||||
"orderNum",
|
||||
"ancestors");
|
||||
}
|
||||
|
||||
public boolean isUsableColumn() {
|
||||
return isUsableColumn(javaField);
|
||||
}
|
||||
|
||||
public static boolean isUsableColumn(String javaField) {
|
||||
// isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单
|
||||
return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark");
|
||||
}
|
||||
|
||||
public String readConverterExp() {
|
||||
String remarks = StringUtils.substringBetween(this.columnComment, "(", ")");
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (StringUtils.isNotEmpty(remarks)) {
|
||||
for (String value : remarks.split(" ")) {
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
Object startStr = value.subSequence(0, 1);
|
||||
String endStr = value.substring(1);
|
||||
sb.append("").append(startStr).append("=").append(endStr).append(",");
|
||||
}
|
||||
}
|
||||
return sb.deleteCharAt(sb.length() - 1).toString();
|
||||
} else {
|
||||
return this.columnComment;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.mapper;
|
||||
|
||||
import cn.universal.admin.generator.domain.GenTableColumn;
|
||||
import java.util.List;
|
||||
|
||||
/** 业务字段 数据层 @Author ruoyi */
|
||||
public interface GenTableColumnMapper {
|
||||
|
||||
/**
|
||||
* 根据表名称查询列信息
|
||||
*
|
||||
* @param tableName 表名称
|
||||
* @return 列信息
|
||||
*/
|
||||
List<GenTableColumn> selectDbTableColumnsByName(String tableName);
|
||||
|
||||
/**
|
||||
* 查询业务字段列表
|
||||
*
|
||||
* @param tableId 业务字段编号
|
||||
* @return 业务字段集合
|
||||
*/
|
||||
List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
|
||||
|
||||
/**
|
||||
* 新增业务字段
|
||||
*
|
||||
* @param genTableColumn 业务字段信息
|
||||
* @return 结果
|
||||
*/
|
||||
int insertGenTableColumn(GenTableColumn genTableColumn);
|
||||
|
||||
/**
|
||||
* 修改业务字段
|
||||
*
|
||||
* @param genTableColumn 业务字段信息
|
||||
* @return 结果
|
||||
*/
|
||||
int updateGenTableColumn(GenTableColumn genTableColumn);
|
||||
|
||||
/**
|
||||
* 删除业务字段
|
||||
*
|
||||
* @param genTableColumns 列数据
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteGenTableColumns(List<GenTableColumn> genTableColumns);
|
||||
|
||||
/**
|
||||
* 批量删除业务字段
|
||||
*
|
||||
* @param ids 需要删除的数据ID
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteGenTableColumnByIds(Long[] ids);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.mapper;
|
||||
|
||||
import cn.universal.admin.generator.domain.GenTable;
|
||||
import java.util.List;
|
||||
|
||||
/** 业务 数据层 @Author ruoyi */
|
||||
public interface GenTableMapper {
|
||||
|
||||
/**
|
||||
* 查询业务列表
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 业务集合
|
||||
*/
|
||||
List<GenTable> selectGenTableList(GenTable genTable);
|
||||
|
||||
/**
|
||||
* 查询据库列表
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 数据库表集合
|
||||
*/
|
||||
List<GenTable> selectDbTableList(GenTable genTable);
|
||||
|
||||
/**
|
||||
* 查询据库列表
|
||||
*
|
||||
* @param tableNames 表名称组
|
||||
* @return 数据库表集合
|
||||
*/
|
||||
List<GenTable> selectDbTableListByNames(String[] tableNames);
|
||||
|
||||
/**
|
||||
* 查询所有表信息
|
||||
*
|
||||
* @return 表信息集合
|
||||
*/
|
||||
List<GenTable> selectGenTableAll();
|
||||
|
||||
/**
|
||||
* 查询表ID业务信息
|
||||
*
|
||||
* @param id 业务ID
|
||||
* @return 业务信息
|
||||
*/
|
||||
GenTable selectGenTableById(Long id);
|
||||
|
||||
/**
|
||||
* 查询表名称业务信息
|
||||
*
|
||||
* @param tableName 表名称
|
||||
* @return 业务信息
|
||||
*/
|
||||
GenTable selectGenTableByName(String tableName);
|
||||
|
||||
/**
|
||||
* 新增业务
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 结果
|
||||
*/
|
||||
int insertGenTable(GenTable genTable);
|
||||
|
||||
/**
|
||||
* 修改业务
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 结果
|
||||
*/
|
||||
int updateGenTable(GenTable genTable);
|
||||
|
||||
/**
|
||||
* 批量删除业务
|
||||
*
|
||||
* @param ids 需要删除的数据ID
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteGenTableByIds(Long[] ids);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.service;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.universal.admin.generator.domain.GenTableColumn;
|
||||
import cn.universal.admin.generator.mapper.GenTableColumnMapper;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/** 业务字段 服务层实现 @Author ruoyi */
|
||||
@Service
|
||||
public class GenTableColumnServiceImpl implements IGenTableColumnService {
|
||||
|
||||
@Autowired private GenTableColumnMapper genTableColumnMapper;
|
||||
|
||||
/**
|
||||
* 查询业务字段列表
|
||||
*
|
||||
* @param tableId 业务字段编号
|
||||
* @return 业务字段集合
|
||||
*/
|
||||
@Override
|
||||
public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId) {
|
||||
return genTableColumnMapper.selectGenTableColumnListByTableId(tableId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增业务字段
|
||||
*
|
||||
* @param genTableColumn 业务字段信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertGenTableColumn(GenTableColumn genTableColumn) {
|
||||
return genTableColumnMapper.insertGenTableColumn(genTableColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改业务字段
|
||||
*
|
||||
* @param genTableColumn 业务字段信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateGenTableColumn(GenTableColumn genTableColumn) {
|
||||
return genTableColumnMapper.updateGenTableColumn(genTableColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除业务字段对象
|
||||
*
|
||||
* @param ids 需要删除的数据ID
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteGenTableColumnByIds(String ids) {
|
||||
return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.service;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.admin.generator.constant.GenConstants;
|
||||
import cn.universal.admin.generator.domain.GenTable;
|
||||
import cn.universal.admin.generator.domain.GenTableColumn;
|
||||
import cn.universal.admin.generator.mapper.GenTableColumnMapper;
|
||||
import cn.universal.admin.generator.mapper.GenTableMapper;
|
||||
import cn.universal.admin.generator.util.GenUtils;
|
||||
import cn.universal.admin.generator.util.VelocityInitializer;
|
||||
import cn.universal.admin.generator.util.VelocityUtils;
|
||||
import cn.universal.common.exception.IoTException;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.velocity.Template;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.app.Velocity;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/** 业务 服务层实现 @Author ruoyi */
|
||||
@Service
|
||||
public class GenTableServiceImpl implements IGenTableService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class);
|
||||
|
||||
@Resource private GenTableMapper genTableMapper;
|
||||
|
||||
@Resource private GenTableColumnMapper genTableColumnMapper;
|
||||
|
||||
/**
|
||||
* 查询业务信息
|
||||
*
|
||||
* @param id 业务ID
|
||||
* @return 业务信息
|
||||
*/
|
||||
@Override
|
||||
public GenTable selectGenTableById(Long id) {
|
||||
GenTable genTable = genTableMapper.selectGenTableById(id);
|
||||
setTableFromOptions(genTable);
|
||||
return genTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询业务列表
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 业务集合
|
||||
*/
|
||||
@Override
|
||||
public List<GenTable> selectGenTableList(GenTable genTable) {
|
||||
return genTableMapper.selectGenTableList(genTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询据库列表
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 数据库表集合
|
||||
*/
|
||||
@Override
|
||||
public List<GenTable> selectDbTableList(GenTable genTable) {
|
||||
return genTableMapper.selectDbTableList(genTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询据库列表
|
||||
*
|
||||
* @param tableNames 表名称组
|
||||
* @return 数据库表集合
|
||||
*/
|
||||
@Override
|
||||
public List<GenTable> selectDbTableListByNames(String[] tableNames) {
|
||||
return genTableMapper.selectDbTableListByNames(tableNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有表信息
|
||||
*
|
||||
* @return 表信息集合
|
||||
*/
|
||||
@Override
|
||||
public List<GenTable> selectGenTableAll() {
|
||||
return genTableMapper.selectGenTableAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改业务
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateGenTable(GenTable genTable) {
|
||||
String options = JSONUtil.toJsonStr(genTable.getParams());
|
||||
genTable.setOptions(options);
|
||||
int row = genTableMapper.updateGenTable(genTable);
|
||||
if (row > 0) {
|
||||
for (GenTableColumn cenTableColumn : genTable.getColumns()) {
|
||||
genTableColumnMapper.updateGenTableColumn(cenTableColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除业务对象
|
||||
*
|
||||
* @param tableIds 需要删除的数据ID
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteGenTableByIds(Long[] tableIds) {
|
||||
genTableMapper.deleteGenTableByIds(tableIds);
|
||||
genTableColumnMapper.deleteGenTableColumnByIds(tableIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入表结构
|
||||
*
|
||||
* @param tableList 导入表列表
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void importGenTable(List<GenTable> tableList) {
|
||||
String operName = SecurityUtils.getUnionId();
|
||||
try {
|
||||
for (GenTable table : tableList) {
|
||||
String tableName = table.getTableName();
|
||||
GenUtils.initTable(table, operName);
|
||||
int row = genTableMapper.insertGenTable(table);
|
||||
if (row > 0) {
|
||||
// 保存列信息
|
||||
List<GenTableColumn> genTableColumns =
|
||||
genTableColumnMapper.selectDbTableColumnsByName(tableName);
|
||||
for (GenTableColumn column : genTableColumns) {
|
||||
GenUtils.initColumnField(column, table);
|
||||
genTableColumnMapper.insertGenTableColumn(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IoTException("导入失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览代码
|
||||
*
|
||||
* @param tableId 表编号
|
||||
* @return 预览数据列表
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> previewCode(Long tableId) {
|
||||
Map<String, String> dataMap = new LinkedHashMap<>();
|
||||
// 查询表信息
|
||||
GenTable table = genTableMapper.selectGenTableById(tableId);
|
||||
// 设置主子表信息
|
||||
setSubTable(table);
|
||||
// 设置主键列信息
|
||||
setPkColumn(table);
|
||||
VelocityInitializer.initVelocity();
|
||||
|
||||
VelocityContext context = VelocityUtils.prepareContext(table);
|
||||
|
||||
// 获取模板列表
|
||||
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
|
||||
for (String template : templates) {
|
||||
// 渲染模板
|
||||
StringWriter sw = new StringWriter();
|
||||
Template tpl = Velocity.getTemplate(template, "utf-8");
|
||||
tpl.merge(context, sw);
|
||||
dataMap.put(template, sw.toString());
|
||||
}
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成代码(下载方式)
|
||||
*
|
||||
* @param tableName 表名称
|
||||
* @return 数据
|
||||
*/
|
||||
@Override
|
||||
public byte[] downloadCode(String tableName) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zip = new ZipOutputStream(outputStream);
|
||||
generatorCode(tableName, zip);
|
||||
IOUtils.closeQuietly(zip);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成代码(自定义路径)
|
||||
*
|
||||
* @param tableName 表名称
|
||||
*/
|
||||
@Override
|
||||
public void generatorCode(String tableName) {
|
||||
// 查询表信息
|
||||
GenTable table = genTableMapper.selectGenTableByName(tableName);
|
||||
// 设置主子表信息
|
||||
setSubTable(table);
|
||||
// 设置主键列信息
|
||||
setPkColumn(table);
|
||||
|
||||
VelocityInitializer.initVelocity();
|
||||
|
||||
VelocityContext context = VelocityUtils.prepareContext(table);
|
||||
|
||||
// 获取模板列表
|
||||
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
|
||||
for (String template : templates) {
|
||||
if (!StringUtils.containsAny(
|
||||
template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) {
|
||||
// 渲染模板
|
||||
StringWriter sw = new StringWriter();
|
||||
Template tpl = Velocity.getTemplate(template, StandardCharsets.UTF_8.toString());
|
||||
tpl.merge(context, sw);
|
||||
try {
|
||||
String path = getGenPath(table, template);
|
||||
FileUtils.writeStringToFile(new File(path), sw.toString(), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new IoTException("渲染模板失败,表名:" + table.getTableName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步数据库
|
||||
*
|
||||
* @param tableName 表名称
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void synchDb(String tableName) {
|
||||
GenTable table = genTableMapper.selectGenTableByName(tableName);
|
||||
List<GenTableColumn> tableColumns = table.getColumns();
|
||||
List<String> tableColumnNames =
|
||||
tableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList());
|
||||
|
||||
List<GenTableColumn> dbTableColumns =
|
||||
genTableColumnMapper.selectDbTableColumnsByName(tableName);
|
||||
if (CollectionUtils.isEmpty(dbTableColumns)) {
|
||||
throw new IoTException("同步数据失败,原表结构不存在");
|
||||
}
|
||||
List<String> dbTableColumnNames =
|
||||
dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList());
|
||||
|
||||
dbTableColumns.forEach(
|
||||
column -> {
|
||||
if (!tableColumnNames.contains(column.getColumnName())) {
|
||||
GenUtils.initColumnField(column, table);
|
||||
genTableColumnMapper.insertGenTableColumn(column);
|
||||
}
|
||||
});
|
||||
|
||||
List<GenTableColumn> delColumns =
|
||||
tableColumns.stream()
|
||||
.filter(column -> !dbTableColumnNames.contains(column.getColumnName()))
|
||||
.collect(Collectors.toList());
|
||||
if (!CollectionUtils.isEmpty(delColumns)) {
|
||||
genTableColumnMapper.deleteGenTableColumns(delColumns);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成代码(下载方式)
|
||||
*
|
||||
* @param tableNames 表数组
|
||||
* @return 数据
|
||||
*/
|
||||
@Override
|
||||
public byte[] downloadCode(String[] tableNames) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zip = new ZipOutputStream(outputStream);
|
||||
for (String tableName : tableNames) {
|
||||
generatorCode(tableName, zip);
|
||||
}
|
||||
IOUtils.closeQuietly(zip);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
/** 查询表信息并生成代码 */
|
||||
private void generatorCode(String tableName, ZipOutputStream zip) {
|
||||
// 查询表信息
|
||||
GenTable table = genTableMapper.selectGenTableByName(tableName);
|
||||
// 设置主子表信息
|
||||
setSubTable(table);
|
||||
// 设置主键列信息
|
||||
setPkColumn(table);
|
||||
|
||||
VelocityInitializer.initVelocity();
|
||||
|
||||
VelocityContext context = VelocityUtils.prepareContext(table);
|
||||
|
||||
// 获取模板列表
|
||||
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
|
||||
for (String template : templates) {
|
||||
// 渲染模板
|
||||
StringWriter sw = new StringWriter();
|
||||
Template tpl = Velocity.getTemplate(template, StandardCharsets.UTF_8.toString());
|
||||
tpl.merge(context, sw);
|
||||
try {
|
||||
// 添加到zip
|
||||
zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
|
||||
IOUtils.write(sw.toString(), zip, StandardCharsets.UTF_8.toString());
|
||||
IOUtils.closeQuietly(sw);
|
||||
zip.flush();
|
||||
zip.closeEntry();
|
||||
} catch (IOException e) {
|
||||
log.error("渲染模板失败,表名:" + table.getTableName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改保存参数校验
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
*/
|
||||
@Override
|
||||
public void validateEdit(GenTable genTable) {
|
||||
if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
|
||||
String options = JSONUtil.toJsonStr(genTable.getParams());
|
||||
JSONObject paramsObj = JSONUtil.parseObj(options);
|
||||
if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_CODE))) {
|
||||
throw new IoTException("树编码字段不能为空");
|
||||
} else if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_PARENT_CODE))) {
|
||||
throw new IoTException("树父编码字段不能为空");
|
||||
} else if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_NAME))) {
|
||||
throw new IoTException("树名称字段不能为空");
|
||||
} else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) {
|
||||
if (StringUtils.isEmpty(genTable.getSubTableName())) {
|
||||
throw new IoTException("关联子表的表名不能为空");
|
||||
} else if (StringUtils.isEmpty(genTable.getSubTableFkName())) {
|
||||
throw new IoTException("子表关联的外键名不能为空");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主键列信息
|
||||
*
|
||||
* @param table 业务表信息
|
||||
*/
|
||||
public void setPkColumn(GenTable table) {
|
||||
for (GenTableColumn column : table.getColumns()) {
|
||||
if (column.isPk()) {
|
||||
table.setPkColumn(column);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Objects.isNull(table.getPkColumn())) {
|
||||
table.setPkColumn(table.getColumns().get(0));
|
||||
}
|
||||
if (GenConstants.TPL_SUB.equals(table.getTplCategory())) {
|
||||
for (GenTableColumn column : table.getSubTable().getColumns()) {
|
||||
if (column.isPk()) {
|
||||
table.getSubTable().setPkColumn(column);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Objects.isNull(table.getSubTable().getPkColumn())) {
|
||||
table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主子表信息
|
||||
*
|
||||
* @param table 业务表信息
|
||||
*/
|
||||
public void setSubTable(GenTable table) {
|
||||
String subTableName = table.getSubTableName();
|
||||
if (StringUtils.isNotEmpty(subTableName)) {
|
||||
table.setSubTable(genTableMapper.selectGenTableByName(subTableName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置代码生成其他选项值
|
||||
*
|
||||
* @param genTable 设置后的生成对象
|
||||
*/
|
||||
public void setTableFromOptions(GenTable genTable) {
|
||||
JSONObject paramsObj = JSONUtil.parseObj(genTable.getOptions());
|
||||
String treeCode = paramsObj.getStr(GenConstants.TREE_CODE);
|
||||
String treeParentCode = paramsObj.getStr(GenConstants.TREE_PARENT_CODE);
|
||||
String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
|
||||
String parentMenuId = paramsObj.getStr(GenConstants.PARENT_MENU_ID);
|
||||
String parentMenuName = paramsObj.getStr(GenConstants.PARENT_MENU_NAME);
|
||||
|
||||
genTable.setTreeCode(treeCode);
|
||||
genTable.setTreeParentCode(treeParentCode);
|
||||
genTable.setTreeName(treeName);
|
||||
genTable.setParentMenuId(parentMenuId);
|
||||
genTable.setParentMenuName(parentMenuName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码生成地址
|
||||
*
|
||||
* @param table 业务表信息
|
||||
* @param template 模板文件路径
|
||||
* @return 生成地址
|
||||
*/
|
||||
public static String getGenPath(GenTable table, String template) {
|
||||
String genPath = table.getGenPath();
|
||||
if (StringUtils.equals(genPath, "/")) {
|
||||
return System.getProperty("user.dir")
|
||||
+ File.separator
|
||||
+ "src"
|
||||
+ File.separator
|
||||
+ VelocityUtils.getFileName(template, table);
|
||||
}
|
||||
return genPath + File.separator + VelocityUtils.getFileName(template, table);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.service;
|
||||
|
||||
import cn.universal.admin.generator.domain.GenTableColumn;
|
||||
import java.util.List;
|
||||
|
||||
/** 业务字段 服务层 @Author ruoyi */
|
||||
public interface IGenTableColumnService {
|
||||
|
||||
/**
|
||||
* 查询业务字段列表
|
||||
*
|
||||
* @param tableId 业务字段编号
|
||||
* @return 业务字段集合
|
||||
*/
|
||||
public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
|
||||
|
||||
/**
|
||||
* 新增业务字段
|
||||
*
|
||||
* @param genTableColumn 业务字段信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertGenTableColumn(GenTableColumn genTableColumn);
|
||||
|
||||
/**
|
||||
* 修改业务字段
|
||||
*
|
||||
* @param genTableColumn 业务字段信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateGenTableColumn(GenTableColumn genTableColumn);
|
||||
|
||||
/**
|
||||
* 删除业务字段信息
|
||||
*
|
||||
* @param ids 需要删除的数据ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteGenTableColumnByIds(String ids);
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.service;
|
||||
|
||||
import cn.universal.admin.generator.domain.GenTable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** 业务 服务层 @Author ruoyi */
|
||||
public interface IGenTableService {
|
||||
|
||||
/**
|
||||
* 查询业务列表
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 业务集合
|
||||
*/
|
||||
List<GenTable> selectGenTableList(GenTable genTable);
|
||||
|
||||
/**
|
||||
* 查询据库列表
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 数据库表集合
|
||||
*/
|
||||
List<GenTable> selectDbTableList(GenTable genTable);
|
||||
|
||||
/**
|
||||
* 查询据库列表
|
||||
*
|
||||
* @param tableNames 表名称组
|
||||
* @return 数据库表集合
|
||||
*/
|
||||
List<GenTable> selectDbTableListByNames(String[] tableNames);
|
||||
|
||||
/**
|
||||
* 查询所有表信息
|
||||
*
|
||||
* @return 表信息集合
|
||||
*/
|
||||
List<GenTable> selectGenTableAll();
|
||||
|
||||
/**
|
||||
* 查询业务信息
|
||||
*
|
||||
* @param id 业务ID
|
||||
* @return 业务信息
|
||||
*/
|
||||
GenTable selectGenTableById(Long id);
|
||||
|
||||
/**
|
||||
* 修改业务
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @return 结果
|
||||
*/
|
||||
void updateGenTable(GenTable genTable);
|
||||
|
||||
/**
|
||||
* 删除业务信息
|
||||
*
|
||||
* @param tableIds 需要删除的表数据ID
|
||||
* @return 结果
|
||||
*/
|
||||
void deleteGenTableByIds(Long[] tableIds);
|
||||
|
||||
/**
|
||||
* 导入表结构
|
||||
*
|
||||
* @param tableList 导入表列表
|
||||
*/
|
||||
void importGenTable(List<GenTable> tableList);
|
||||
|
||||
/**
|
||||
* 预览代码
|
||||
*
|
||||
* @param tableId 表编号
|
||||
* @return 预览数据列表
|
||||
*/
|
||||
Map<String, String> previewCode(Long tableId);
|
||||
|
||||
/**
|
||||
* 生成代码(下载方式)
|
||||
*
|
||||
* @param tableName 表名称
|
||||
* @return 数据
|
||||
*/
|
||||
byte[] downloadCode(String tableName);
|
||||
|
||||
/**
|
||||
* 生成代码(自定义路径)
|
||||
*
|
||||
* @param tableName 表名称
|
||||
* @return 数据
|
||||
*/
|
||||
void generatorCode(String tableName);
|
||||
|
||||
/**
|
||||
* 同步数据库
|
||||
*
|
||||
* @param tableName 表名称
|
||||
*/
|
||||
void synchDb(String tableName);
|
||||
|
||||
/**
|
||||
* 批量生成代码(下载方式)
|
||||
*
|
||||
* @param tableNames 表数组
|
||||
* @return 数据
|
||||
*/
|
||||
byte[] downloadCode(String[] tableNames);
|
||||
|
||||
/**
|
||||
* 修改保存参数校验
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
*/
|
||||
void validateEdit(GenTable genTable);
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.universal.admin.generator.config.GenConfig;
|
||||
import cn.universal.admin.generator.constant.GenConstants;
|
||||
import cn.universal.admin.generator.domain.GenTable;
|
||||
import cn.universal.admin.generator.domain.GenTableColumn;
|
||||
import java.util.Arrays;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/** 代码生成器 工具类 @Author ruoyi */
|
||||
public class GenUtils {
|
||||
|
||||
/** 初始化表信息 */
|
||||
public static void initTable(GenTable genTable, String operName) {
|
||||
genTable.setClassName(convertClassName(genTable.getTableName()));
|
||||
genTable.setPackageName(GenConfig.getPackageName());
|
||||
genTable.setModuleName(getModuleName(GenConfig.getPackageName()));
|
||||
genTable.setBusinessName(getBusinessName(genTable.getTableName()));
|
||||
genTable.setFunctionName(replaceText(genTable.getTableComment()));
|
||||
genTable.setFunctionAuthor(GenConfig.getAuthor());
|
||||
genTable.setCreateBy(operName);
|
||||
}
|
||||
|
||||
/** 初始化列属性字段 */
|
||||
public static void initColumnField(GenTableColumn column, GenTable table) {
|
||||
String dataType = getDbType(column.getColumnType());
|
||||
String columnName = column.getColumnName();
|
||||
column.setTableId(table.getTableId());
|
||||
column.setCreateBy(table.getCreateBy());
|
||||
// 设置java字段名
|
||||
column.setJavaField(StrUtil.toCamelCase(columnName));
|
||||
// 设置默认类型
|
||||
column.setJavaType(GenConstants.TYPE_STRING);
|
||||
|
||||
if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType)
|
||||
|| arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) {
|
||||
// 字符串长度超过500设置为文本域
|
||||
Integer columnLength = getColumnLength(column.getColumnType());
|
||||
String htmlType =
|
||||
columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)
|
||||
? GenConstants.HTML_TEXTAREA
|
||||
: GenConstants.HTML_INPUT;
|
||||
column.setHtmlType(htmlType);
|
||||
} else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) {
|
||||
column.setJavaType(GenConstants.TYPE_DATE);
|
||||
column.setHtmlType(GenConstants.HTML_DATETIME);
|
||||
} else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
|
||||
column.setHtmlType(GenConstants.HTML_INPUT);
|
||||
|
||||
// 如果是浮点型 统一用BigDecimal
|
||||
String[] str =
|
||||
StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ",");
|
||||
if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) {
|
||||
column.setJavaType(GenConstants.TYPE_BIGDECIMAL);
|
||||
}
|
||||
// 如果是整形
|
||||
else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) {
|
||||
column.setJavaType(GenConstants.TYPE_INTEGER);
|
||||
}
|
||||
// 长整形
|
||||
else {
|
||||
column.setJavaType(GenConstants.TYPE_LONG);
|
||||
}
|
||||
}
|
||||
|
||||
// 插入字段(默认所有字段都需要插入)
|
||||
column.setIsInsert(GenConstants.REQUIRE);
|
||||
|
||||
// 编辑字段
|
||||
if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) {
|
||||
column.setIsEdit(GenConstants.REQUIRE);
|
||||
}
|
||||
// 列表字段
|
||||
if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) {
|
||||
column.setIsList(GenConstants.REQUIRE);
|
||||
}
|
||||
// 查询字段
|
||||
if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) {
|
||||
column.setIsQuery(GenConstants.REQUIRE);
|
||||
}
|
||||
|
||||
// 查询字段类型
|
||||
if (StringUtils.endsWithIgnoreCase(columnName, "name")) {
|
||||
column.setQueryType(GenConstants.QUERY_LIKE);
|
||||
}
|
||||
// 状态字段设置单选框
|
||||
if (StringUtils.endsWithIgnoreCase(columnName, "status")) {
|
||||
column.setHtmlType(GenConstants.HTML_RADIO);
|
||||
}
|
||||
// 类型&性别字段设置下拉框
|
||||
else if (StringUtils.endsWithIgnoreCase(columnName, "type")
|
||||
|| StringUtils.endsWithIgnoreCase(columnName, "sex")) {
|
||||
column.setHtmlType(GenConstants.HTML_SELECT);
|
||||
}
|
||||
// 图片字段设置图片上传控件
|
||||
else if (StringUtils.endsWithIgnoreCase(columnName, "image")) {
|
||||
column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD);
|
||||
}
|
||||
// 文件字段设置文件上传控件
|
||||
else if (StringUtils.endsWithIgnoreCase(columnName, "file")) {
|
||||
column.setHtmlType(GenConstants.HTML_FILE_UPLOAD);
|
||||
}
|
||||
// 内容字段设置富文本控件
|
||||
else if (StringUtils.endsWithIgnoreCase(columnName, "content")) {
|
||||
column.setHtmlType(GenConstants.HTML_EDITOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验数组是否包含指定值
|
||||
*
|
||||
* @param arr 数组
|
||||
* @param targetValue 值
|
||||
* @return 是否包含
|
||||
*/
|
||||
public static boolean arraysContains(String[] arr, String targetValue) {
|
||||
return Arrays.asList(arr).contains(targetValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块名
|
||||
*
|
||||
* @param packageName 包名
|
||||
* @return 模块名
|
||||
*/
|
||||
public static String getModuleName(String packageName) {
|
||||
int lastIndex = packageName.lastIndexOf(".");
|
||||
int nameLength = packageName.length();
|
||||
String moduleName = StringUtils.substring(packageName, lastIndex + 1, nameLength);
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取业务名
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return 业务名
|
||||
*/
|
||||
public static String getBusinessName(String tableName) {
|
||||
int lastIndex = tableName.lastIndexOf("_");
|
||||
int nameLength = tableName.length();
|
||||
String businessName = StringUtils.substring(tableName, lastIndex + 1, nameLength);
|
||||
return businessName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表名转换成Java类名
|
||||
*
|
||||
* @param tableName 表名称
|
||||
* @return 类名
|
||||
*/
|
||||
public static String convertClassName(String tableName) {
|
||||
boolean autoRemovePre = GenConfig.getAutoRemovePre();
|
||||
String tablePrefix = GenConfig.getTablePrefix();
|
||||
if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) {
|
||||
String[] searchList = StringUtils.split(tablePrefix, ",");
|
||||
tableName = replaceFirst(tableName, searchList);
|
||||
}
|
||||
String name = StrUtil.toCamelCase(tableName);
|
||||
return name.substring(0, 1).toUpperCase() + name.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量替换前缀
|
||||
*
|
||||
* @param replacementm 替换值
|
||||
* @param searchList 替换列表
|
||||
* @return
|
||||
*/
|
||||
public static String replaceFirst(String replacementm, String[] searchList) {
|
||||
String text = replacementm;
|
||||
for (String searchString : searchList) {
|
||||
if (replacementm.startsWith(searchString)) {
|
||||
text = replacementm.replaceFirst(searchString, "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关键字替换
|
||||
*
|
||||
* @param text 需要被替换的名字
|
||||
* @return 替换后的名字
|
||||
*/
|
||||
public static String replaceText(String text) {
|
||||
return RegExUtils.replaceAll(text, "(?:表|若依)", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库类型字段
|
||||
*
|
||||
* @param columnType 列类型
|
||||
* @return 截取后的列类型
|
||||
*/
|
||||
public static String getDbType(String columnType) {
|
||||
if (StringUtils.indexOf(columnType, "(") > 0) {
|
||||
return StringUtils.substringBefore(columnType, "(");
|
||||
} else {
|
||||
return columnType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段长度
|
||||
*
|
||||
* @param columnType 列类型
|
||||
* @return 截取后的列类型
|
||||
*/
|
||||
public static Integer getColumnLength(String columnType) {
|
||||
if (StringUtils.indexOf(columnType, "(") > 0) {
|
||||
String length = StringUtils.substringBetween(columnType, "(", ")");
|
||||
return Integer.valueOf(length);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.util;
|
||||
|
||||
import java.util.Properties;
|
||||
import org.apache.velocity.app.Velocity;
|
||||
|
||||
/** VelocityEngine工厂 @Author ruoyi */
|
||||
public class VelocityInitializer {
|
||||
|
||||
/** 初始化vm方法 */
|
||||
public static void initVelocity() {
|
||||
Properties p = new Properties();
|
||||
try {
|
||||
// 加载classpath目录下的vm文件
|
||||
p.setProperty(
|
||||
"resource.loader.file.class",
|
||||
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
|
||||
// 定义字符集
|
||||
p.setProperty(Velocity.INPUT_ENCODING, "utf-8");
|
||||
// 初始化Velocity引擎,指定配置Properties
|
||||
Velocity.init(p);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.util;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.admin.generator.constant.GenConstants;
|
||||
import cn.universal.admin.generator.domain.GenTable;
|
||||
import cn.universal.admin.generator.domain.GenTableColumn;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
|
||||
/** 模板处理工具类 @Author ruoyi */
|
||||
public class VelocityUtils {
|
||||
|
||||
/** 项目空间路径 */
|
||||
private static final String PROJECT_PATH = "main/java";
|
||||
|
||||
/** mybatis空间路径 */
|
||||
private static final String MYBATIS_PATH = "main/resources/mapper";
|
||||
|
||||
/** 默认上级菜单,系统工具 */
|
||||
private static final String DEFAULT_PARENT_MENU_ID = "3";
|
||||
|
||||
/**
|
||||
* 设置模板变量信息
|
||||
*
|
||||
* @return 模板列表
|
||||
*/
|
||||
public static VelocityContext prepareContext(GenTable genTable) {
|
||||
String moduleName = genTable.getModuleName();
|
||||
String businessName = genTable.getBusinessName();
|
||||
String packageName = genTable.getPackageName();
|
||||
String tplCategory = genTable.getTplCategory();
|
||||
String functionName = genTable.getFunctionName();
|
||||
|
||||
VelocityContext velocityContext = new VelocityContext();
|
||||
velocityContext.put("tplCategory", genTable.getTplCategory());
|
||||
velocityContext.put("tableName", genTable.getTableName());
|
||||
velocityContext.put(
|
||||
"functionName", StrUtil.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
|
||||
velocityContext.put("ClassName", genTable.getClassName());
|
||||
velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
|
||||
velocityContext.put("moduleName", genTable.getModuleName());
|
||||
velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
|
||||
velocityContext.put("businessName", genTable.getBusinessName());
|
||||
velocityContext.put("basePackage", getPackagePrefix(packageName));
|
||||
velocityContext.put("packageName", packageName);
|
||||
velocityContext.put("Author", genTable.getFunctionAuthor());
|
||||
velocityContext.put("datetime", DateUtil.formatDate(new Date()));
|
||||
velocityContext.put("pkColumn", genTable.getPkColumn());
|
||||
velocityContext.put("importList", getImportList(genTable));
|
||||
velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
|
||||
velocityContext.put("columns", genTable.getColumns());
|
||||
velocityContext.put("table", genTable);
|
||||
velocityContext.put("dicts", getDicts(genTable));
|
||||
setMenuVelocityContext(velocityContext, genTable);
|
||||
if (GenConstants.TPL_TREE.equals(tplCategory)) {
|
||||
setTreeVelocityContext(velocityContext, genTable);
|
||||
}
|
||||
if (GenConstants.TPL_SUB.equals(tplCategory)) {
|
||||
setSubVelocityContext(velocityContext, genTable);
|
||||
}
|
||||
return velocityContext;
|
||||
}
|
||||
|
||||
public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) {
|
||||
String options = genTable.getOptions();
|
||||
JSONObject paramsObj = JSONUtil.parseObj(options);
|
||||
String parentMenuId = getParentMenuId(paramsObj);
|
||||
context.put("parentMenuId", parentMenuId);
|
||||
}
|
||||
|
||||
public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
|
||||
String options = genTable.getOptions();
|
||||
JSONObject paramsObj = JSONUtil.parseObj(options);
|
||||
String treeCode = getTreecode(paramsObj);
|
||||
String treeParentCode = getTreeParentCode(paramsObj);
|
||||
String treeName = getTreeName(paramsObj);
|
||||
|
||||
context.put("treeCode", treeCode);
|
||||
context.put("treeParentCode", treeParentCode);
|
||||
context.put("treeName", treeName);
|
||||
context.put("expandColumn", getExpandColumn(genTable));
|
||||
if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
|
||||
context.put("tree_parent_code", paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
|
||||
}
|
||||
if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
|
||||
context.put("tree_name", paramsObj.getStr(GenConstants.TREE_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSubVelocityContext(VelocityContext context, GenTable genTable) {
|
||||
GenTable subTable = genTable.getSubTable();
|
||||
String subTableName = genTable.getSubTableName();
|
||||
String subTableFkName = genTable.getSubTableFkName();
|
||||
String subClassName = genTable.getSubTable().getClassName();
|
||||
String subTableFkClassName = StrUtil.toCamelCase(subTableFkName);
|
||||
subTableFkClassName =
|
||||
subTableFkClassName.substring(0, 1).toUpperCase() + subTableFkClassName.substring(1);
|
||||
|
||||
context.put("subTable", subTable);
|
||||
context.put("subTableName", subTableName);
|
||||
context.put("subTableFkName", subTableFkName);
|
||||
context.put("subTableFkClassName", subTableFkClassName);
|
||||
context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName));
|
||||
context.put("subClassName", subClassName);
|
||||
context.put("subclassName", StringUtils.uncapitalize(subClassName));
|
||||
context.put("subImportList", getImportList(genTable.getSubTable()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板信息
|
||||
*
|
||||
* @return 模板列表
|
||||
*/
|
||||
public static List<String> getTemplateList(String tplCategory) {
|
||||
List<String> templates = new ArrayList<String>();
|
||||
templates.add("vm/java/domain.java.vm");
|
||||
templates.add("vm/java/mapper.java.vm");
|
||||
templates.add("vm/java/service.java.vm");
|
||||
templates.add("vm/java/serviceImpl.java.vm");
|
||||
templates.add("vm/java/controller.java.vm");
|
||||
templates.add("vm/xml/mapper.xml.vm");
|
||||
templates.add("vm/sql/sql.vm");
|
||||
templates.add("vm/js/api.js.vm");
|
||||
if (GenConstants.TPL_CRUD.equals(tplCategory)) {
|
||||
templates.add("vm/vue/index.vue.vm");
|
||||
} else if (GenConstants.TPL_TREE.equals(tplCategory)) {
|
||||
templates.add("vm/vue/index-tree.vue.vm");
|
||||
} else if (GenConstants.TPL_SUB.equals(tplCategory)) {
|
||||
templates.add("vm/vue/index.vue.vm");
|
||||
templates.add("vm/java/sub-domain.java.vm");
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
/** 获取文件名 */
|
||||
public static String getFileName(String template, GenTable genTable) {
|
||||
// 文件名称
|
||||
String fileName = "";
|
||||
// 包路径
|
||||
String packageName = genTable.getPackageName();
|
||||
// 模块名
|
||||
String moduleName = genTable.getModuleName();
|
||||
// 大写类名
|
||||
String className = genTable.getClassName();
|
||||
// 业务名称
|
||||
String businessName = genTable.getBusinessName();
|
||||
|
||||
String javaPath = PROJECT_PATH + "/" + StrUtil.replace(packageName, ".", "/");
|
||||
String mybatisPath = MYBATIS_PATH + "/" + moduleName;
|
||||
String vuePath = "vue";
|
||||
|
||||
if (template.contains("domain.java.vm")) {
|
||||
fileName = StrUtil.format("{}/domain/{}.java", javaPath, className);
|
||||
}
|
||||
if (template.contains("sub-domain.java.vm")
|
||||
&& StrUtil.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) {
|
||||
fileName =
|
||||
StrUtil.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName());
|
||||
} else if (template.contains("mapper.java.vm")) {
|
||||
fileName = StrUtil.format("{}/mapper/{}Mapper.java", javaPath, className);
|
||||
} else if (template.contains("third.java.vm")) {
|
||||
fileName = StrUtil.format("{}/third/I{}Service.java", javaPath, className);
|
||||
} else if (template.contains("serviceImpl.java.vm")) {
|
||||
fileName = StrUtil.format("{}/third/third/{}ServiceImpl.java", javaPath, className);
|
||||
} else if (template.contains("controller.java.vm")) {
|
||||
fileName = StrUtil.format("{}/controller/{}Controller.java", javaPath, className);
|
||||
} else if (template.contains("mapper.xml.vm")) {
|
||||
fileName = StrUtil.format("{}/{}Mapper.xml", mybatisPath, className);
|
||||
} else if (template.contains("sql.vm")) {
|
||||
fileName = businessName + "Menu.sql";
|
||||
} else if (template.contains("api.js.vm")) {
|
||||
fileName = StrUtil.format("{}/api/{}/{}.js", vuePath, moduleName, businessName);
|
||||
} else if (template.contains("index.vue.vm")) {
|
||||
fileName = StrUtil.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
|
||||
} else if (template.contains("index-tree.vue.vm")) {
|
||||
fileName = StrUtil.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包前缀
|
||||
*
|
||||
* @param packageName 包名称
|
||||
* @return 包前缀名称
|
||||
*/
|
||||
public static String getPackagePrefix(String packageName) {
|
||||
int lastIndex = packageName.lastIndexOf(".");
|
||||
String basePackage = StringUtils.substring(packageName, 0, lastIndex);
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据列类型获取导入包
|
||||
*
|
||||
* @param genTable 业务表对象
|
||||
* @return 返回需要导入的包列表
|
||||
*/
|
||||
public static HashSet<String> getImportList(GenTable genTable) {
|
||||
List<GenTableColumn> columns = genTable.getColumns();
|
||||
GenTable subGenTable = genTable.getSubTable();
|
||||
HashSet<String> importList = new HashSet<String>();
|
||||
if (Objects.nonNull(subGenTable)) {
|
||||
importList.add("java.util.List");
|
||||
}
|
||||
for (GenTableColumn column : columns) {
|
||||
if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
|
||||
importList.add("java.util.Date");
|
||||
importList.add("com.fasterxml.jackson.annotation.JsonFormat");
|
||||
} else if (!column.isSuperColumn()
|
||||
&& GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
|
||||
importList.add("java.math.BigDecimal");
|
||||
}
|
||||
}
|
||||
return importList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据列类型获取字典组
|
||||
*
|
||||
* @param genTable 业务表对象
|
||||
* @return 返回字典组
|
||||
*/
|
||||
public static String getDicts(GenTable genTable) {
|
||||
List<GenTableColumn> columns = genTable.getColumns();
|
||||
List<String> dicts = new ArrayList<String>();
|
||||
for (GenTableColumn column : columns) {
|
||||
if (!column.isSuperColumn()
|
||||
&& StrUtil.isNotEmpty(column.getDictType())
|
||||
&& StrUtil.equalsAny(
|
||||
column.getHtmlType(),
|
||||
new String[] {
|
||||
GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX
|
||||
})) {
|
||||
dicts.add("'" + column.getDictType() + "'");
|
||||
}
|
||||
}
|
||||
return StringUtils.join(dicts, ", ");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限前缀
|
||||
*
|
||||
* @param moduleName 模块名称
|
||||
* @param businessName 业务名称
|
||||
* @return 返回权限前缀
|
||||
*/
|
||||
public static String getPermissionPrefix(String moduleName, String businessName) {
|
||||
return StrUtil.format("{}:{}", moduleName, businessName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上级菜单ID字段
|
||||
*
|
||||
* @param paramsObj 生成其他选项
|
||||
* @return 上级菜单ID字段
|
||||
*/
|
||||
public static String getParentMenuId(JSONObject paramsObj) {
|
||||
if (Objects.nonNull(paramsObj)
|
||||
&& paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
|
||||
&& StrUtil.isNotEmpty(paramsObj.getStr(GenConstants.PARENT_MENU_ID))) {
|
||||
return paramsObj.getStr(GenConstants.PARENT_MENU_ID);
|
||||
}
|
||||
return DEFAULT_PARENT_MENU_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树编码
|
||||
*
|
||||
* @param paramsObj 生成其他选项
|
||||
* @return 树编码
|
||||
*/
|
||||
public static String getTreecode(JSONObject paramsObj) {
|
||||
if (paramsObj.containsKey(GenConstants.TREE_CODE)) {
|
||||
return StrUtil.toCamelCase(paramsObj.getStr(GenConstants.TREE_CODE));
|
||||
}
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树父编码
|
||||
*
|
||||
* @param paramsObj 生成其他选项
|
||||
* @return 树父编码
|
||||
*/
|
||||
public static String getTreeParentCode(JSONObject paramsObj) {
|
||||
if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
|
||||
return StrUtil.toCamelCase(paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
|
||||
}
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树名称
|
||||
*
|
||||
* @param paramsObj 生成其他选项
|
||||
* @return 树名称
|
||||
*/
|
||||
public static String getTreeName(JSONObject paramsObj) {
|
||||
if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
|
||||
return StrUtil.toCamelCase(paramsObj.getStr(GenConstants.TREE_NAME));
|
||||
}
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要在哪一列上面显示展开按钮
|
||||
*
|
||||
* @param genTable 业务表对象
|
||||
* @return 展开按钮列序号
|
||||
*/
|
||||
public static int getExpandColumn(GenTable genTable) {
|
||||
String options = genTable.getOptions();
|
||||
JSONObject paramsObj = JSONUtil.parseObj(options);
|
||||
String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
|
||||
int num = 0;
|
||||
for (GenTableColumn column : genTable.getColumns()) {
|
||||
if (column.isList()) {
|
||||
num++;
|
||||
String columnName = column.getColumnName();
|
||||
if (columnName.equals(treeName)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.generator.web;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.universal.admin.generator.domain.GenTable;
|
||||
import cn.universal.admin.generator.domain.GenTableColumn;
|
||||
import cn.universal.admin.generator.service.IGenTableColumnService;
|
||||
import cn.universal.admin.generator.service.IGenTableService;
|
||||
import cn.universal.persistence.page.TableDataInfo;
|
||||
import cn.universal.persistence.query.AjaxResult;
|
||||
import cn.universal.security.BaseController;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 代码生成 操作处理 @Author ruoyi */
|
||||
@RestController
|
||||
@RequestMapping("/admin/tool/gen")
|
||||
public class GenController extends BaseController {
|
||||
|
||||
@Autowired private IGenTableService genTableService;
|
||||
|
||||
@Autowired private IGenTableColumnService genTableColumnService;
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo genList(GenTable genTable) {
|
||||
startPage();
|
||||
List<GenTable> list = genTableService.selectGenTableList(genTable);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/** 修改代码生成业务 */
|
||||
@GetMapping(value = "/{talbleId}")
|
||||
public AjaxResult getInfo(@PathVariable Long talbleId) {
|
||||
GenTable table = genTableService.selectGenTableById(talbleId);
|
||||
List<GenTable> tables = genTableService.selectGenTableAll();
|
||||
List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(talbleId);
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("info", table);
|
||||
map.put("rows", list);
|
||||
map.put("tables", tables);
|
||||
return AjaxResult.success(map);
|
||||
}
|
||||
|
||||
/** 查询数据库列表 */
|
||||
@GetMapping("/db/list")
|
||||
public TableDataInfo dataList(GenTable genTable) {
|
||||
startPage();
|
||||
List<GenTable> list = genTableService.selectDbTableList(genTable);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/** 查询数据表字段列表 */
|
||||
@GetMapping(value = "/column/{talbleId}")
|
||||
public TableDataInfo columnList(Long tableId) {
|
||||
TableDataInfo dataInfo = new TableDataInfo();
|
||||
List<GenTableColumn> list = genTableColumnService.selectGenTableColumnListByTableId(tableId);
|
||||
dataInfo.setRows(list);
|
||||
dataInfo.setTotal(list.size());
|
||||
return dataInfo;
|
||||
}
|
||||
|
||||
/** 导入表结构(保存) */
|
||||
@PostMapping("/importTable")
|
||||
public AjaxResult importTableSave(String tables) {
|
||||
String[] tableNames = Convert.toStrArray(tables);
|
||||
// 查询表信息
|
||||
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
|
||||
genTableService.importGenTable(tableList);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/** 修改保存代码生成业务 */
|
||||
@PutMapping
|
||||
public AjaxResult editSave(@Validated @RequestBody GenTable genTable) {
|
||||
genTableService.validateEdit(genTable);
|
||||
genTableService.updateGenTable(genTable);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/** 删除代码生成 */
|
||||
@DeleteMapping("/{tableIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] tableIds) {
|
||||
genTableService.deleteGenTableByIds(tableIds);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/** 预览代码 */
|
||||
@GetMapping("/preview/{tableId}")
|
||||
public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException {
|
||||
Map<String, String> dataMap = genTableService.previewCode(tableId);
|
||||
return AjaxResult.success(dataMap);
|
||||
}
|
||||
|
||||
/** 生成代码(下载方式) */
|
||||
@GetMapping("/download/{tableName}")
|
||||
public void download(HttpServletResponse response, @PathVariable("tableName") String tableName)
|
||||
throws IOException {
|
||||
byte[] data = genTableService.downloadCode(tableName);
|
||||
genCode(response, data);
|
||||
}
|
||||
|
||||
/** 生成代码(自定义路径) */
|
||||
@GetMapping("/genCode/{tableName}")
|
||||
public AjaxResult genCode(@PathVariable("tableName") String tableName) {
|
||||
genTableService.generatorCode(tableName);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/** 同步数据库 */
|
||||
@GetMapping("/synchDb/{tableName}")
|
||||
public AjaxResult synchDb(@PathVariable("tableName") String tableName) {
|
||||
genTableService.synchDb(tableName);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/** 批量生成代码 */
|
||||
@GetMapping("/batchGenCode")
|
||||
public void batchGenCode(HttpServletResponse response, String tables) throws IOException {
|
||||
String[] tableNames = Convert.toStrArray(tables);
|
||||
byte[] data = genTableService.downloadCode(tableNames);
|
||||
genCode(response, data);
|
||||
}
|
||||
|
||||
/** 生成zip文件 */
|
||||
private void genCode(HttpServletResponse response, byte[] data) throws IOException {
|
||||
response.reset();
|
||||
response.addHeader("Access-Control-Allow-Origin", "*");
|
||||
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\"");
|
||||
response.addHeader("Content-Length", "" + data.length);
|
||||
response.setContentType("application/octet-stream; charset=UTF-8");
|
||||
IOUtils.write(data, response.getOutputStream());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.network.config;
|
||||
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* 网络组件配置
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
* @since 2025/1/20
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "network")
|
||||
public class NetworkConfig {
|
||||
|
||||
/** TCP配置模板 */
|
||||
private Map<String, Object> tcpTemplate;
|
||||
|
||||
/** MQTT配置模板 */
|
||||
private Map<String, Object> mqttTemplate;
|
||||
|
||||
/** 默认配置 */
|
||||
private DefaultConfig defaults = new DefaultConfig();
|
||||
|
||||
/** 配置 RestTemplate Bean */
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(5000); // 连接超时5秒
|
||||
factory.setReadTimeout(10000); // 读取超时10秒
|
||||
return new RestTemplate(factory);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DefaultConfig {
|
||||
|
||||
/** 默认端口 */
|
||||
private int defaultPort = 8080;
|
||||
|
||||
/** 默认超时时间 */
|
||||
private int defaultTimeout = 30;
|
||||
|
||||
/** 默认心跳间隔 */
|
||||
private int defaultKeepAlive = 60;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.network.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 网络类型枚举
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
* @since 2025/1/20
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum NetworkTypeEnum {
|
||||
|
||||
/** TCP客户端 */
|
||||
TCP_CLIENT("TCP_CLIENT", "TCP客户端", "tcp_parser_type"),
|
||||
|
||||
/** TCP服务端 */
|
||||
TCP_SERVER("TCP_SERVER", "TCP服务端", "tcp_parser_type"),
|
||||
|
||||
/** MQTT客户端 */
|
||||
MQTT_CLIENT("MQTT_CLIENT", "MQTT客户端", "mqtt_parser_type"),
|
||||
|
||||
/** MQTT服务端 */
|
||||
MQTT_SERVER("MQTT_SERVER", "MQTT服务端", "mqtt_parser_type");
|
||||
|
||||
/** 类型代码 */
|
||||
private final String code;
|
||||
|
||||
/** 类型名称 */
|
||||
private final String name;
|
||||
|
||||
/** 字典类型 */
|
||||
private final String dictType;
|
||||
|
||||
/**
|
||||
* 根据代码获取枚举
|
||||
*
|
||||
* @param code 类型代码
|
||||
* @return 枚举
|
||||
*/
|
||||
public static NetworkTypeEnum getByCode(String code) {
|
||||
for (NetworkTypeEnum type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代码获取名称
|
||||
*
|
||||
* @param code 类型代码
|
||||
* @return 类型名称
|
||||
*/
|
||||
public static String getNameByCode(String code) {
|
||||
NetworkTypeEnum type = getByCode(code);
|
||||
return type != null ? type.getName() : code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代码获取字典类型
|
||||
*
|
||||
* @param code 类型代码
|
||||
* @return 字典类型
|
||||
*/
|
||||
public static String getDictTypeByCode(String code) {
|
||||
NetworkTypeEnum type = getByCode(code);
|
||||
return type != null ? type.getDictType() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.network.service;
|
||||
|
||||
import cn.universal.common.domain.R;
|
||||
import cn.universal.persistence.entity.IoTDevice;
|
||||
import cn.universal.persistence.entity.Network;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceBO;
|
||||
import cn.universal.persistence.entity.bo.NetworkBO;
|
||||
import cn.universal.persistence.entity.vo.NetworkVO;
|
||||
import cn.universal.persistence.query.NetworkQuery;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 网络组件Service接口
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
* @since 2025/1/20
|
||||
*/
|
||||
public interface INetworkService {
|
||||
|
||||
boolean del(String productKey);
|
||||
|
||||
List<NetworkVO> selectNetworkList(NetworkBO bo);
|
||||
|
||||
List<IoTDevice> queryMileSightList(NetworkBO bo);
|
||||
|
||||
R insertDevInstance(IoTDeviceBO devInstancebo);
|
||||
|
||||
R deleteDevInstanceByIds(String[] ids);
|
||||
|
||||
/**
|
||||
* 查询网络组件列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
List<NetworkBO> selectNetworkList(NetworkQuery query);
|
||||
|
||||
/**
|
||||
* 根据ID查询网络组件
|
||||
*
|
||||
* @param id 网络组件ID
|
||||
* @return 网络组件
|
||||
*/
|
||||
NetworkVO selectNetworkById(Integer id);
|
||||
|
||||
/**
|
||||
* 新增网络组件
|
||||
*
|
||||
* @param network 网络组件
|
||||
* @return 结果
|
||||
*/
|
||||
int insertNetwork(Network network);
|
||||
|
||||
/**
|
||||
* 修改网络组件
|
||||
*
|
||||
* @param network 网络组件
|
||||
* @return 结果
|
||||
*/
|
||||
int updateNetwork(Network network);
|
||||
|
||||
/**
|
||||
* 删除网络组件
|
||||
*
|
||||
* @param id 网络组件ID
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteNetworkById(Integer id);
|
||||
|
||||
/**
|
||||
* 批量删除网络组件
|
||||
*
|
||||
* @param ids 网络组件ID数组
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteNetworkByIds(Integer[] ids);
|
||||
|
||||
/**
|
||||
* 启动网络组件
|
||||
*
|
||||
* @param id 网络组件ID
|
||||
* @return 结果
|
||||
*/
|
||||
int startNetwork(Integer id);
|
||||
|
||||
/**
|
||||
* 停止网络组件
|
||||
*
|
||||
* @param id 网络组件ID
|
||||
* @return 结果
|
||||
*/
|
||||
int stopNetwork(Integer id);
|
||||
|
||||
/**
|
||||
* 重启网络组件
|
||||
*
|
||||
* @param id 网络组件ID
|
||||
* @return 结果
|
||||
*/
|
||||
int restartNetwork(Integer id);
|
||||
|
||||
/**
|
||||
* 获取网络类型列表
|
||||
*
|
||||
* @return 网络类型列表
|
||||
*/
|
||||
List<String> getNetworkTypes();
|
||||
|
||||
/**
|
||||
* 验证网络组件配置
|
||||
*
|
||||
* @param network 网络组件
|
||||
* @return 验证结果
|
||||
*/
|
||||
boolean validateNetworkConfig(Network network);
|
||||
|
||||
/**
|
||||
* 根据ID查询设备
|
||||
*
|
||||
* @param id 设备ID
|
||||
* @return 设备
|
||||
*/
|
||||
IoTDevice getDeviceById(String id);
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.network.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.persistence.entity.Network;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 网络组件配置工具类
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
* @since 2025/1/20
|
||||
*/
|
||||
@Slf4j
|
||||
public class NetworkConfigUtil {
|
||||
|
||||
/**
|
||||
* 验证网络组件配置
|
||||
*
|
||||
* @param network 网络组件
|
||||
* @return 验证结果
|
||||
*/
|
||||
public static boolean validateConfig(Network network) {
|
||||
if (network == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if (StrUtil.isBlank(network.getType())
|
||||
|| StrUtil.isBlank(network.getUnionId())
|
||||
|| StrUtil.isBlank(network.getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证配置JSON格式
|
||||
if (StrUtil.isNotBlank(network.getConfiguration())) {
|
||||
try {
|
||||
JSONUtil.parseObj(network.getConfiguration());
|
||||
} catch (Exception e) {
|
||||
log.warn("网络组件配置JSON格式错误: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证TCP配置
|
||||
*
|
||||
* @param config 配置JSON字符串
|
||||
* @return 验证结果
|
||||
*/
|
||||
public static boolean validateTcpConfig(String config) {
|
||||
if (StrUtil.isBlank(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject jsonConfig = JSONUtil.parseObj(config);
|
||||
|
||||
// 验证必填字段
|
||||
if (!jsonConfig.containsKey("host") || !jsonConfig.containsKey("port")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证端口范围
|
||||
int port = jsonConfig.getInt("port", 0);
|
||||
if (port <= 0 || port > 65535) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.warn("TCP配置验证失败: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证MQTT配置
|
||||
*
|
||||
* @param config 配置JSON字符串
|
||||
* @return 验证结果
|
||||
*/
|
||||
public static boolean validateMqttConfig(String config) {
|
||||
if (StrUtil.isBlank(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject jsonConfig = JSONUtil.parseObj(config);
|
||||
|
||||
// 验证必填字段
|
||||
if (!jsonConfig.containsKey("host")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证QoS范围
|
||||
if (jsonConfig.containsKey("defaultQos")) {
|
||||
int qos = jsonConfig.getInt("defaultQos", -1);
|
||||
if (qos < 0 || qos > 2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证心跳间隔
|
||||
if (jsonConfig.containsKey("keepAliveInterval")) {
|
||||
int keepAlive = jsonConfig.getInt("keepAliveInterval", 0);
|
||||
if (keepAlive < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.warn("MQTT配置验证失败: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据网络类型验证配置
|
||||
*
|
||||
* @param network 网络组件
|
||||
* @return 验证结果
|
||||
*/
|
||||
public static boolean validateConfigByType(Network network) {
|
||||
if (network == null
|
||||
|| StrUtil.isBlank(network.getType())
|
||||
|| StrUtil.isBlank(network.getConfiguration())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (network.getType()) {
|
||||
case "TCP_CLIENT":
|
||||
case "TCP_SERVER":
|
||||
return validateTcpConfig(network.getConfiguration());
|
||||
case "MQTT_CLIENT":
|
||||
case "MQTT_SERVER":
|
||||
return validateMqttConfig(network.getConfiguration());
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认TCP配置
|
||||
*
|
||||
* @return 默认TCP配置
|
||||
*/
|
||||
public static String getDefaultTcpConfig() {
|
||||
JSONObject config = new JSONObject();
|
||||
config.set("host", "0.0.0.0");
|
||||
config.set("port", 8080);
|
||||
config.set("ssl", false);
|
||||
config.set("allIdleTime", 0);
|
||||
config.set("readerIdleTime", 360);
|
||||
config.set("writerIdleTime", 0);
|
||||
config.set("decoderType", "STRING");
|
||||
config.set("parserType", "DELIMITED");
|
||||
config.set(
|
||||
"parserConfiguration",
|
||||
new JSONObject()
|
||||
.set("byteOrderLittle", true)
|
||||
.set("delimited", "]")
|
||||
.set("delimitedMaxlength", 1024)
|
||||
.set("failFast", true));
|
||||
return config.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认MQTT配置
|
||||
*
|
||||
* @return 默认MQTT配置
|
||||
*/
|
||||
public static String getDefaultMqttConfig() {
|
||||
JSONObject config = new JSONObject();
|
||||
config.set("host", "tcp://localhost:1883");
|
||||
config.set("username", "");
|
||||
config.set("password", "");
|
||||
config.set("clientIdPrefix", "univ_cli_");
|
||||
config.set("defaultQos", 1);
|
||||
config.set("keepAliveInterval", 60);
|
||||
config.set("connectTimeout", 30);
|
||||
config.set("autoReconnect", true);
|
||||
config.set("cleanSession", true);
|
||||
config.set("ssl", false);
|
||||
config.set("enabled", true);
|
||||
return config.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据网络类型获取默认配置
|
||||
*
|
||||
* @param type 网络类型
|
||||
* @return 默认配置
|
||||
*/
|
||||
public static String getDefaultConfigByType(String type) {
|
||||
switch (type) {
|
||||
case "TCP_CLIENT":
|
||||
case "TCP_SERVER":
|
||||
return getDefaultTcpConfig();
|
||||
case "MQTT_CLIENT":
|
||||
case "MQTT_SERVER":
|
||||
return getDefaultMqttConfig();
|
||||
default:
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化配置显示
|
||||
*
|
||||
* @param config 配置JSON字符串
|
||||
* @return 格式化后的配置
|
||||
*/
|
||||
public static String formatConfig(String config) {
|
||||
if (StrUtil.isBlank(config)) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
try {
|
||||
return JSONUtil.parseObj(config).toStringPretty();
|
||||
} catch (Exception e) {
|
||||
log.warn("格式化配置失败: {}", e.getMessage());
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.network.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 网络类型工具类
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
* @since 2025/1/20
|
||||
*/
|
||||
@Slf4j
|
||||
public class NetworkTypeUtil {
|
||||
|
||||
/** 支持的网络类型列表 */
|
||||
public static final List<String> SUPPORTED_TYPES =
|
||||
Arrays.asList("TCP_CLIENT", "TCP_SERVER", "MQTT_CLIENT", "MQTT_SERVER", "UDP");
|
||||
|
||||
/**
|
||||
* 解析网络类型字符串,支持逗号分隔的多个类型
|
||||
*
|
||||
* @param typeStr 类型字符串,如 "MQTT_CLIENT,MQTT_SERVER"
|
||||
* @return 类型列表
|
||||
*/
|
||||
public static List<String> parseTypes(String typeStr) {
|
||||
if (StrUtil.isBlank(typeStr)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// 分割并清理空白字符
|
||||
return Arrays.stream(typeStr.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证网络类型是否有效
|
||||
*
|
||||
* @param type 网络类型
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isValidType(String type) {
|
||||
return SUPPORTED_TYPES.contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证多个网络类型是否都有效
|
||||
*
|
||||
* @param types 网络类型列表
|
||||
* @return 是否都有效
|
||||
*/
|
||||
public static boolean isValidTypes(List<String> types) {
|
||||
return types.stream().allMatch(NetworkTypeUtil::isValidType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取无效的类型列表
|
||||
*
|
||||
* @param types 网络类型列表
|
||||
* @return 无效的类型列表
|
||||
*/
|
||||
public static List<String> getInvalidTypes(List<String> types) {
|
||||
return types.stream().filter(type -> !isValidType(type)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化类型字符串(去重、排序)
|
||||
*
|
||||
* @param typeStr 原始类型字符串
|
||||
* @return 格式化后的类型字符串
|
||||
*/
|
||||
public static String formatTypes(String typeStr) {
|
||||
List<String> types = parseTypes(typeStr);
|
||||
return types.stream().distinct().sorted().collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含指定类型
|
||||
*
|
||||
* @param typeStr 类型字符串
|
||||
* @param targetType 目标类型
|
||||
* @return 是否包含
|
||||
*/
|
||||
public static boolean containsType(String typeStr, String targetType) {
|
||||
List<String> types = parseTypes(typeStr);
|
||||
return types.contains(targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型的中文名称
|
||||
*
|
||||
* @param type 类型代码
|
||||
* @return 中文名称
|
||||
*/
|
||||
public static String getTypeName(String type) {
|
||||
return switch (type) {
|
||||
case "TCP_CLIENT" -> "TCP客户端";
|
||||
case "TCP_SERVER" -> "TCP服务端";
|
||||
case "MQTT_CLIENT" -> "MQTT客户端";
|
||||
case "MQTT_SERVER" -> "MQTT服务端";
|
||||
default -> type;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多个类型的中文名称
|
||||
*
|
||||
* @param types 类型列表
|
||||
* @return 中文名称列表
|
||||
*/
|
||||
public static List<String> getTypeNames(List<String> types) {
|
||||
return types.stream().map(NetworkTypeUtil::getTypeName).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.network.utils;
|
||||
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** 用户上下文工具类 用于获取当前登录用户信息 @Author gitee.com/NexIoT */
|
||||
public class UserContextUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UserContextUtil.class);
|
||||
|
||||
/**
|
||||
* 获取当前登录用户的 unionId 如果无法获取用户信息,返回默认值 "system"
|
||||
*
|
||||
* @return 当前用户的 unionId,如果获取失败则返回 "system"
|
||||
*/
|
||||
public static String getCurrentUserUnionId() {
|
||||
try {
|
||||
IoTUser currentUser = SecurityUtils.getIoTUnionUser();
|
||||
if (currentUser != null && currentUser.getUnionId() != null) {
|
||||
return currentUser.getUnionId();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("获取当前用户信息失败,使用默认用户: {}", e.getMessage());
|
||||
}
|
||||
return "system";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息 如果无法获取用户信息,返回 null
|
||||
*
|
||||
* @return 当前用户信息,如果获取失败则返回 null
|
||||
*/
|
||||
public static IoTUser getCurrentUser() {
|
||||
try {
|
||||
return SecurityUtils.getIoTUnionUser();
|
||||
} catch (Exception e) {
|
||||
log.warn("获取当前用户信息失败: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.network.web;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.admin.network.service.INetworkService;
|
||||
import cn.universal.common.annotation.Log;
|
||||
import cn.universal.common.domain.R;
|
||||
import cn.universal.common.enums.BusinessType;
|
||||
import cn.universal.common.exception.IoTException;
|
||||
import cn.universal.persistence.entity.IoTDevice;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.entity.Network;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceBO;
|
||||
import cn.universal.persistence.entity.bo.NetworkBO;
|
||||
import cn.universal.persistence.entity.vo.NetworkVO;
|
||||
import cn.universal.persistence.page.TableDataInfo;
|
||||
import cn.universal.persistence.query.AjaxResult;
|
||||
import cn.universal.persistence.query.NetworkQuery;
|
||||
import cn.universal.security.BaseController;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Controller
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
* @since 2025/1/20
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@Tag(name = "网络管理", description = "网络管理")
|
||||
@RequestMapping("/admin/network")
|
||||
public class NetworkController extends BaseController {
|
||||
|
||||
@Autowired private INetworkService networkService;
|
||||
|
||||
/** 查询列表 */
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(NetworkQuery query) {
|
||||
try {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
if (iotUser != null && !iotUser.isAdmin()) {
|
||||
query.setCreateUser(iotUser.getUnionId());
|
||||
}
|
||||
List<NetworkBO> list = networkService.selectNetworkList(query);
|
||||
return getDataTable(list);
|
||||
} catch (Exception e) {
|
||||
log.error("查询列表失败", e);
|
||||
throw new IoTException("查询列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据ID查询 */
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult getInfo(@PathVariable Integer id) {
|
||||
try {
|
||||
String createUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
NetworkVO network = networkService.selectNetworkById(id);
|
||||
if (network == null) {
|
||||
return AjaxResult.error("不存在");
|
||||
}
|
||||
// 校验用户权限
|
||||
if (!createUser.equals(network.getCreateUser()) && !iotUser.isAdmin()) {
|
||||
return AjaxResult.error("无权限访问该");
|
||||
}
|
||||
return AjaxResult.success(network);
|
||||
} catch (Exception e) {
|
||||
log.error("查询失败", e);
|
||||
return AjaxResult.error("查询失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 新增 */
|
||||
@PostMapping
|
||||
@Log(title = "新增网络", businessType = BusinessType.INSERT)
|
||||
public AjaxResult add(@RequestBody Network network) {
|
||||
try {
|
||||
// 设置创建用户
|
||||
network.setCreateUser(SecurityUtils.getUnionId());
|
||||
int result = networkService.insertNetwork(network);
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("新增成功");
|
||||
} else {
|
||||
return AjaxResult.error("新增失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("新增失败", e);
|
||||
return AjaxResult.error("新增失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 修改 */
|
||||
@PutMapping
|
||||
@Log(title = "修改网络", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult edit(@RequestBody Network network) {
|
||||
try {
|
||||
// 校验用户权限
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
NetworkVO existingNetwork = networkService.selectNetworkById(network.getId());
|
||||
if (existingNetwork == null) {
|
||||
return AjaxResult.error("不存在");
|
||||
}
|
||||
String createUser = SecurityUtils.getUnionId();
|
||||
if (!createUser.equals(existingNetwork.getCreateUser()) && !iotUser.isAdmin()) {
|
||||
return AjaxResult.error("无权限修改该");
|
||||
}
|
||||
|
||||
int result = networkService.updateNetwork(network);
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("修改成功");
|
||||
} else {
|
||||
return AjaxResult.error("修改失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("修改失败", e);
|
||||
return AjaxResult.error("操作失败" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
@DeleteMapping("/{id}")
|
||||
@Log(title = "删除网络", businessType = BusinessType.DELETE)
|
||||
public AjaxResult remove(@PathVariable Integer id) {
|
||||
try {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 校验用户权限
|
||||
NetworkVO network = networkService.selectNetworkById(id);
|
||||
if (network == null) {
|
||||
return AjaxResult.error("不存在");
|
||||
}
|
||||
String createUser = SecurityUtils.getUnionId();
|
||||
if (!createUser.equals(network.getCreateUser()) && !iotUser.isAdmin()) {
|
||||
return AjaxResult.error("无权限删除该");
|
||||
}
|
||||
|
||||
int result = networkService.deleteNetworkById(id);
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("删除成功");
|
||||
} else {
|
||||
return AjaxResult.error("删除失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("删除失败", e);
|
||||
return AjaxResult.error("删除失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 批量删除 */
|
||||
@DeleteMapping("/batch/{ids}")
|
||||
@Log(title = "批量删除网络", businessType = BusinessType.DELETE)
|
||||
public AjaxResult removeBatch(@PathVariable Integer[] ids) {
|
||||
try {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 校验用户权限
|
||||
String createUser = SecurityUtils.getUnionId();
|
||||
for (Integer id : ids) {
|
||||
NetworkVO network = networkService.selectNetworkById(id);
|
||||
if (network == null) {
|
||||
return AjaxResult.error("不存在: " + id);
|
||||
}
|
||||
if (!createUser.equals(network.getCreateUser()) && !iotUser.isAdmin()) {
|
||||
return AjaxResult.error("无权限删除: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
int result = networkService.deleteNetworkByIds(ids);
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("批量删除成功");
|
||||
} else {
|
||||
return AjaxResult.error("批量删除失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("批量删除失败", e);
|
||||
return AjaxResult.error("批量删除失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 启动 */
|
||||
@PostMapping("/start/{id}")
|
||||
@Log(title = "启动网络", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult start(@PathVariable Integer id) {
|
||||
try {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 校验用户权限
|
||||
NetworkVO network = networkService.selectNetworkById(id);
|
||||
if (network == null) {
|
||||
return AjaxResult.error("不存在");
|
||||
}
|
||||
String createUser = SecurityUtils.getUnionId();
|
||||
if (!createUser.equals(network.getCreateUser()) && !iotUser.isAdmin()) {
|
||||
return AjaxResult.error("无权限操作该");
|
||||
}
|
||||
|
||||
int result = networkService.startNetwork(id);
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("启动成功");
|
||||
} else {
|
||||
return AjaxResult.error("启动失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("启动失败", e);
|
||||
return AjaxResult.error("启动失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 停止 */
|
||||
@PostMapping("/stop/{id}")
|
||||
@Log(title = "停止网络", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult stop(@PathVariable Integer id) {
|
||||
try {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 校验用户权限
|
||||
NetworkVO network = networkService.selectNetworkById(id);
|
||||
if (network == null) {
|
||||
return AjaxResult.error("不存在");
|
||||
}
|
||||
String createUser = SecurityUtils.getUnionId();
|
||||
if (!createUser.equals(network.getCreateUser()) && !iotUser.isAdmin()) {
|
||||
return AjaxResult.error("无权限操作该");
|
||||
}
|
||||
|
||||
int result = networkService.stopNetwork(id);
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("操作成功");
|
||||
} else {
|
||||
return AjaxResult.error("操作失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("操作失败", e);
|
||||
return AjaxResult.error("操作失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 重启 */
|
||||
@PostMapping("/restart/{id}")
|
||||
@Log(title = "重启网络", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult restart(@PathVariable Integer id) {
|
||||
try {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 校验用户权限
|
||||
NetworkVO network = networkService.selectNetworkById(id);
|
||||
if (network == null) {
|
||||
return AjaxResult.error("不存在");
|
||||
}
|
||||
String createUser = SecurityUtils.getUnionId();
|
||||
if (!createUser.equals(network.getCreateUser()) && !iotUser.isAdmin()) {
|
||||
return AjaxResult.error("无权限操作该");
|
||||
}
|
||||
|
||||
int result = networkService.restartNetwork(id);
|
||||
if (result > 0) {
|
||||
return AjaxResult.success("重启成功");
|
||||
} else {
|
||||
return AjaxResult.error("重启失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("重启失败", e);
|
||||
return AjaxResult.error("重启失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取网络类型列表 */
|
||||
@GetMapping("/types")
|
||||
public AjaxResult getNetworkTypes() {
|
||||
try {
|
||||
List<String> types = networkService.getNetworkTypes();
|
||||
return AjaxResult.success(types);
|
||||
} catch (Exception e) {
|
||||
log.error("获取网络类型列表失败", e);
|
||||
return AjaxResult.error("获取网络类型列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 验证配置 */
|
||||
@PostMapping("/validate")
|
||||
public AjaxResult validateConfig(@RequestBody Network network) {
|
||||
try {
|
||||
// 设置创建用户
|
||||
network.setCreateUser(SecurityUtils.getUnionId());
|
||||
boolean isValid = networkService.validateNetworkConfig(network);
|
||||
if (isValid) {
|
||||
return AjaxResult.success("配置验证通过");
|
||||
} else {
|
||||
return AjaxResult.error("配置验证失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("验证配置失败", e);
|
||||
return AjaxResult.error("验证配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Resource private INetworkService iNetworkService;
|
||||
|
||||
/** 查询网络列表 */
|
||||
@GetMapping("/v1/list")
|
||||
public TableDataInfo list(NetworkBO bo) {
|
||||
startPage();
|
||||
// 设置用户权限过滤
|
||||
bo.setCreateUser(SecurityUtils.getUnionId());
|
||||
List<NetworkVO> list = iNetworkService.selectNetworkList(bo);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/** 查询绑定指定星纵网关的设备 */
|
||||
@GetMapping("/xz/gateway/list")
|
||||
public TableDataInfo queryMileSightList(NetworkBO bo) {
|
||||
startPage();
|
||||
// 设置用户权限过滤
|
||||
bo.setCreateUser(SecurityUtils.getUnionId());
|
||||
List<IoTDevice> list = iNetworkService.queryMileSightList(bo);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/** 新增设备 */
|
||||
@PostMapping("/xz/gateway/add")
|
||||
@Log(title = "新增设备", businessType = BusinessType.INSERT)
|
||||
public R add(@RequestBody IoTDeviceBO devInstancebo) {
|
||||
// 设置创建用户
|
||||
devInstancebo.setCreatorId(SecurityUtils.getUnionId());
|
||||
R result = iNetworkService.insertDevInstance(devInstancebo);
|
||||
JSONObject jsonObject = JSONUtil.parseObj(result);
|
||||
String code = jsonObject.getStr("code");
|
||||
String msg = jsonObject.getStr("msg");
|
||||
if ("0".equals(code)) {
|
||||
return R.ok();
|
||||
} else {
|
||||
return R.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除设备 */
|
||||
@DeleteMapping("/xz/gateway/{ids}")
|
||||
@Log(title = "删除设备", businessType = BusinessType.DELETE)
|
||||
public R remove(@PathVariable String[] ids) {
|
||||
// 校验用户权限
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
for (String id : ids) {
|
||||
IoTDevice device = iNetworkService.getDeviceById(id);
|
||||
if (device == null) {
|
||||
return R.error("设备不存在: " + id);
|
||||
}
|
||||
if (!currentUser.equals(device.getCreatorId())) {
|
||||
return R.error("无权限删除设备: " + id);
|
||||
}
|
||||
}
|
||||
R result = iNetworkService.deleteDevInstanceByIds(ids);
|
||||
JSONObject jsonObject = JSONUtil.parseObj(result);
|
||||
String code = jsonObject.getStr("code");
|
||||
String msg = jsonObject.getStr("msg");
|
||||
if ("0".equals(code)) {
|
||||
return R.ok();
|
||||
} else {
|
||||
return R.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/reload/tcpclient/{applicationId}")
|
||||
public R reloadTcpClient(@PathVariable("applicationId") String applicationId) {
|
||||
// 校验用户权限 - 需要根据applicationId获取相关的信息进行权限校验
|
||||
// 这里可能需要额外的业务逻辑来实现权限校验
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/** 获取状态 */
|
||||
@GetMapping("/status/{id}")
|
||||
public AjaxResult getStatus(@PathVariable Integer id) {
|
||||
try {
|
||||
NetworkVO network = networkService.selectNetworkById(id);
|
||||
if (network == null) {
|
||||
return AjaxResult.error("不存在");
|
||||
}
|
||||
|
||||
// 校验用户权限
|
||||
String createUser = SecurityUtils.getUnionId();
|
||||
if (!createUser.equals(network.getCreateUser())) {
|
||||
return AjaxResult.error("无权限查看该状态");
|
||||
}
|
||||
|
||||
// 构建状态信息
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("id", network.getId());
|
||||
status.put("name", network.getName());
|
||||
status.put("type", network.getType());
|
||||
status.put("typeName", network.getTypeName());
|
||||
status.put("state", network.getState());
|
||||
status.put("stateName", network.getStateName());
|
||||
status.put("productKey", network.getProductKey());
|
||||
status.put("unionId", network.getUnionId());
|
||||
status.put("createDate", network.getCreateDate());
|
||||
status.put("createUser", network.getCreateUser());
|
||||
|
||||
return AjaxResult.success(status);
|
||||
} catch (Exception e) {
|
||||
log.error("获取状态失败", e);
|
||||
return AjaxResult.error("获取状态失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取所有状态 */
|
||||
@GetMapping("/status")
|
||||
public AjaxResult getAllStatus() {
|
||||
try {
|
||||
NetworkQuery query = new NetworkQuery();
|
||||
query.setPageNum(1);
|
||||
query.setPageSize(1000); // 获取所有
|
||||
// 设置用户权限过滤
|
||||
query.setCreateUser(SecurityUtils.getUnionId());
|
||||
|
||||
List<NetworkBO> networks = networkService.selectNetworkList(query);
|
||||
|
||||
List<Map<String, Object>> statusList = new ArrayList<>();
|
||||
for (NetworkBO network : networks) {
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("id", network.getId());
|
||||
status.put("name", network.getName());
|
||||
status.put("type", network.getType());
|
||||
status.put("state", network.getState());
|
||||
status.put("productKey", network.getProductKey());
|
||||
status.put("unionId", network.getUnionId());
|
||||
statusList.add(status);
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("total", statusList.size());
|
||||
result.put("networks", statusList);
|
||||
|
||||
return AjaxResult.success(result);
|
||||
} catch (Exception e) {
|
||||
log.error("获取所有状态失败", e);
|
||||
return AjaxResult.error("获取所有状态失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package cn.universal.admin.platform.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/** 连接信息数据传输对象 */
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ConnectionInfoDTO {
|
||||
|
||||
/** 内置连接信息 */
|
||||
private BuiltinConnection builtin;
|
||||
|
||||
/** 网络组件连接信息 */
|
||||
private NetworkConnection network;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class BuiltinConnection {
|
||||
/** 连接方式 */
|
||||
private String connectionType;
|
||||
|
||||
/** 连接地址 */
|
||||
private String host;
|
||||
|
||||
/** 端口 */
|
||||
private Integer port;
|
||||
|
||||
/** 用户名 */
|
||||
private String username;
|
||||
|
||||
/** 密码(默认不显示,需要点击查看) */
|
||||
private String password;
|
||||
|
||||
/** 配置说明 */
|
||||
private String description;
|
||||
|
||||
/** MQTT主题信息(仅MQTT类型有) */
|
||||
private TopicInfo topics;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class NetworkConnection {
|
||||
/** 是否启用网络组件 */
|
||||
private Boolean enabled;
|
||||
|
||||
/** 组件名称 */
|
||||
private String name;
|
||||
|
||||
/** 组件类型 */
|
||||
private String type;
|
||||
|
||||
/** 连接地址 */
|
||||
private String host;
|
||||
|
||||
/** 端口 */
|
||||
private Integer port;
|
||||
|
||||
/** 用户名 */
|
||||
private String username;
|
||||
|
||||
/** 密码(默认不显示,需要点击查看) */
|
||||
private String password;
|
||||
|
||||
/** 运行状态 */
|
||||
private Boolean state;
|
||||
|
||||
/** 订阅主题信息 */
|
||||
private TopicInfo subscribeTopics;
|
||||
|
||||
/** 错误信息(当enabled为false时) */
|
||||
private String message;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class TopicInfo {
|
||||
/** 物模型主题 */
|
||||
private ThingTopics thingTopics;
|
||||
|
||||
/** 透传主题 */
|
||||
private PassthroughTopics passthroughTopics;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class ThingTopics {
|
||||
private String propertyUp;
|
||||
private String eventUp;
|
||||
private String commandDown;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class PassthroughTopics {
|
||||
private String dataUp;
|
||||
private String commandDown;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.scheduler;
|
||||
|
||||
import cn.universal.admin.platform.service.IGatewayPollingService;
|
||||
import cn.universal.persistence.entity.GatewayPollingConfig;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 网关轮询定时任务调度器
|
||||
*
|
||||
* <p>使用固定轮询间隔(30s, 60s, 120s, 300s, 600s),通过分布式锁保证集群环境下只有一个节点执行
|
||||
*
|
||||
* @author Aleo
|
||||
* @date 2025-10-26
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@ConditionalOnProperty(prefix = "gateway.polling", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class GatewayPollingScheduler {
|
||||
|
||||
@Autowired
|
||||
private IGatewayPollingService gatewayPollingService;
|
||||
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
/**
|
||||
* 30秒轮询任务
|
||||
*/
|
||||
@Scheduled(cron = "0/30 * * * * ?")
|
||||
public void polling30s() {
|
||||
executePollingWithLock(30);
|
||||
}
|
||||
|
||||
/**
|
||||
* 60秒轮询任务
|
||||
*/
|
||||
@Scheduled(cron = "0 0/1 * * * ?")
|
||||
public void polling60s() {
|
||||
executePollingWithLock(60);
|
||||
}
|
||||
|
||||
/**
|
||||
* 120秒轮询任务
|
||||
*/
|
||||
@Scheduled(cron = "0 0/2 * * * ?")
|
||||
public void polling120s() {
|
||||
executePollingWithLock(120);
|
||||
}
|
||||
|
||||
/**
|
||||
* 300秒轮询任务 (5分钟)
|
||||
*/
|
||||
@Scheduled(cron = "0 0/5 * * * ?")
|
||||
public void polling300s() {
|
||||
executePollingWithLock(300);
|
||||
}
|
||||
|
||||
/**
|
||||
* 600秒轮询任务 (10分钟)
|
||||
*/
|
||||
@Scheduled(cron = "0 0/10 * * * ?")
|
||||
public void polling600s() {
|
||||
executePollingWithLock(600);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用分布式锁执行轮询任务
|
||||
*
|
||||
* @param intervalSeconds 轮询间隔(秒)
|
||||
*/
|
||||
private void executePollingWithLock(int intervalSeconds) {
|
||||
String lockKey = "gateway:polling:lock:" + intervalSeconds;
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
|
||||
try {
|
||||
// 尝试获取锁,最多等待0秒,锁自动释放时间为任务执行周期的80%
|
||||
boolean acquired = lock.tryLock(0, (long) (intervalSeconds * 0.8), TimeUnit.SECONDS);
|
||||
|
||||
if (acquired) {
|
||||
log.info("✓ 节点获得 {}s 轮询任务锁,开始执行", intervalSeconds);
|
||||
executePollingTask(intervalSeconds);
|
||||
} else {
|
||||
log.debug("✗ 节点未获得 {}s 轮询任务锁,跳过执行", intervalSeconds);
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("获取轮询任务锁被中断: intervalSeconds={}", intervalSeconds);
|
||||
} catch (Exception e) {
|
||||
log.error("执行轮询任务异常: intervalSeconds={}", intervalSeconds, e);
|
||||
} finally {
|
||||
// 释放锁
|
||||
if (lock.isHeldByCurrentThread()) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行轮询任务
|
||||
*
|
||||
* @param intervalSeconds 轮询间隔(秒)
|
||||
*/
|
||||
private void executePollingTask(int intervalSeconds) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 1. 查询待轮询的网关设备
|
||||
List<GatewayPollingConfig> configs = gatewayPollingService.getDuePollingDevices(intervalSeconds);
|
||||
|
||||
if (configs == null || configs.isEmpty()) {
|
||||
log.debug("本次{}s轮询无待执行设备", intervalSeconds);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("本次{}s轮询共 {} 个网关设备", intervalSeconds, configs.size());
|
||||
|
||||
// 2. 并发执行轮询
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (GatewayPollingConfig config : configs) {
|
||||
try {
|
||||
gatewayPollingService.pollGatewayDevice(config);
|
||||
successCount++;
|
||||
} catch (Exception e) {
|
||||
failCount++;
|
||||
log.error("轮询网关设备失败: deviceId={}", config.getDeviceId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
long costTime = System.currentTimeMillis() - startTime;
|
||||
log.info("{}s轮询任务完成: 总数={}, 成功={}, 失败={}, 耗时={}ms",
|
||||
intervalSeconds, configs.size(), successCount, failCount, costTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.collection.ConcurrentHashSet;
|
||||
import cn.hutool.core.thread.NamedThreadFactory;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.common.constant.IoTConstant.DownCmd;
|
||||
import cn.universal.common.domain.R;
|
||||
import cn.universal.core.message.UnifiedDownlinkCommand;
|
||||
import cn.universal.core.service.IoTDownlFactory;
|
||||
import cn.universal.persistence.dto.IoTDeviceFunctionHistoryDTO;
|
||||
import cn.universal.persistence.entity.IoTDeviceFunctionHistory;
|
||||
import cn.universal.persistence.entity.IoTDeviceFunctionTask;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.mapper.IoTDeviceFunctionHistoryMapper;
|
||||
import cn.universal.persistence.mapper.IoTDeviceFunctionTaskMapper;
|
||||
import cn.universal.persistence.mapper.IoTProductMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tk.mybatis.mapper.entity.Example;
|
||||
|
||||
/**
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
* @since 2025/3/21
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class BatchFunctionTask {
|
||||
|
||||
@Resource private IoTProductMapper ioTProductMapper;
|
||||
@Resource private IoTDeviceFunctionTaskMapper ioTDeviceFunctionTaskMapper;
|
||||
@Resource private IoTDeviceFunctionHistoryMapper ioTDeviceFunctionHistoryMapper;
|
||||
|
||||
private AtomicBoolean scheduled = new AtomicBoolean();
|
||||
|
||||
private static ScheduledExecutorService executor =
|
||||
Executors.newScheduledThreadPool(1, new NamedThreadFactory("function-batch-task-log", true));
|
||||
|
||||
private Set<IoTDeviceFunctionHistoryDTO> functionSet = new ConcurrentHashSet<>();
|
||||
|
||||
/** 定时 */
|
||||
@Profile({"prod", "dev", "out"})
|
||||
@Scheduled(cron = "0 0/10 * * * ? ")
|
||||
public void doTask() {
|
||||
if (functionSet.size() > 0) {
|
||||
return;
|
||||
} else {
|
||||
IoTDeviceFunctionTask bo = new IoTDeviceFunctionTask();
|
||||
bo.setStatus(0);
|
||||
IoTDeviceFunctionTask ioTDeviceFunctionTask = ioTDeviceFunctionTaskMapper.selectOneTask(bo);
|
||||
if (ObjectUtil.isEmpty(ioTDeviceFunctionTask)) {
|
||||
return;
|
||||
}
|
||||
log.info(
|
||||
"开始批量功能下发,taskId={},任务名称={},执行指令={}",
|
||||
ioTDeviceFunctionTask.getId(),
|
||||
ioTDeviceFunctionTask.getTaskName(),
|
||||
ioTDeviceFunctionTask.getCommand());
|
||||
Example ex2 = new Example(IoTDeviceFunctionHistory.class);
|
||||
// 重新下发时不更改任务时间
|
||||
ioTDeviceFunctionTask.setBeginTime(
|
||||
ioTDeviceFunctionTask.getBeginTime() != null
|
||||
? ioTDeviceFunctionTask.getBeginTime()
|
||||
: new Date());
|
||||
ioTDeviceFunctionTask.setStatus(2);
|
||||
ex2.createCriteria()
|
||||
.andEqualTo("taskId", ioTDeviceFunctionTask.getId())
|
||||
.andEqualTo("downState", 0);
|
||||
List<IoTDeviceFunctionHistory> histories =
|
||||
ioTDeviceFunctionHistoryMapper.selectByExample(ex2);
|
||||
List<IoTDeviceFunctionHistoryDTO> dtos = new ArrayList<>();
|
||||
if (CollectionUtil.isEmpty(histories)) {
|
||||
ioTDeviceFunctionTask.setEndTime(
|
||||
ioTDeviceFunctionTask.getEndTime() != null
|
||||
? ioTDeviceFunctionTask.getEndTime()
|
||||
: new Date());
|
||||
ioTDeviceFunctionTask.setStatus(1);
|
||||
}
|
||||
ioTDeviceFunctionTaskMapper.updateByPrimaryKey(ioTDeviceFunctionTask);
|
||||
IoTProduct productByProductKey =
|
||||
ioTProductMapper.getProductByProductKey(ioTDeviceFunctionTask.getProductKey());
|
||||
String thirdPlatform = productByProductKey.getThirdPlatform();
|
||||
histories.forEach(
|
||||
ioTDeviceFunctionHistory -> {
|
||||
IoTDeviceFunctionHistoryDTO dto =
|
||||
BeanUtil.copyProperties(
|
||||
ioTDeviceFunctionHistory, IoTDeviceFunctionHistoryDTO.class);
|
||||
JSONObject commandData = JSONUtil.parseObj(ioTDeviceFunctionTask.getCommandData());
|
||||
if (ObjectUtil.isNotEmpty(ioTDeviceFunctionHistory.getExtParam())) {
|
||||
JSONObject data = commandData.getJSONObject("data");
|
||||
data.putAll(JSONUtil.parseObj(ioTDeviceFunctionHistory.getExtParam()));
|
||||
commandData.set("data", data);
|
||||
}
|
||||
dto.setCommand(ioTDeviceFunctionTask.getCommand());
|
||||
dto.setCommandData(JSONUtil.toJsonStr(commandData));
|
||||
dto.setThirdPlatform(thirdPlatform);
|
||||
dtos.add(dto);
|
||||
});
|
||||
functionSet.addAll(dtos);
|
||||
consumer();
|
||||
}
|
||||
if (scheduled.compareAndSet(false, true)) {
|
||||
executor.scheduleWithFixedDelay(this::consumer, 5, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
public void consumer() {
|
||||
try {
|
||||
if (!functionSet.isEmpty()) {
|
||||
Iterator<IoTDeviceFunctionHistoryDTO> iterator = functionSet.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
IoTDeviceFunctionHistoryDTO history = iterator.next();
|
||||
iterator.remove();
|
||||
down(history);
|
||||
// 最后一个设备指令时 修改任务完成状态
|
||||
if (!iterator.hasNext()) {
|
||||
IoTDeviceFunctionTask task = new IoTDeviceFunctionTask();
|
||||
task.setId(history.getTaskId());
|
||||
IoTDeviceFunctionTask ioTDeviceFunctionTask =
|
||||
ioTDeviceFunctionTaskMapper.selectOneTask(task);
|
||||
if (ioTDeviceFunctionTask.getEndTime() == null) {
|
||||
ioTDeviceFunctionTask.setEndTime(new Date());
|
||||
}
|
||||
ioTDeviceFunctionTask.setStatus(1);
|
||||
ioTDeviceFunctionTaskMapper.updateByPrimaryKey(ioTDeviceFunctionTask);
|
||||
log.info(
|
||||
"结束批量功能下发,taskId={},任务名称={},执行指令={}",
|
||||
ioTDeviceFunctionTask.getId(),
|
||||
ioTDeviceFunctionTask.getTaskName(),
|
||||
ioTDeviceFunctionTask.getCommand());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("批量功能下发异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void down(IoTDeviceFunctionHistoryDTO h) {
|
||||
JSONObject param = new JSONObject();
|
||||
param.set("appUnionId", "innerBatchTask");
|
||||
param.set("productKey", h.getProductKey());
|
||||
param.set("deviceId", h.getDeviceId());
|
||||
param.set("cmd", DownCmd.DEV_FUNCTION.getValue());
|
||||
param.set("function", JSONUtil.parseObj(h.getCommandData()));
|
||||
param.set("applicationId", "innerBatchTask");
|
||||
// 转换为UnifiedDownlinkCommand(保持所有参数)
|
||||
UnifiedDownlinkCommand command = UnifiedDownlinkCommand.fromJson(param);
|
||||
R r = IoTDownlFactory.getIDown(h.getThirdPlatform()).doAction(command);
|
||||
IoTDeviceFunctionHistory history = BeanUtil.copyProperties(h, IoTDeviceFunctionHistory.class);
|
||||
history.setDownState(2);
|
||||
history.setDownResult(r.getCode() == 0 ? 1 : 0);
|
||||
history.setDownError(r.getMsg());
|
||||
history.setRetry(history.getRetry() + 1);
|
||||
history.setUpdateTime(new Date());
|
||||
ioTDeviceFunctionHistoryMapper.updateByPrimaryKeySelective(history);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.admin.platform.dto.ConnectionInfoDTO;
|
||||
|
||||
/** 连接信息服务接口 */
|
||||
public interface ConnectionInfoService {
|
||||
|
||||
/**
|
||||
* 获取产品的连接信息
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 连接信息
|
||||
*/
|
||||
ConnectionInfoDTO getConnectionInfo(String productKey);
|
||||
|
||||
/**
|
||||
* 获取产品的MQTT密码信息
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 密码信息
|
||||
*/
|
||||
ConnectionInfoDTO getMqttPasswordInfo(String productKey);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.common.domain.R;
|
||||
import cn.universal.persistence.base.DeviceCommandSender;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.mapper.IoTProductMapper;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 设备指令统一调度服务
|
||||
*
|
||||
* <p>自动发现并路由到可用的协议处理器
|
||||
*
|
||||
* <p>支持TCP、UDP、MQTT等多种协议的自动识别和调度
|
||||
*
|
||||
* @author Aleo
|
||||
* @date 2025-10-26
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DeviceCommandDispatcher {
|
||||
|
||||
@Resource private IoTProductMapper productMapper;
|
||||
|
||||
/** 自动注入所有实现了 DeviceCommandSender 的 Bean required=false 避免找不到时启动失败 */
|
||||
@Autowired(required = false)
|
||||
private List<DeviceCommandSender> commandSenders;
|
||||
|
||||
/** 协议类型 -> 处理器映射 */
|
||||
private final Map<String, DeviceCommandSender> senderMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 初始化协议处理器映射
|
||||
*
|
||||
* <p>在Spring容器启动后自动扫描并注册所有可用的协议处理器
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (commandSenders == null || commandSenders.isEmpty()) {
|
||||
log.warn("[DeviceCommandDispatcher] 未找到任何协议处理器实现,网关轮询功能可能不可用");
|
||||
log.warn("[DeviceCommandDispatcher] 请确保TCP/UDP/MQTT协议模块已正确引入并实现DeviceCommandSender接口");
|
||||
return;
|
||||
}
|
||||
|
||||
for (DeviceCommandSender sender : commandSenders) {
|
||||
try {
|
||||
if (sender.isAvailable()) {
|
||||
String protocol = sender.getSupportedProtocol();
|
||||
if (protocol != null && !protocol.isEmpty()) {
|
||||
senderMap.put(protocol.toUpperCase(), sender);
|
||||
log.info(
|
||||
"[DeviceCommandDispatcher] 注册协议处理器: {} -> {}",
|
||||
protocol.toUpperCase(),
|
||||
sender.getClass().getSimpleName());
|
||||
} else {
|
||||
log.warn(
|
||||
"[DeviceCommandDispatcher] 协议处理器返回空协议名称,跳过注册: {}",
|
||||
sender.getClass().getSimpleName());
|
||||
}
|
||||
} else {
|
||||
log.info(
|
||||
"[DeviceCommandDispatcher] 协议处理器不可用,跳过注册: {}", sender.getClass().getSimpleName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[DeviceCommandDispatcher] 注册协议处理器失败: {}", sender.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("[DeviceCommandDispatcher] 初始化完成,可用协议: {}", senderMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送设备指令
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceId 设备ID
|
||||
* @param payload HEX载荷或其他格式载荷
|
||||
* @return 发送结果
|
||||
*/
|
||||
public R<?> sendCommand(String productKey, String deviceId, String payload) {
|
||||
try {
|
||||
// 1. 查询产品协议类型
|
||||
IoTProduct product =
|
||||
productMapper.selectOne(IoTProduct.builder().productKey(productKey).build());
|
||||
|
||||
if (product == null) {
|
||||
log.warn("[DeviceCommandDispatcher] 产品不存在: productKey={}", productKey);
|
||||
return R.error("产品不存在");
|
||||
}
|
||||
|
||||
String protocol = product.getThirdPlatform();
|
||||
if (protocol == null || protocol.isEmpty()) {
|
||||
log.warn(
|
||||
"[DeviceCommandDispatcher] 产品未配置协议: productKey={}, deviceId={}", productKey, deviceId);
|
||||
return R.error("产品未配置协议类型");
|
||||
}
|
||||
|
||||
// 2. 查找协议处理器
|
||||
DeviceCommandSender sender = senderMap.get(protocol.toUpperCase());
|
||||
if (sender == null) {
|
||||
log.warn(
|
||||
"[DeviceCommandDispatcher] 不支持的协议或协议模块未安装: productKey={}, deviceId={}, protocol={}, available={}",
|
||||
productKey,
|
||||
deviceId,
|
||||
protocol,
|
||||
senderMap.keySet());
|
||||
return R.error("不支持的协议: " + protocol + ", 可用协议: " + senderMap.keySet());
|
||||
}
|
||||
|
||||
// 3. 发送指令
|
||||
log.info(
|
||||
"[DeviceCommandDispatcher] 路由指令: productKey={}, deviceId={}, protocol={}, payloadLength={}",
|
||||
productKey,
|
||||
deviceId,
|
||||
protocol,
|
||||
payload != null ? payload.length() : 0);
|
||||
|
||||
return sender.sendCommand(productKey, deviceId, payload);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"[DeviceCommandDispatcher] 发送指令异常: productKey={}, deviceId={}", productKey, deviceId, e);
|
||||
return R.error("发送失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的协议列表
|
||||
*
|
||||
* @return 协议列表
|
||||
*/
|
||||
public List<String> getAvailableProtocols() {
|
||||
return List.copyOf(senderMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查协议是否可用
|
||||
*
|
||||
* @param protocol 协议名称
|
||||
* @return true-可用, false-不可用
|
||||
*/
|
||||
public boolean isProtocolAvailable(String protocol) {
|
||||
return protocol != null && senderMap.containsKey(protocol.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议处理器数量
|
||||
*
|
||||
* @return 处理器数量
|
||||
*/
|
||||
public int getHandlerCount() {
|
||||
return senderMap.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.persistence.entity.IoTCertificate;
|
||||
import java.util.List;
|
||||
|
||||
public interface ICertificateService {
|
||||
|
||||
IoTCertificate getBySslKey(String sslKey);
|
||||
|
||||
List<IoTCertificate> listAll();
|
||||
|
||||
void addCertificate(IoTCertificate cert);
|
||||
|
||||
void updateCertificate(IoTCertificate cert);
|
||||
|
||||
void deleteCertificate(Long id);
|
||||
|
||||
List<IoTCertificate> listByUser(String userId);
|
||||
|
||||
List<IoTCertificate> searchCertificates(
|
||||
String name,
|
||||
String userId,
|
||||
String sslKey,
|
||||
String expireStart,
|
||||
String expireEnd,
|
||||
boolean isAdmin);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.common.domain.R;
|
||||
import cn.universal.persistence.dto.GatewayPollingConfigDTO;
|
||||
import cn.universal.persistence.entity.GatewayPollingConfig;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 网关轮询服务接口
|
||||
*
|
||||
* @author Aleo
|
||||
* @date 2025-10-26
|
||||
*/
|
||||
public interface IGatewayPollingService {
|
||||
|
||||
/**
|
||||
* 保存或更新网关轮询配置
|
||||
*
|
||||
* @param dto 配置DTO
|
||||
* @return 操作结果
|
||||
*/
|
||||
R savePollingConfig(GatewayPollingConfigDTO dto);
|
||||
|
||||
/**
|
||||
* 获取网关轮询配置
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceId 设备ID
|
||||
* @return 配置信息
|
||||
*/
|
||||
R getPollingConfig(String productKey, String deviceId);
|
||||
|
||||
/**
|
||||
* 删除网关轮询配置
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
R deletePollingConfig(String productKey, String deviceId);
|
||||
|
||||
/**
|
||||
* 执行单个网关的轮询任务
|
||||
*
|
||||
* @param config 轮询配置
|
||||
*/
|
||||
void pollGatewayDevice(GatewayPollingConfig config);
|
||||
|
||||
/**
|
||||
* 查询待轮询的网关设备列表
|
||||
*
|
||||
* @param intervalSeconds 轮询间隔
|
||||
* @return 待轮询设备列表
|
||||
*/
|
||||
List<GatewayPollingConfig> getDuePollingDevices(int intervalSeconds);
|
||||
|
||||
/**
|
||||
* 测试轮询 - 立即执行一次轮询
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
R testPolling(String productKey, String deviceId);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.persistence.entity.IoTDeviceEvents;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceLogBO;
|
||||
import cn.universal.persistence.entity.vo.IoTDeviceLogVO;
|
||||
import cn.universal.persistence.query.LogQuery;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备日志Service接口
|
||||
*
|
||||
* @since 2025-12-30
|
||||
*/
|
||||
public interface IIoTDevLogService {
|
||||
|
||||
/** 分页查询设备日志列表 */
|
||||
List<IoTDeviceLogVO> queryPageList(IoTDeviceLogBO bo);
|
||||
|
||||
/** 根据日志id查询日志详情 */
|
||||
IoTDeviceLogVO queryById(LogQuery logQuery);
|
||||
|
||||
/**
|
||||
* 获取设备事件的统计信息
|
||||
*
|
||||
* @param productId 产品Id
|
||||
* @param iotId 设备id
|
||||
*/
|
||||
List<IoTDeviceEvents> queryEventTotal(String productId, String iotId);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceFunctionHistoryBO;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceFunctionTaskBO;
|
||||
import cn.universal.persistence.entity.vo.IoTDeviceFunctionHistoryVO;
|
||||
import cn.universal.persistence.entity.vo.IoTDeviceFunctionTaskVO;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备Service接口
|
||||
*
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
public interface IIoTDeviceFunctionTaskService {
|
||||
|
||||
List<IoTDeviceFunctionTaskVO> selectTaskList(IoTDeviceFunctionTaskBO bo, IoTUser iotUser);
|
||||
|
||||
List<IoTDeviceFunctionHistoryVO> queryFunctionListByTaskId(
|
||||
IoTDeviceFunctionHistoryBO bo, IoTUser iotUser);
|
||||
|
||||
Boolean addFunctionTask(IoTDeviceFunctionTaskBO ioTDeviceFunctionTaskBO, IoTUser iotUser);
|
||||
|
||||
Boolean retryFunctionTask(IoTDeviceFunctionTaskBO ioTDeviceFunctionTaskBO);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.persistence.entity.IoTDevice;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceGroupBO;
|
||||
import cn.universal.persistence.entity.vo.IoTDeviceGroupVO;
|
||||
import cn.universal.persistence.query.AjaxResult;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备分组Service接口
|
||||
*
|
||||
* @since 2025-12-30
|
||||
*/
|
||||
public interface IIoTDeviceGroupService {
|
||||
|
||||
/**
|
||||
* 查询设备分组
|
||||
*
|
||||
* @param id 设备分组ID
|
||||
* @return 设备分组
|
||||
*/
|
||||
IoTDeviceGroupVO selectDevGroupById(Long id);
|
||||
|
||||
/**
|
||||
* 查询设备分组列表
|
||||
*
|
||||
* @return 设备分组集合
|
||||
*/
|
||||
List<IoTDeviceGroupVO> selectDevGroupList();
|
||||
|
||||
/**
|
||||
* 新增设备分组
|
||||
*
|
||||
* @param devGroup 设备分组
|
||||
* @return 结果
|
||||
*/
|
||||
AjaxResult<Void> insertDevGroup(IoTDeviceGroupBO devGroup);
|
||||
|
||||
/**
|
||||
* 修改设备分组
|
||||
*
|
||||
* @param devGroup 设备分组
|
||||
* @return 结果
|
||||
*/
|
||||
int updateDevGroup(IoTDeviceGroupBO devGroup);
|
||||
|
||||
/**
|
||||
* 删除设备分组信息
|
||||
*
|
||||
* @param id 设备分组ID
|
||||
* @return 结果
|
||||
*/
|
||||
AjaxResult<Void> deleteDevGroupById(Long id);
|
||||
|
||||
AjaxResult<Void> bindDevToGroup(IoTDeviceGroupBO devGroup);
|
||||
|
||||
List<IoTDevice> selectDevInstanceListByGroupId(String groupId);
|
||||
|
||||
AjaxResult<Void> unBindDevByGroupId(String groupId, String[] devId);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.common.domain.R;
|
||||
import cn.universal.common.poi.ExcelTemplate;
|
||||
import cn.universal.persistence.entity.IoTDevice;
|
||||
import cn.universal.persistence.entity.IoTDeviceShadow;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceBO;
|
||||
import cn.universal.persistence.entity.bo.IoTGwDeviceBO;
|
||||
import cn.universal.persistence.entity.vo.GwIoTDeviceVo;
|
||||
import cn.universal.persistence.entity.vo.IoTDeviceCompanyVO;
|
||||
import cn.universal.persistence.entity.vo.IoTDeviceCountVO;
|
||||
import cn.universal.persistence.entity.vo.IoTDeviceModelVO;
|
||||
import cn.universal.persistence.entity.vo.IoTDeviceVO;
|
||||
import com.github.pagehelper.Page;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备Service接口
|
||||
*
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
public interface IIoTDeviceService {
|
||||
|
||||
/**
|
||||
* 查询设备分组
|
||||
*
|
||||
* @param iotId iotId
|
||||
*/
|
||||
List<String> selectDevInstanceGroupName(String iotId);
|
||||
|
||||
/**
|
||||
* 查询设备
|
||||
*
|
||||
* @param id 设备主键
|
||||
* @return 设备
|
||||
*/
|
||||
public IoTDevice selectDevInstanceById(String id);
|
||||
|
||||
List<IoTDeviceCountVO> countDevByApplication(String appUniqueId);
|
||||
|
||||
/**
|
||||
* 查询设备列表
|
||||
*
|
||||
* @param ioTDevice 设备
|
||||
* @return 设备集合
|
||||
*/
|
||||
public List<IoTDevice> selectDevInstanceList(IoTDevice ioTDevice, IoTUser iotUser);
|
||||
|
||||
public List<IoTDevice> selectDevInstanceUnList(IoTDevice ioTDevice, IoTUser iotUser);
|
||||
|
||||
/** 导出 */
|
||||
List<IoTDevice> selectDevInstanceForExport(IoTDevice ioTDevice, IoTUser iotUser);
|
||||
|
||||
/** 设备批量导入 */
|
||||
public int importDevice(List<IoTDevice> devicesImport, IoTProduct ioTProduct, String deviceId);
|
||||
|
||||
// 设备应用绑定
|
||||
int bindApp(String ids, String appUniqueId);
|
||||
|
||||
/**
|
||||
* 新增设备
|
||||
*
|
||||
* @param devInstancebo 设备
|
||||
* @return 结果
|
||||
*/
|
||||
public R insertDevInstance(IoTDeviceBO devInstancebo);
|
||||
|
||||
/**
|
||||
* 修改设备
|
||||
*
|
||||
* @param devInstancebo 设备
|
||||
* @return 结果
|
||||
*/
|
||||
public R updateDevInstance(IoTDeviceBO devInstancebo);
|
||||
|
||||
/**
|
||||
* 批量删除设备
|
||||
*
|
||||
* @param ids 需要删除的设备主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public R deleteDevInstanceByIds(String[] ids);
|
||||
|
||||
/**
|
||||
* 删除设备信息
|
||||
*
|
||||
* @param id 设备主键
|
||||
* @return 结果
|
||||
*/
|
||||
public R deleteDevInstanceById(String id);
|
||||
|
||||
/** 根据应用id查找设备 */
|
||||
public int selectDevByAppUniqueId(String[] ids);
|
||||
|
||||
/**
|
||||
* 查询所以设备型号
|
||||
*
|
||||
* @param supportChild 等于true 查询支持子设备的型号
|
||||
* @return 设备型号
|
||||
*/
|
||||
List<IoTDeviceCompanyVO> queryAllBySupportChild(Boolean supportChild, String apps);
|
||||
|
||||
/**
|
||||
* 查询所以设备型号
|
||||
*
|
||||
* @return 设备影子
|
||||
*/
|
||||
IoTDeviceShadow getDeviceShadow(String id);
|
||||
|
||||
/**
|
||||
* 设备下发
|
||||
*
|
||||
* @param productKey 产品key
|
||||
* @param downRequest 下发内容
|
||||
* @return 结果
|
||||
*/
|
||||
public R iotFunctionsDown(String productKey, String downRequest);
|
||||
|
||||
/**
|
||||
* 根据产品key查型号配置
|
||||
*
|
||||
* @param productKey 产品key
|
||||
* @return 结果
|
||||
*/
|
||||
public IoTDeviceModelVO getModelByProductKey(String productKey);
|
||||
|
||||
List<IoTDevice> selectNotBindDevInstanceList(
|
||||
String groupId, IoTDevice ioTDevice, IoTUser iotUser);
|
||||
|
||||
Page<IoTDeviceVO> selectFenceDevice(IoTDeviceBO ioTDeviceBO, int page, int size);
|
||||
|
||||
ExcelTemplate importTemplateExcel(String productKey);
|
||||
|
||||
List<GwIoTDeviceVo> selectGwIotDevices(IoTGwDeviceBO bo);
|
||||
|
||||
IoTDevice selectIoTDevice(String productKey, String deviceId);
|
||||
|
||||
List<IoTDevice> selectDevInstanceListWithTags(IoTDeviceBO ioTDeviceBO, IoTUser iotUser);
|
||||
|
||||
List<IoTProduct> selectProductListInBatchFunction(Long applicationId, IoTUser iotUser);
|
||||
|
||||
/** 新:按第三方平台过滤设备列表(不影响旧接口) */
|
||||
List<IoTDevice> selectDevInstanceListByPlatform(
|
||||
IoTDevice ioTDevice, IoTUser iotUser, String thirdPlatform);
|
||||
|
||||
/** 根据网关设备查询子设备关系 */
|
||||
List<GwIoTDeviceVo> selectSubDeviceRelationByGw(String gwProductKey, String gwDeviceId);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.common.constant.IoTConstant.TcpFlushType;
|
||||
import cn.universal.persistence.entity.IoTDeviceEvents;
|
||||
import cn.universal.persistence.entity.IoTDeviceFunction;
|
||||
import cn.universal.persistence.entity.IoTDeviceProperties;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.bo.IoTProductBO;
|
||||
import cn.universal.persistence.entity.bo.IoTProductImportBO;
|
||||
import cn.universal.persistence.entity.vo.IoTDeviceModelVO;
|
||||
import cn.universal.persistence.entity.vo.IoTProductExportVO;
|
||||
import cn.universal.persistence.entity.vo.IoTProductVO;
|
||||
import cn.universal.persistence.query.AjaxResult;
|
||||
import cn.universal.persistence.query.IoTProductQuery;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备产品Service接口
|
||||
*
|
||||
* @since 2025-12-24
|
||||
*/
|
||||
public interface IIoTProductService {
|
||||
|
||||
/**
|
||||
* 查询设备产品
|
||||
*
|
||||
* @param id 设备产品主键
|
||||
* @return 设备产品
|
||||
*/
|
||||
IoTProductVO selectDevProductById(String id);
|
||||
|
||||
/**
|
||||
* 查询设备产品列表
|
||||
*
|
||||
* @param ioTProduct 设备产品
|
||||
* @return 设备产品集合
|
||||
*/
|
||||
List<IoTProduct> selectDevProductList(IoTProduct ioTProduct);
|
||||
|
||||
/**
|
||||
* 查询设备产品列表
|
||||
*
|
||||
* @param ioTProductQuery 设备产品QU
|
||||
* @return 设备产品集合
|
||||
*/
|
||||
List<IoTProduct> selectDevProductV2List(IoTProductQuery ioTProductQuery);
|
||||
|
||||
/**
|
||||
* 查询设备产品列表V3,单表
|
||||
*
|
||||
* @param ioTProductQuery 设备产品QU
|
||||
* @return 设备产品集合
|
||||
*/
|
||||
List<IoTProductVO> selectDevProductV3List(IoTProductQuery ioTProductQuery);
|
||||
|
||||
/**
|
||||
* 查询设备产品列表V4,单表
|
||||
*
|
||||
* @param ioTProductQuery 设备产品QU
|
||||
* @return 设备产品集合
|
||||
*/
|
||||
List<IoTProductVO> selectDevProductV4List(IoTProductQuery ioTProductQuery);
|
||||
|
||||
/**
|
||||
* 搜索当前条件下产品的所有字段
|
||||
*
|
||||
* @param ioTProductQuery 设备产品QU
|
||||
* @return 设备产品集合
|
||||
*/
|
||||
List<IoTProductExportVO> selectAllDevProductV2List(IoTProductQuery ioTProductQuery);
|
||||
|
||||
/**
|
||||
* 新增设备产品
|
||||
*
|
||||
* @param ioTProduct 设备产品
|
||||
* @return 结果
|
||||
*/
|
||||
int insertDevProduct(IoTProduct ioTProduct);
|
||||
|
||||
/**
|
||||
* 新增设备产品
|
||||
*
|
||||
* @param ioTProductList 设备产品列表
|
||||
* @return 结果
|
||||
*/
|
||||
int insertList(List<IoTProduct> ioTProductList);
|
||||
|
||||
/** 批量导入 */
|
||||
String importProduct(List<IoTProductImportBO> ioTProductImportBOS, String unionId);
|
||||
|
||||
/** 导出产品完整包(包含协议、网络配置、物模型) */
|
||||
List<cn.universal.persistence.entity.dto.ProductExportPackageDTO> exportProductPackages(
|
||||
IoTProductQuery query);
|
||||
|
||||
/** 导入产品完整包 */
|
||||
String importProductPackages(
|
||||
List<cn.universal.persistence.entity.dto.ProductExportPackageDTO> packages, String unionId);
|
||||
|
||||
/**
|
||||
* 修改设备产品
|
||||
*
|
||||
* @param ioTProduct 设备产品
|
||||
* @return 结果
|
||||
*/
|
||||
int updateDevProduct(IoTProduct ioTProduct);
|
||||
|
||||
/**
|
||||
* 修改网络信息
|
||||
*
|
||||
* @param id
|
||||
* @param networkUnionId
|
||||
* @return
|
||||
*/
|
||||
int updateNetworkUnionId(Long id, String networkUnionId);
|
||||
|
||||
/**
|
||||
* 批量删除设备产品
|
||||
*
|
||||
* @param ids 需要删除的设备产品主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteDevProductByIds(String[] ids);
|
||||
|
||||
/**
|
||||
* 删除设备产品信息
|
||||
*
|
||||
* @param id 设备产品主键
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteDevProductById(String id);
|
||||
|
||||
/** 修改产品配置信息 */
|
||||
int updateDevProductConfig(IoTProductVO devProduct);
|
||||
|
||||
/**
|
||||
* 查询设备产品物模型属性列表
|
||||
*
|
||||
* @return 设备产品物模型属性集合
|
||||
*/
|
||||
List<IoTDeviceProperties> selectDevProperties(String Id);
|
||||
|
||||
/**
|
||||
* 查询设备产品物模型事件列表
|
||||
*
|
||||
* @return 设备产品物模型事件集合
|
||||
*/
|
||||
List<IoTDeviceEvents> selectDevEvents(String Id);
|
||||
|
||||
/**
|
||||
* 查询设备产品物模型方法列表
|
||||
*
|
||||
* @return 设备产品物模型方法集合
|
||||
*/
|
||||
List<IoTDeviceFunction> selectDevFunctions(String Id);
|
||||
|
||||
/**
|
||||
* 根据产品key查model配置
|
||||
*
|
||||
* @param productKey 产品key
|
||||
* @return 结果
|
||||
*/
|
||||
IoTDeviceModelVO getModelByProductKey(String productKey);
|
||||
|
||||
int insertMetadata(IoTProductBO ioTProductBO);
|
||||
|
||||
int deleteMetadata(IoTProductBO ioTProductBO);
|
||||
|
||||
int updateMetadata(IoTProductBO ioTProductBO);
|
||||
|
||||
IoTProductBO getMetadata(IoTProductBO ioTProductBO);
|
||||
|
||||
String getMetadataProductKey(String productKey);
|
||||
|
||||
Map<String, Integer> countDevNumberByProductKey(String unionId);
|
||||
|
||||
AjaxResult<IoTProduct> selectIoTProductByKey(String key);
|
||||
|
||||
AjaxResult<List<IoTProduct>> selectGatewaySubProductsByKey(String gwProductKey);
|
||||
|
||||
int updateDevProductOtherConfig(String otherConfig);
|
||||
|
||||
int updateDevProductStoreConfig(IoTProductVO storeConfig);
|
||||
|
||||
void flushNettyServer(String config, String productKey, TcpFlushType type);
|
||||
|
||||
List<IoTProductVO> selectDevProductAllList();
|
||||
|
||||
List<IoTProductVO> getGatewayList();
|
||||
|
||||
List<IoTProductVO> getGatewaySubDeviceList(String productKey);
|
||||
|
||||
/** 绑定证书到产品(将sslKey和ssl=true写入configuration) */
|
||||
int bindCertificate(String productKey, String sslKey);
|
||||
|
||||
/** 解绑证书(将ssl=false, sslKey设空) */
|
||||
int unbindCertificate(String productKey);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: gitee.com/NexIoT
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.admin.platform.service;
|
||||
|
||||
import cn.universal.persistence.entity.IoTProductSort;
|
||||
import cn.universal.persistence.entity.bo.RuleModelBO.IoTProductSortBO;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 产品分类Service接口 @Author gitee.com/NexIoT
|
||||
*
|
||||
* @since 2025-12-29
|
||||
*/
|
||||
public interface IIoTProductSortService {
|
||||
|
||||
/**
|
||||
* 查询产品分类
|
||||
*
|
||||
* @param id 产品分类主键
|
||||
* @return 产品分类
|
||||
*/
|
||||
public IoTProductSort selectDevProductSortById(String id);
|
||||
|
||||
/**
|
||||
* 查询产品分类列表
|
||||
*
|
||||
* @param ioTProductSort 产品分类
|
||||
* @return 产品分类集合
|
||||
*/
|
||||
public List<IoTProductSort> selectDevProductSortList(IoTProductSort ioTProductSort);
|
||||
|
||||
/**
|
||||
* 新增产品分类
|
||||
*
|
||||
* @param ioTProductSort 产品分类
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertDevProductSort(IoTProductSort ioTProductSort);
|
||||
|
||||
/**
|
||||
* 修改产品分类
|
||||
*
|
||||
* @param ioTProductSort 产品分类
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateDevProductSort(IoTProductSort ioTProductSort);
|
||||
|
||||
/**
|
||||
* 批量删除产品分类
|
||||
*
|
||||
* @param ids 需要删除的产品分类主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteDevProductSortByIds(String[] ids);
|
||||
|
||||
/**
|
||||
* 删除产品分类信息
|
||||
*
|
||||
* @param id 产品分类主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteDevProductSortById(String id);
|
||||
|
||||
/**
|
||||
* 构建前端所需要树结构
|
||||
*
|
||||
* @param ioTProductSortList 菜单列表
|
||||
* @return 树结构列表
|
||||
*/
|
||||
public List<IoTProductSort> buildProductSortTree(List<IoTProductSort> ioTProductSortList);
|
||||
|
||||
// ==================== 新增的BO方法 ====================
|
||||
|
||||
/**
|
||||
* 新增产品分类(使用BO)
|
||||
*
|
||||
* @param bo 产品分类业务对象
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertDevProductSort(IoTProductSortBO bo);
|
||||
|
||||
/**
|
||||
* 修改产品分类(使用BO)
|
||||
*
|
||||
* @param bo 产品分类业务对象
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateDevProductSort(IoTProductSortBO bo);
|
||||
|
||||
/**
|
||||
* 获取产品分类树结构
|
||||
*
|
||||
* @return 树结构列表
|
||||
*/
|
||||
public List<IoTProductSort> getProductSortTree();
|
||||
|
||||
/**
|
||||
* 构建前端所需要下拉树结构
|
||||
*
|
||||
* @param devProductSortList 菜单列表
|
||||
* @return 下拉树结构列表
|
||||
*/
|
||||
// public List<TreeSelect> buildProductSortTreeSelect(List<IoTProductSort> devProductSortList);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package cn.universal.admin.platform.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.universal.admin.platform.service.ICertificateService;
|
||||
import cn.universal.common.utils.CertInfoUtil;
|
||||
import cn.universal.persistence.entity.IoTCertificate;
|
||||
import cn.universal.persistence.mapper.IoTCertificateMapper;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tk.mybatis.mapper.entity.Example;
|
||||
|
||||
@Service
|
||||
public class CertificateServiceImpl implements ICertificateService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CertificateServiceImpl.class);
|
||||
|
||||
@Autowired private IoTCertificateMapper certificateMapper;
|
||||
|
||||
@Override
|
||||
public IoTCertificate getBySslKey(String sslKey) {
|
||||
Example example = new Example(IoTCertificate.class);
|
||||
example.createCriteria().andEqualTo("sslKey", sslKey);
|
||||
return certificateMapper.selectOneByExample(example);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IoTCertificate> listAll() {
|
||||
return certificateMapper.selectAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCertificate(IoTCertificate cert) {
|
||||
fillCertInfo(cert);
|
||||
certificateMapper.insertSelective(cert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCertificate(IoTCertificate cert) {
|
||||
fillCertInfo(cert);
|
||||
certificateMapper.updateByPrimaryKeySelective(cert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteCertificate(Long id) {
|
||||
certificateMapper.deleteByPrimaryKey(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IoTCertificate> listByUser(String userId) {
|
||||
Example example = new Example(IoTCertificate.class);
|
||||
example.createCriteria().andEqualTo("createUser", userId);
|
||||
return certificateMapper.selectByExample(example);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IoTCertificate> searchCertificates(
|
||||
String name,
|
||||
String userId,
|
||||
String sslKey,
|
||||
String expireStart,
|
||||
String expireEnd,
|
||||
boolean isAdmin) {
|
||||
Example example = new Example(IoTCertificate.class);
|
||||
Example.Criteria c = example.createCriteria();
|
||||
if (!isAdmin && userId != null) {
|
||||
c.andEqualTo("createUser", userId);
|
||||
}
|
||||
if (name != null && !name.isEmpty()) {
|
||||
c.andLike("name", "%" + name + "%");
|
||||
}
|
||||
if (sslKey != null && !sslKey.isEmpty()) {
|
||||
c.andEqualTo("sslKey", sslKey);
|
||||
}
|
||||
if (expireStart != null && !expireStart.isEmpty()) {
|
||||
c.andGreaterThanOrEqualTo("expireTime", expireStart);
|
||||
}
|
||||
if (expireEnd != null && !expireEnd.isEmpty()) {
|
||||
c.andLessThanOrEqualTo("expireTime", expireEnd);
|
||||
}
|
||||
example.orderBy("createTime").desc();
|
||||
return certificateMapper.selectByExample(example);
|
||||
}
|
||||
|
||||
/** 自动提取证书详细信息并填充到certInfo字段 */
|
||||
private void fillCertInfo(IoTCertificate cert) {
|
||||
if (cert == null || cert.getCertContent() == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
JSONObject jsonObject = CertInfoUtil.extractCertInfo(cert.getCertContent());
|
||||
if (jsonObject != null) {
|
||||
cert.setCertInfo(jsonObject.toString());
|
||||
if (jsonObject.getDate("notAfter") != null) {
|
||||
cert.setExpireTime(jsonObject.getDate("notAfter"));
|
||||
}
|
||||
} else {
|
||||
log.warn("证书详细信息提取失败: sslKey={}", cert.getSslKey());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("证书详细信息提取异常: sslKey={}, err={}", cert.getSslKey(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package cn.universal.admin.platform.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.admin.config.ConnectionConfig;
|
||||
import cn.universal.admin.platform.dto.ConnectionInfoDTO;
|
||||
import cn.universal.admin.platform.service.ConnectionInfoService;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.Network;
|
||||
import cn.universal.persistence.mapper.IoTProductMapper;
|
||||
import cn.universal.persistence.mapper.NetworkMapper;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/** 连接信息服务实现 */
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ConnectionInfoServiceImpl implements ConnectionInfoService {
|
||||
|
||||
private final IoTProductMapper productMapper;
|
||||
private final NetworkMapper networkMapper;
|
||||
private final ConnectionConfig connectionConfig;
|
||||
|
||||
@Override
|
||||
public ConnectionInfoDTO getConnectionInfo(String productKey) {
|
||||
log.debug("获取产品连接信息: productKey={}", productKey);
|
||||
|
||||
// 查询产品信息
|
||||
IoTProduct product = productMapper.getProductByProductKey(productKey);
|
||||
if (product == null) {
|
||||
throw new RuntimeException("产品不存在: " + productKey);
|
||||
}
|
||||
|
||||
// 构建内置连接信息
|
||||
ConnectionInfoDTO.BuiltinConnection builtin = buildBuiltinConnection(product);
|
||||
|
||||
// 构建网络组件连接信息
|
||||
ConnectionInfoDTO.NetworkConnection network = buildNetworkConnection(product);
|
||||
|
||||
return ConnectionInfoDTO.builder().builtin(builtin).network(network).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionInfoDTO getMqttPasswordInfo(String productKey) {
|
||||
log.debug("获取产品MQTT密码信息: productKey={}", productKey);
|
||||
|
||||
// 查询产品信息
|
||||
IoTProduct product = productMapper.getProductByProductKey(productKey);
|
||||
if (product == null) {
|
||||
throw new RuntimeException("产品不存在: " + productKey);
|
||||
}
|
||||
|
||||
// 构建内置MQTT密码信息
|
||||
ConnectionInfoDTO.BuiltinConnection builtin = buildBuiltinMqttPassword(product);
|
||||
|
||||
// 构建网络组件MQTT密码信息
|
||||
ConnectionInfoDTO.NetworkConnection network = buildNetworkMqttPassword(product);
|
||||
|
||||
return ConnectionInfoDTO.builder().builtin(builtin).network(network).build();
|
||||
}
|
||||
|
||||
/** 构建内置连接信息 */
|
||||
private ConnectionInfoDTO.BuiltinConnection buildBuiltinConnection(IoTProduct product) {
|
||||
String platform = product.getThirdPlatform();
|
||||
|
||||
ConnectionInfoDTO.BuiltinConnection.BuiltinConnectionBuilder builder =
|
||||
ConnectionInfoDTO.BuiltinConnection.builder();
|
||||
|
||||
switch (platform) {
|
||||
case "tcp":
|
||||
return builder
|
||||
.connectionType(connectionConfig.getTcpConnectionType())
|
||||
.host(connectionConfig.getTcpHost(product.getProductKey()))
|
||||
.port(connectionConfig.getTcpSniPort())
|
||||
.description(connectionConfig.getTcpDescription())
|
||||
.build();
|
||||
|
||||
case "mqtt":
|
||||
ConnectionInfoDTO.BuiltinConnection builtinMqtt =
|
||||
builder
|
||||
.connectionType(connectionConfig.getMqttConnectionType())
|
||||
.host(connectionConfig.getMqttHost())
|
||||
.port(connectionConfig.extractPortFromHost(connectionConfig.getMqttHost()))
|
||||
.username(product.getProductKey())
|
||||
.password("点击查看密码")
|
||||
.description(connectionConfig.getMqttDescription())
|
||||
.topics(buildDefaultTopics(product.getProductKey()))
|
||||
.build();
|
||||
return builtinMqtt;
|
||||
|
||||
case "udp":
|
||||
return builder
|
||||
.connectionType(connectionConfig.getUdpConnectionType())
|
||||
.host(connectionConfig.getTcpHost(product.getProductKey()))
|
||||
.port(connectionConfig.getUdpPort())
|
||||
.description(connectionConfig.getUdpDescription())
|
||||
.build();
|
||||
default:
|
||||
return builder.connectionType("请联系管理员").host("请联系管理员").port(0).description("暂无").build();
|
||||
}
|
||||
}
|
||||
|
||||
/** 构建网络组件连接信息 */
|
||||
private ConnectionInfoDTO.NetworkConnection buildNetworkConnection(IoTProduct product) {
|
||||
if (StrUtil.isBlank(product.getNetworkUnionId())) {
|
||||
return ConnectionInfoDTO.NetworkConnection.builder()
|
||||
.enabled(false)
|
||||
.message("未绑定网络组件")
|
||||
.build();
|
||||
}
|
||||
|
||||
// 查询网络组件
|
||||
Network network = Network.builder().unionId(product.getNetworkUnionId()).build();
|
||||
network = networkMapper.selectOne(network);
|
||||
if (network == null) {
|
||||
return ConnectionInfoDTO.NetworkConnection.builder()
|
||||
.enabled(false)
|
||||
.message("绑定的网络组件不存在")
|
||||
.build();
|
||||
}
|
||||
|
||||
JSONObject config = JSONUtil.parseObj(network.getConfiguration());
|
||||
|
||||
ConnectionInfoDTO.NetworkConnection.NetworkConnectionBuilder builder =
|
||||
ConnectionInfoDTO.NetworkConnection.builder()
|
||||
.enabled(true)
|
||||
.name(network.getName())
|
||||
.type(network.getType())
|
||||
.state(network.getState())
|
||||
.host(config.getStr("host"))
|
||||
.port(config.getInt("port"))
|
||||
.username(config.getStr("username"))
|
||||
.password("点击查看密码");
|
||||
|
||||
// 处理MQTT主题信息
|
||||
if ("mqtt".equals(product.getThirdPlatform())) {
|
||||
ConnectionInfoDTO.TopicInfo subscribeTopics =
|
||||
buildSubscribeTopics(config, product.getProductKey());
|
||||
builder.subscribeTopics(subscribeTopics);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** 构建内置MQTT密码信息 */
|
||||
private ConnectionInfoDTO.BuiltinConnection buildBuiltinMqttPassword(IoTProduct product) {
|
||||
if (!"mqtt".equals(product.getThirdPlatform())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ConnectionInfoDTO.BuiltinConnection.builder()
|
||||
.connectionType(connectionConfig.getMqttConnectionType())
|
||||
.host(connectionConfig.getMqttHost())
|
||||
.port(connectionConfig.extractPortFromHost(connectionConfig.getMqttHost()))
|
||||
.username(product.getProductKey())
|
||||
.password(product.getProductSecret())
|
||||
.description(connectionConfig.getMqttDescription())
|
||||
.build();
|
||||
}
|
||||
|
||||
/** 构建网络组件MQTT密码信息 */
|
||||
private ConnectionInfoDTO.NetworkConnection buildNetworkMqttPassword(IoTProduct product) {
|
||||
if (StrUtil.isBlank(product.getNetworkUnionId())) {
|
||||
return ConnectionInfoDTO.NetworkConnection.builder()
|
||||
.enabled(false)
|
||||
.message("未绑定网络组件")
|
||||
.build();
|
||||
}
|
||||
|
||||
List<Network> networks = networkMapper.selectByUnionId(product.getNetworkUnionId());
|
||||
if (networks.isEmpty()) {
|
||||
return ConnectionInfoDTO.NetworkConnection.builder()
|
||||
.enabled(false)
|
||||
.message("绑定的网络组件不存在")
|
||||
.build();
|
||||
}
|
||||
|
||||
Network network = networks.get(0);
|
||||
JSONObject config = JSONUtil.parseObj(network.getConfiguration());
|
||||
|
||||
return ConnectionInfoDTO.NetworkConnection.builder()
|
||||
.enabled(true)
|
||||
.name(network.getName())
|
||||
.type(network.getType())
|
||||
.host(config.getStr("host"))
|
||||
.port(config.getInt("port"))
|
||||
.username(config.getStr("username"))
|
||||
.password(config.getStr("password"))
|
||||
.build();
|
||||
}
|
||||
|
||||
/** 构建默认主题信息 */
|
||||
private ConnectionInfoDTO.TopicInfo buildDefaultTopics(String productKey) {
|
||||
ConnectionInfoDTO.ThingTopics thingTopics =
|
||||
ConnectionInfoDTO.ThingTopics.builder()
|
||||
.propertyUp(replaceProductKey(connectionConfig.getMqttThingPropertyUp(), productKey))
|
||||
.eventUp(replaceProductKey(connectionConfig.getMqttThingEventUp(), productKey))
|
||||
.commandDown(replaceProductKey(connectionConfig.getMqttThingCommandDown(), productKey))
|
||||
.build();
|
||||
|
||||
ConnectionInfoDTO.PassthroughTopics passthroughTopics =
|
||||
ConnectionInfoDTO.PassthroughTopics.builder()
|
||||
.dataUp(replaceProductKey(connectionConfig.getMqttPassthroughDataUp(), productKey))
|
||||
.commandDown(
|
||||
replaceProductKey(connectionConfig.getMqttPassthroughCommandDown(), productKey))
|
||||
.build();
|
||||
|
||||
return ConnectionInfoDTO.TopicInfo.builder()
|
||||
.thingTopics(thingTopics)
|
||||
.passthroughTopics(passthroughTopics)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** 构建订阅主题信息 */
|
||||
private ConnectionInfoDTO.TopicInfo buildSubscribeTopics(JSONObject config, String productKey) {
|
||||
String subscribe = config.getStr("subscribeTopics");
|
||||
;
|
||||
if (!JSONUtil.isTypeJSON(subscribe)) {
|
||||
return buildDefaultTopics(productKey);
|
||||
}
|
||||
JSONObject subscribeTopics = config.getJSONObject("subscribeTopics");
|
||||
if (subscribeTopics == null || subscribeTopics.isEmpty()) {
|
||||
// 使用默认主题
|
||||
return buildDefaultTopics(productKey);
|
||||
}
|
||||
ConnectionInfoDTO.ThingTopics thingTopics = null;
|
||||
ConnectionInfoDTO.PassthroughTopics passthroughTopics = null;
|
||||
|
||||
JSONObject thingTopicsConfig = subscribeTopics.getJSONObject("thingTopics");
|
||||
if (thingTopicsConfig != null) {
|
||||
thingTopics =
|
||||
ConnectionInfoDTO.ThingTopics.builder()
|
||||
.propertyUp(thingTopicsConfig.getStr("propertyUp"))
|
||||
.eventUp(thingTopicsConfig.getStr("eventUp"))
|
||||
.commandDown(thingTopicsConfig.getStr("commandDown"))
|
||||
.build();
|
||||
}
|
||||
|
||||
JSONObject passthroughTopicsConfig = subscribeTopics.getJSONObject("passthroughTopics");
|
||||
if (passthroughTopicsConfig != null) {
|
||||
passthroughTopics =
|
||||
ConnectionInfoDTO.PassthroughTopics.builder()
|
||||
.dataUp(passthroughTopicsConfig.getStr("dataUp"))
|
||||
.commandDown(passthroughTopicsConfig.getStr("commandDown"))
|
||||
.build();
|
||||
}
|
||||
|
||||
return ConnectionInfoDTO.TopicInfo.builder()
|
||||
.thingTopics(thingTopics)
|
||||
.passthroughTopics(passthroughTopics)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** 替换主题中的产品Key占位符 */
|
||||
private String replaceProductKey(String topic, String productKey) {
|
||||
if (StrUtil.isBlank(topic)) {
|
||||
return topic;
|
||||
}
|
||||
// 支持两种格式:{productKey} 和 ${productKey}
|
||||
return topic
|
||||
.replace("{productKey}", productKey)
|
||||
.replace("${productKey}", productKey)
|
||||
.replace("{deviceId}", "${deviceId}")
|
||||
.replace("${deviceId}", "${deviceId}");
|
||||
}
|
||||
}
|
||||