This commit is contained in:
Join
2025-10-29 21:32:25 +08:00
commit fa5adc6056
2288 changed files with 323903 additions and 0 deletions
+41
View File
@@ -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
+7
View File
@@ -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
+65
View File
@@ -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
Binary file not shown.
+128
View File
@@ -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日**
+65
View File
@@ -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`
+201
View File
@@ -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.
+16
View File
@@ -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.
+2
View File
@@ -0,0 +1,2 @@
健康检查:http://localhost:9091/actuator/health
安全状态:http://localhost:9091/actuator/health/security
+297
View File
@@ -0,0 +1,297 @@
# 🤖 NexIoT AI物联网平台
<div align="center">
![License](https://img.shields.io/badge/license-AGPL3.0-blue.svg)
![Java](https://img.shields.io/badge/Java-21-orange.svg)
![SpringBoot](https://img.shields.io/badge/SpringBoot-3.5-brightgreen.svg)
![MySQL](https://img.shields.io/badge/MySQL-5.7+-blue.svg)
![Redis](https://img.shields.io/badge/Redis-Latest-red.svg)
![AI](https://img.shields.io/badge/AI-Enabled-purple.svg)
## 🎯 创新的"真·零代码侵入"物联网平台
**突破传统物联网平台设计思路 · 设备驱动完全外置 · 一键导出即用 · 零代码侵入**
[📖 文档地址](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能力增强**:智能故障诊断
- **📱 移动端应用**:付费图鸟定小程序,具备指令控制、属性、告警查看,轻量化、多管理员
### 🌟 长期愿景
- **🧠 深度学习**:设备行为学习、预测性维护、智能优化
- **🌐 生态建设**:插件市场、第三方集成、开发者社区
- **🏭 行业方案**:智能制造、智慧城市、智慧农业等垂直解决方案
## 📸 实战案例与视频教程
### 📊 支持设备类型
| | |
|-------------------------------------------------|-----------------------------------------------|
| ![电表设备](/__MACOSX/shot/1018/dian.png) | ![摄像头](/__MACOSX/shot/1018/v22.png) |
| ![网关DTU](/__MACOSX/shot/1018/1760431601441.jpg) | ![水浸设备](/__MACOSX/shot/1018/111.jpg) |
| ![声光报警器](/__MACOSX/shot/1026/device-sgbjq.png) | ![水浸设备](/__MACOSX/shot/1026/device-sj.jpg) |
### 📡 天翼物联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解析
#### ⚡ 接入成果展示
![接入成果动态](__MACOSX/shot/dianbiao.gif)
---
## 📸 平台界面展示
### 🏗️ 驱动的系统架构
![系统架构](/__MACOSX/shot/dataflow.png)
### 🚀 云原生部署架构
![云原生部署](/__MACOSX/shot/deploy.jpg)
### 🖥️ 智能化功能展示
#### 🌐 国际双语支持
![img.png](__MACOSX/shot/1018/en.png)
#### 🌐 驱动的天翼产品接入
![天翼产品接入](__MACOSX/shot/99.gif)
#### 🔄 流程编排引擎
![流程编排](__MACOSX/shot/53.png)
#### 🛠️ 协议统一管理与智能IDE
![协议管理与IDE](__MACOSX/shot/54.png)
### 🎯 功能深度展示
| | |
|------------------------------------------|---------------------------------------|
| ![产品管理](/__MACOSX/shot/1018/pd.jpg) | ![设备管理](/__MACOSX/shot/1018/sb.jpg) |
| ![视频监控](/__MACOSX/shot/1018/video.jpg) | ![网络管理](/__MACOSX/shot/1018/net.jpg) |
| ![数据管理1](/__MACOSX/shot/1018/d1.jpg) | ![数据管理2](/__MACOSX/shot/1018/d2.jpg) |
| ![运维管理](/__MACOSX/shot/1018/yz.jpg) | ![设备状态](/__MACOSX/shot/1018/zsb.jpg) |
| ![设备面板](/__MACOSX/shot/1018/zsb-pb.jpg) | ![系统配置](/__MACOSX/shot/1018/zt.jpg) |
| ![报警管理](/__MACOSX/shot/1018/bjm.jpg) | ![数据采集](/__MACOSX/shot/1018/cj.jpg) |
## 📝 更新日志
我们维护详细的更新日志,记录每个版本的重要变更和新功能。
### 📖 完整更新日志
查看完整的更新历史,请访问:[📝 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站 | 抖音 |
|-------------------------------|-------------------------------|----------------------------------|----------------------------------|
| ![QQ群](/__MACOSX/shot/qq.png) | ![微信群](/__MACOSX/shot/wx.png) | ![B站](/__MACOSX/shot/bzhan.jpg) | ![抖音](/__MACOSX/shot/douyin.jpg) |
## 🛠️ 技术支持与服务
我们提供全方位的技术支持服务,从基础答疑到企业级解决方案,满足不同层次的需求。
### 📋 服务类型对比
| 服务类型 | 服务内容 | 价格 | 适用场景 | 联系方式 |
|---------|---------|--------------|----------|----------|
| **🆓 社区技术答疑** | • 基础问题答疑<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 等物联网平台
- **社区支持**:所有贡献者和用户的支持与反馈
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

+143
View File
@@ -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;
}
}
File diff suppressed because it is too large Load Diff
@@ -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}");
}
}

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