合并最新版本代码

This commit is contained in:
奈科斯(NexIoT)
2025-09-22 21:27:27 +08:00
commit fd4b9b26b1
1420 changed files with 178989 additions and 0 deletions
+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
+661
View File
@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
+345
View File
@@ -0,0 +1,345 @@
# 🚀 NexIoT物联网平台
<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)
## 一款基于Java技术栈构建的"零代码侵入"企业级物联网平台
[📖 文档地址](https://nexiotplatform.github.io/universal-iot-docs/) | [🌐 在线演示](http://iot.192886.xyz:81/) | [🔧 调试IDE](http://iot.192886.xyz:81/magic/debug/index.html)
</div>
## 📋 目录
- [✨ 平台简介](#-平台简介)
- [🌟 平台亮点](#-平台亮点)
- [🚀 快速开始](#-快速开始)
- [📦 运行部署](#-运行部署)
- [🎯 核心功能](#-核心功能)
- [🏗️ 技术架构](#️-技术架构)
- [📈 正在推进](#-正在推进)
- [💡 最佳实践](#-最佳实践)
- [🎥 视频教程](#-视频教程)
- [📸 界面展示](#-界面展示)
- [🤝 贡献指南](#-贡献指南)
- [📄 开源协议](#-开源协议)
## ✨ 平台简介
NexIoT IoT
是一款基于Java技术栈构建的“零代码侵入”企业级物联网平台,具备高内聚低耦合,零代码侵入。平台集成了云调试功能,支持TCP、MQTT、天翼物联、HTTP等多种协议的统一管理,提供完整的产品定义、物模型设计、设备接入、北向应用输出、规则引擎等核心功能,为物联网应用提供一站式解决方案。
## 🌟 平台亮点
- ✅ **低成本对接**:接入一款设备在`外置调试器`完成,不用`改`系统任何代码,无代码侵入,`大学生`就能完成。
- ✅ **模块化**:TCP、MQTT、天翼物联等按需开启,对接一个新平台太容易了
- ✅ **没有标准化应对各种标准**:国内行业的协议现状,不再搞标准,主打应对各种标准接入,看着很复杂无头绪,上手用起来很舒服
- ✅ **强大的协议调试器**:基于浏览器的实时协议开发和调试,直连设备接入已经半年多没重启过。
- ✅ **极简架构**:Java21、mysql5.7+、redis、MQTT(不限emqx还是其他),轻松应付百万级别的设备;支持clickhouse存储(配置就行)
## 🚀 快速开始
### 📋 环境要求
- **JDK**: 21+
- **MySQL**: 5.7+
- **Redis**: 6.0+
- **Maven**: 3.9+
### 🎯 演示地址
- **管理后台**: <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/>
## 📦 运行部署
### 🖥️ 本地部署
```bash
# 1. 修改配置文件
vim cn-universal-web/src/main/resources/application-{dev}.properties
# 2. 配置数据库、redis、emqx
spring:
datasource:
url: jdbc:mysql://localhost:3306/universal_iot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
# 3. 启动应用
mvn spring-boot:run
```
### ☁️ 生产环境部署
```bash
# 1. 打包应用
mvn clean package -DskipTests -PProd
# 2. 上传到服务器
scp cn-universal-web/target/cn-universal-web.tar.gz root@your-server:/opt/nexiot/
# 3. 启动服务
tar -zxvf cn-universal-web.tar.gz
cd cn-universal-web & ./bin/restart.sh
```
## 🎯 核心功能
### 🚀 设备接入能力
- ✅ **TCP设备接入**:16进制报文传输、协议解析、云调试、在线测试
- ✅ **MQTT设备接入**:物模型/透传模式、云调试、在线测试、支持EMQX/HiveMQ等
- ✅ **HTTP设备接入**:REST API、Java SDK、OAuth2认证、完整接口规范
- ✅ **天翼物联接入**:设备注册、数据上报、指令下发、标准协议对接
- ✅ **云云对接接入**:已支持,具备完整的SDK和API,支持多平台数据同步、协议转换
### 🎯 核心管理功能
- ✅ **产品管理**:多协议产品创建、物模型定义、产品标签、公共配置
- ✅ **协议管理**:云调试环境、插件化架构、实时协议开发、统一管理平台
- ✅ **设备管理**:设备详情监控、在线指令下发、设备影子、消息订阅、日志查看
- ✅ **网络组件**:TCP/MQTT服务管理、启动/暂停/禁用操作、个性化参数配置
- ✅ **应用管理**:北向应用输出、OAuth2 API SDK、设备绑定、推送配置
- ✅ **通知管理**:多渠道支持(钉钉、阿里云、腾讯云、webhook、邮箱、飞书)
- ✅ **场景联动**:基于设备属性/事件的触发规则、多动作执行、实时自动化
- 🔄 **RuleGO规则引擎**:可视化规则引擎执行、支持OPCUA、Moudbus工业场景
### 📊 协议接入现状
| 协议类型 | 状态 | 核心功能 |
|------------|-------|-----------------------------------|
| **TCP设备** | ✅ 已实现 | 16进制报文传输、协议解析、云调试、在线测试 |
| **MQTT设备** | ✅ 已实现 | 物模型/透传模式、云调试、在线测试、支持EMQX/HiveMQ等 |
| **HTTP设备** | ✅ 已实现 | REST API、Java SDK、OAuth2认证、完整接口规范 |
| **天翼物联** | ✅ 已实现 | 完整的产品创建设备注册、数据上报、指令下发、标准协议对接 |
| **云云对接** | ✅ 已支持 | Java SDK 自行扩展协议模块 |
## 🏗️ 技术架构
### 🛠️ 技术栈
- **后端框架**:`Java 21` `SpringBoot 3.5` `Tk.Mybatis 5.0.1`
- **数据存储**:MySQL5.7+ / Redis / ClickHouse(可选)
- **架构模式**:单体架构设计,支持微服务化部署
- **前端技术**:基于`RuoYi-Antdv`构建,感谢开源社区!
### 🏛️ 系统架构
```text
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 设备接入层 │ │ 协议处理层 │ │ 业务服务层 │
│ │ │ │ │ │
│ • TCP Server │───▶│ • 协议解析 │───▶│ • 设备管理 │
│ • MQTT Broker │ │ • 数据转换 │ │ • 产品管理 │
│ • HTTP API │ │ • 消息路由 │ │ • 规则引擎 │
│ • 天翼物联 │ │ • 云调试 │ │ • 通知服务 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 数据存储层 │ │ 缓存层 │ │ 外部接口层 │
│ │ │ │ │ │
│ • MySQL │ │ • Redis │ │ • 北向API │
│ • ClickHouse │ │ • 本地缓存 │ │ • Webhook │
│ • 时序数据 │ │ • 会话管理 │ │ • 第三方集成 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
## 📈 正在推进
- **🔄 小程序开发**:正在定制UI,具备指令控制、属性、告警查看,轻量化
- **🔄 扩展推送**:MQTT / RocketMQ / DB / Kafka
- **🔄 其他**:也可以提一些意见
## 💡 最佳实践
### 我在1688自费购入了两款设备(NB水浸、4G电表),通过电信AEP与TCP直连。将围绕典型接入方式,发布视频和教程。以过来人的经验,单看文档不如对照实物从0到1动手实践更高效,你在实际操作中会有更多收获
### 当然你也可以购买与作者同款设备进行学习,另外请注意用电安全
| | | |
|------------------------------|------------------------------|-----------------------------|
| ![电表设备](/__MACOSX/shot/dian.jpg) | ![水浸设备](/__MACOSX/shot/shui.jpg) | ![设备展示](/__MACOSX/shot/mai.jpg) |
## 🎥 视频教程
我们提供了详细的视频教程,帮助您快速上手NexIoT平台。所有教程都是基于真实设备进行实战演示。
### 📡 天翼物联接入教程
#### 🌊 NB水浸传感器系列
1. **【手把手实战:水浸传感器开箱:初见与硬件解析】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1kLY5z5EMH/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:设备开箱、硬件介绍、基本功能测试
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直连接入教程
#### ⚡ DL/T64电表系列
1. **【【开源】手把手实战:DL/T64电表TCP直连接入】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1x4pBzZEVN/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:TCP协议解析、设备直连配置、数据解析
2. **【【开源】手把手实战:产品创建与SNI解读】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1zSWNzoETF/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:产品创建流程、SNI设备标识解析、协议调试
3. **【【NexIoT开源】手把手实战:如何写TCP接入的DL/T645电表解析脚本】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1SAWVzwEZE/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:数据流介绍、调试IDE的编写和调试、645电表的编写
### 🎯 更多教程
我们正在持续制作更多设备的接入教程,尽情期待
## 📸 界面展示
### 🏗️ 架构图
![架构图](/__MACOSX/shot/dataflow.png)
### 🚀 部署图
![部署图](/__MACOSX/shot/deploy.jpg)
### 🖥️ 功能截图
#### 🌐 直接使用天翼产品
![直接使用天翼产品](__MACOSX/shot/99.gif)
#### 🔄 流程编排
![流程编排](__MACOSX/shot/53.png)
#### 🛠️ 协议统一管理与IDE(推荐)
![协议统一管理与IDE](__MACOSX/shot/54.png)
| ![功能截图1](/__MACOSX/shot/01.png) | ![功能截图2](/__MACOSX/shot/02.png) | ![功能截图3](/__MACOSX/shot/03.png) |
| ![功能截图4](/__MACOSX/shot/04.png) | ![功能截图5](/__MACOSX/shot/05.png) | ![功能截图6](/__MACOSX/shot/06.png) |
| ![功能截图7](/__MACOSX/shot/07.png) | ![功能截图8](/__MACOSX/shot/08.png) | ![功能截图9](/__MACOSX/shot/09.png) |
| ![功能截图10](/__MACOSX/shot/10.png) | ![功能截图11](/__MACOSX/shot/11.png) | ![功能截图12](/__MACOSX/shot/12.png) |
| ![功能截图13](/__MACOSX/shot/13.png) | ![功能截图14](/__MACOSX/shot/14.png) | ![功能截图15](/__MACOSX/shot/15.png) |
| ![功能截图16](/__MACOSX/shot/16.png) | ![功能截图17](/__MACOSX/shot/17.png) | ![功能截图18](/__MACOSX/shot/18.png) |
| ![功能截图19](/__MACOSX/shot/19.png) | ![功能截图20](/__MACOSX/shot/20.png) | ![功能截图21](/__MACOSX/shot/21.png) |
| ![功能截图22](/__MACOSX/shot/22.png) | ![功能截图23](/__MACOSX/shot/23.png) | ![功能截图24](/__MACOSX/shot/24.png) |
| ![功能截图52](/__MACOSX/shot/52.png) | ![功能截图51](/__MACOSX/shot/51.png) | ![功能截图50](/__MACOSX/shot/50.png) |
### 🔧 强大调试器
| ![调试器登录](/__MACOSX/shot/debug/login.png) | ![调试器界面](/__MACOSX/shot/debug/debug.png) | ![调试器运行](/__MACOSX/shot/debug/run.png) |
### 💬 社区交流
有任何问题加入QQ群
| ![QQ群](/__MACOSX/shot/qq.png) | ![微信群](/__MACOSX/shot/wx.png) |
## 🤝 贡献指南
我们欢迎所有形式的贡献!无论是代码、文档、问题反馈还是功能建议。
### 🚀 如何贡献
1. **Fork 项目**
```bash
# 点击右上角 Fork 按钮
```
2. **创建分支**
```bash
git checkout -b feature/your-feature-name
```
3. **提交代码**
```bash
git add .
git commit -m "feat: add your feature"
git push origin feature/your-feature-name
```
4. **创建 Pull Request**
- 在 Gitee 上创建 Pull Request
- 详细描述你的修改内容
- 等待代码审查
### 📝 代码规范
- 遵循 Java 编码规范
- 添加必要的注释和文档
- 确保代码通过所有测试
- 提交信息使用 [Conventional Commits](https://www.conventionalcommits.org/) 格式
### 🐛 问题反馈
- 使用 [Issues](https://gitee.com/nexiotplatform/Universal-IoT-Java/issues) 报告 Bug
- 提供详细的问题描述和复现步骤
- 包含系统环境信息
### 💡 功能建议
- 在 Issues 中提出新功能建议
- 详细描述功能需求和预期效果
- 欢迎讨论技术实现方案
## 📄 开源协议
### 📋 协议说明
开源版本采用 **AGPL3.0** 协议,可用于个人学习和使用,商业用途需赞助项目,获得授权。
二开项目同样遵守 AGPL3.0 协议进行开源,可以向原作者申请授权。
大学生毕设项目免费使用
### 📜 授权说明
1. **授权用户可无限期在其内部使用**
- 企业授权用户不含分公司、子公司及第三方公司
- 个人授权用户仅可个人使用,不可用于在职公司或任何第三方
2. **授权用户不可向第三方发行或转让授权源码**
- 无论有偿或无偿
- 不可申请包含本项目的授权源码的软件著作权
- 用户可申请自主二次研发部分源码的软件著作权
3. **授权用户仅可通过项目形式进行源码的二次开发**
- 完成的定制化软件必须加密打包后才可交付客户
- 如需交付项目源码,必须为客户购买对应的商业授权
### 🔒 开源和商业
开源版本暂不包含 **TCP**、**天翼物联ctaiot** 模块。可自行二次开发扩展,商用授权需获取授权。
### 🙏 致谢
感谢以下开源项目和技术平台:
- **开源框架**:若依、Antdv、jetlink、ssssssss-team
- **云平台**:阿里云、华为云、腾讯云、AEP、OneNet 等物联网平台
- **社区支持**:所有贡献者和用户的支持与反馈
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 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.
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: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

+120
View File
@@ -0,0 +1,120 @@
<?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>
</dependencies>
</project>
@@ -0,0 +1,30 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** 数据权限过滤注解 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
String alias() default "";
String created() default "";
}
@@ -0,0 +1,40 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.common.annotation;
import cn.universal.admin.common.enums.BusinessType;
import cn.universal.admin.common.enums.OperatorType;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** 自定义操作日志记录注解 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/** 模块 */
public String title() default "";
/** 功能 */
public BusinessType businessType() default BusinessType.OTHER;
/** 操作人类别 */
public OperatorType operatorType() default OperatorType.MANAGE;
/** 是否保存请求的参数 */
public boolean isSaveRequestData() default true;
}
@@ -0,0 +1,118 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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.admin.common.annotation.DataScope;
import cn.universal.admin.common.utils.SecurityUtils;
import cn.universal.admin.system.service.IIotUserService;
import cn.universal.common.domain.BaseEntity;
import cn.universal.common.utils.ReflectUtils;
import cn.universal.persistence.entity.IoTUser;
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 IIotUserService iIotUserService;
// 配置织入点
@Pointcut("@annotation(cn.universal.admin.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 = iIotUserService.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,222 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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.admin.common.annotation.Log;
import cn.universal.admin.common.enums.BusinessStatus;
import cn.universal.admin.common.enums.HttpMethod;
import cn.universal.admin.common.utils.SecurityUtils;
import cn.universal.admin.system.service.AsyncService;
import cn.universal.admin.system.service.IIotUserService;
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 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 IIotUserService iIotUserService;
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
// 配置织入点
@Pointcut("@annotation(cn.universal.admin.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 = iIotUserService.selectUserByUnionId(SecurityUtils.getUnionId());
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = ServletUtils.getClientIP();
operLog.setOperIp(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(AsyncService.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,22 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.common.enums;
/** 操作状态枚举 */
public enum BusinessStatus {
/** 成功 */
SUCCESS,
/** 失败 */
FAIL,
}
@@ -0,0 +1,46 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.common.enums;
/** 业务操作类型枚举 */
public enum BusinessType {
/** 其它 */
OTHER,
/** 新增 */
INSERT,
/** 修改 */
UPDATE,
/** 删除 */
DELETE,
/** 授权 */
GRANT,
/** 导出 */
EXPORT,
/** 导入 */
IMPORT,
/** 强退 */
FORCE,
/** 生成代码 */
GENCODE,
/** 清空数据 */
CLEAN,
}
@@ -0,0 +1,46 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.common.enums;
import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
/** HTTP请求方式枚举 */
public enum HttpMethod {
GET,
HEAD,
POST,
PUT,
PATCH,
DELETE,
OPTIONS,
TRACE;
private static final Map<String, HttpMethod> mappings = new HashMap<>(16);
static {
for (HttpMethod httpMethod : values()) {
mappings.put(httpMethod.name(), httpMethod);
}
}
@Nullable
public static HttpMethod resolve(@Nullable String method) {
return (method != null ? mappings.get(method) : null);
}
public boolean matches(String method) {
return (this == resolve(method));
}
}
@@ -0,0 +1,25 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.common.enums;
/** 操作人类别枚举 */
public enum OperatorType {
/** 其它 */
OTHER,
/** 后台用户 */
MANAGE,
/** 手机端用户 */
MOBILE
}
@@ -0,0 +1,20 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.common.service;
import cn.universal.admin.system.service.IIotUserService;
import cn.universal.persistence.entity.IoTUser;
import jakarta.annotation.Resource;
import org.springframework.cache.annotation.Cacheable;
public class BaseServiceImpl implements BaseService {
@Resource private IIotUserService iIotUserService;
@Override
@Cacheable(cacheNames = "user_parent_entity", key = "''+#unionId", unless = "#result == null")
public IoTUser queryIotUser(String unionId) {
IoTUser iotUser = iIotUserService.selectUserByUnionId(unionId);
if (iotUser.getParentUnionId() != null) {
iotUser = iIotUserService.selectUserByUnionId(iotUser.getParentUnionId());
return iotUser;
}
return iotUser;
}
}
@@ -0,0 +1,161 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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,50 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.common.utils;
import cn.universal.common.utils.StringUtils;
import cn.universal.persistence.page.PageDomain;
import cn.universal.persistence.page.TableDataInfo;
import cn.universal.persistence.page.TableSupport;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import java.util.List;
/** 分页工具类 */
public class PageUtils extends PageHelper {
/** 设置请求分页数据 */
public static void startPage() {
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) {
String orderBy = pageDomain.getOrderBy();
startPage(pageNum, pageSize, orderBy);
}
}
/** 封装分页数据 */
public static <T> TableDataInfo getDataTable(List<T> list) {
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(0);
rspData.setRows(list);
rspData.setMsg("查询成功");
if (list instanceof Page) {
rspData.setTotal((int) ((Page<?>) list).getTotal());
} else {
rspData.setTotal(list.size());
}
return rspData;
}
}
@@ -0,0 +1,143 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.common.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.universal.admin.system.service.IIotUserService;
import cn.universal.common.exception.BaseException;
import cn.universal.common.exception.IoTException;
import cn.universal.persistence.entity.IoTUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/** 安全服务工具类 */
@Component
public class SecurityUtils {
private static ApplicationContext applicationContext;
private static final Logger log = LoggerFactory.getLogger(SecurityUtils.class);
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
SecurityUtils.applicationContext = applicationContext;
}
/** 获取unionId用户 */
public static String getUnionId() {
try {
Authentication authentication = getAuthentication();
Object principal = authentication.getPrincipal();
// 如果 principal 是 JWT,从 JWT 中提取用户信息
if (principal instanceof Jwt) {
Jwt jwt = (Jwt) principal;
// 优先使用 sub 字段
String subject = jwt.getSubject();
String currentJti = jwt.getId();
log.debug("oauth2 subject={}, currentJti={}", subject, currentJti);
if (StringUtils.hasText(subject)) {
return subject; // 如果 sub 就是 unionId,直接返回
}
}
// 如果无法获取用户信息,抛出异常
throw new BaseException("无法获取用户信息", HttpStatus.HTTP_UNAUTHORIZED + "");
} catch (Exception e) {
throw new IoTException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED);
}
}
/** 获取当前认证用户信息(从 JWT 中获取) */
public static IoTUser getIoTUnionUser() {
try {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof Jwt jwt) {
// 优先使用 sub 字段
String subject = jwt.getSubject();
if (StringUtils.hasText(subject)) {
// 从数据库加载用户信息
return getIotUserByUnionId(subject);
}
// 如果 sub 为空,尝试从 unionId claim 获取
String unionId = jwt.getClaimAsString("unionId");
if (StringUtils.hasText(unionId)) {
return getIotUserByUnionId(unionId);
}
}
} catch (Exception e) {
log.error("获取当前用户信息失败", e);
}
return null;
}
/** 根据用户名获取 IoTUser */
private static IoTUser getIotUserByUsername(String username) {
if (StrUtil.isBlank(username)) {
throw new BaseException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED + "");
}
try {
IIotUserService iIotUserService = applicationContext.getBean(IIotUserService.class);
return iIotUserService.selectUserByUserName(username);
} catch (Exception e) {
throw new BaseException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED + "");
}
}
/** 根据 unionId 获取 IoTUser */
private static IoTUser getIotUserByUnionId(String unionId) {
if (StrUtil.isBlank(unionId)) {
throw new BaseException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED + "");
}
try {
IIotUserService iIotUserService = applicationContext.getBean(IIotUserService.class);
return iIotUserService.selectUserByUnionId(unionId);
} catch (Exception e) {
throw new BaseException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED + "");
}
}
/** 获取Authentication */
public static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* 生成BCryptPasswordEncoder密码
*
* @param password 密码
* @return 加密字符串
*/
public static String encryptPassword(String password) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.encode(password);
}
/**
* 判断密码是否相同
*
* @param rawPassword 真实密码
* @param encodedPassword 加密后字符
* @return 结果
*/
public static boolean matchesPassword(String rawPassword, String encodedPassword) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
@@ -0,0 +1,75 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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.common.utils.SecurityUtils;
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 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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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.admin.system.web.BaseController;
import cn.universal.persistence.page.TableDataInfo;
import cn.universal.persistence.query.AjaxResult;
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,64 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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;
/**
* 网络组件配置
*
* @version 1.0 @Author Aleo
* @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,82 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.network.doc;
/**
* 网络组件API文档
*
* @version 1.0 @Author Aleo
* @since 2025/1/20
*/
public class NetworkApiDoc {
/**
* 网络组件管理API文档
*
* <p>基础路径: /admin/network
*
* <p>1. 查询网络组件列表 GET /admin/network/list 参数: - page: 页码 (可选,默认1) - size: 每页大小 (可选,默认10) - type:
* 网络类型 (可选,单个类型,如 TCP_CLIENT) - types: 网络类型列表 (可选,多个类型,如 ["MQTT_CLIENT", "MQTT_SERVER"]) - name:
* 网络组件名称 (可选,模糊查询) - productKey: 产品Key (可选) - state: 状态 (可选,true/false) - unionId: 唯一标识 (可选)
*
* <p>2. 查询网络组件列表(支持多个类型) GET /admin/network/list/multi-type 参数: - page: 页码 (可选,默认1) - size: 每页大小
* (可选,默认10) - type: 网络类型 (可选,支持逗号分隔多个类型,如 "MQTT_CLIENT,MQTT_SERVER") - name: 网络组件名称 (可选,模糊查询) -
* productKey: 产品Key (可选) - state: 状态 (可选,true/false) - unionId: 唯一标识 (可选)
*
* <p>3. 根据ID查询网络组件 GET /admin/network/{id} 参数: - id: 网络组件ID (路径参数)
*
* <p>4. 新增网络组件 POST /admin/network 请求体: { "type": "TCP_SERVER", "unionId": "unique_id",
* "productKey": "product_key", "name": "网络组件名称", "description": "详细描述", "state": false,
* "configuration": "{\"host\":\"0.0.0.0\",\"port\":6372,\"ssl\":false}", "createUser": "admin" }
*
* <p>5. 修改网络组件 PUT /admin/network 请求体: { "id": 1, "type": "TCP_SERVER", "unionId": "unique_id",
* "productKey": "product_key", "name": "网络组件名称", "description": "详细描述", "state": false,
* "configuration": "{\"host\":\"0.0.0.0\",\"port\":6372,\"ssl\":false}" }
*
* <p>6. 删除网络组件 DELETE /admin/network/{id} 参数: - id: 网络组件ID (路径参数)
*
* <p>7. 批量删除网络组件 DELETE /admin/network/batch/{ids} 参数: - ids: 网络组件ID数组 (路径参数,逗号分隔)
*
* <p>8. 启动网络组件 POST /admin/network/start/{id} 参数: - id: 网络组件ID (路径参数)
*
* <p>9. 停止网络组件 POST /admin/network/stop/{id} 参数: - id: 网络组件ID (路径参数)
*
* <p>10. 重启网络组件 POST /admin/network/restart/{id} 参数: - id: 网络组件ID (路径参数)
*
* <p>11. 获取网络类型列表 GET /admin/network/types
*
* <p>12. 验证网络组件配置 POST /admin/network/validate 请求体: { "type": "TCP_SERVER", "unionId":
* "unique_id", "name": "网络组件名称", "configuration":
* "{\"host\":\"0.0.0.0\",\"port\":6372,\"ssl\":false}" }
*
* <p>响应格式: { "code": 200, "msg": "操作成功", "data": { // 具体数据 } }
*
* <p>网络类型说明: - TCP_CLIENT: TCP客户端 - TCP_SERVER: TCP服务端 - MQTT_CLIENT: MQTT客户端 - MQTT_SERVER:
* MQTT服务端
*
* <p>配置示例:
*
* <p>TCP_SERVER配置: { "allIdleTime": 0, "allowInsert": false, "alwaysPreDecode": false,
* "decoderType": "STRING", "host": "0.0.0.0", "idleInterval": 0, "onlyCache": false,
* "parserConfiguration": { "byteOrderLittle": true, "delimited": "]", "delimitedMaxlength": 1024,
* "failFast": true }, "parserType": "DELIMITED", "port": 6372, "productKey":
* "630477f1cd68445d90b04653", "readerIdleTime": 360, "readTimeout": 0, "sendTimeout": 0, "ssl":
* false, "writerIdleTime": 0 }
*
* <p>MQTT_SERVER配置: { "autoReconnect": true, "cleanSession": true, "clientIdPrefix": "univ_cli_",
* "defaultQos": 1, "enabled": true, "host": "tcp://125.210.48.96:1883", "id":
* "6863896d75386b5c1e7e908c", "keepAliveInterval": 60, "password": "univiot@2025!", "productKey":
* "local", "ssl": false, "subscribeTopics": "$univ_cli/up/property/+/+", "username": "univ_cli",
* "connectTimeout": 30 }
*/
}
@@ -0,0 +1,85 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.network.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 网络类型枚举
*
* @version 1.0 @Author Aleo
* @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,146 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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接口
*
* @version 1.0 @Author Aleo
* @since 2025/1/20
*/
public interface INetworkService {
boolean start(String productKey);
boolean stop(String productKey);
Object get(String productKey);
boolean del(String productKey);
List<NetworkVO> selectNetworkList(NetworkBO bo);
NetworkVO getDetail(Long id);
List<IoTDevice> queryMileSightList(NetworkBO bo);
R insertDevInstance(IoTDeviceBO devInstancebo);
R deleteDevInstanceByIds(String[] ids);
void reloadTcpClient(String applicationId);
/**
* 查询网络组件列表
*
* @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,884 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.network.service.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.universal.admin.common.utils.SecurityUtils;
import cn.universal.admin.network.service.INetworkService;
import cn.universal.admin.network.utils.NetworkTypeUtil;
import cn.universal.common.constant.IoTConstant.DownCmd;
import cn.universal.common.constant.NetworkTypeConstants;
import cn.universal.common.domain.R;
import cn.universal.common.enums.NetworkType;
import cn.universal.common.exception.IoTException;
import cn.universal.core.service.IoTDownlFactory;
import cn.universal.mqtt.protocol.third.ThirdMQTTServerManager;
import cn.universal.persistence.entity.IoTDevice;
import cn.universal.persistence.entity.IoTProduct;
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.IoTProductVO;
import cn.universal.persistence.entity.vo.NetworkVO;
import cn.universal.persistence.mapper.IoTDeviceFenceRelMapper;
import cn.universal.persistence.mapper.IoTDeviceMapper;
import cn.universal.persistence.mapper.IoTProductMapper;
import cn.universal.persistence.mapper.NetworkMapper;
import cn.universal.persistence.query.NetworkQuery;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import jakarta.annotation.Resource;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
/**
* 网络组件Service实现类
*
* @version 1.0 @Author Aleo
* @since 2025/1/20
*/
@Slf4j
@Service
public class NetworkServiceImpl implements INetworkService {
@Autowired
private NetworkMapper networkMapper;
@Resource
private NetworkMapper netWorkMapper;
@Resource
private IoTDeviceMapper ioTDeviceMapper;
@Resource
private IoTProductMapper ioTProductMapper;
@Resource
private IoTDeviceFenceRelMapper ioTDeviceFenceRelMapper;
@Autowired(required = false)
private ThirdMQTTServerManager mqttServerManager;
@Override
public boolean start(String productKey) {
return true;
}
@Override
public boolean stop(String productKey) {
return true;
}
@Override
public Object get(String productKey) {
return null;
}
@Override
public boolean del(String productKey) {
Example ex = new Example(Network.class);
ex.createCriteria().andEqualTo("productKey", productKey);
return netWorkMapper.deleteByExample(ex) > 0;
}
@Override
public List<NetworkVO> selectNetworkList(NetworkBO bo) {
return netWorkMapper.selectNetworkListV1(bo);
}
@Override
public NetworkVO getDetail(Long id) {
return netWorkMapper.selectById(id);
}
@Override
public List<IoTDevice> queryMileSightList(NetworkBO bo) {
return ioTDeviceMapper.queryMileSightList(bo);
}
@Override
public R insertDevInstance(IoTDeviceBO devInstancebo) {
JSONObject downObj = new JSONObject();
// 预添加归属为空
downObj.set("appUnionId", "");
// 产品编号
downObj.set("productKey", devInstancebo.getProductKey());
// 设备序列号
downObj.set("deviceId", devInstancebo.getDeviceId());
// Lora密匙
downObj.set("deviceKey", devInstancebo.getSecretKey());
// 添加
downObj.set("cmd", DownCmd.DEV_ADD.name());
// 说明
downObj.set("detail", devInstancebo.getDetail());
JSONObject ob = new JSONObject();
// 设备实例名称
ob.set("deviceName", devInstancebo.getDeviceName());
// 设备序列号
ob.set("imei", devInstancebo.getDeviceId());
// 维度
ob.set("latitude", devInstancebo.getLatitude());
// 经度
ob.set("longitude", devInstancebo.getLongitude());
// 添加额外参数
for (Map.Entry<String, Object> entry : devInstancebo.getOtherParams().entrySet()) {
ob.set(entry.getKey(), entry.getValue());
}
downObj.set("data", ob);
IoTProduct ioTProduct = ioTProductMapper.getProductByProductKey(devInstancebo.getProductKey());
return IoTDownlFactory.getIDown(ioTProduct.getThirdPlatform()).doAction(downObj);
}
@Override
public R deleteDevInstanceByIds(String[] ids) {
R result = new R();
// 获取网关eui
String id1 = ids[0];
IoTDevice ioTDevice1 = ioTDeviceMapper.selectDevInstanceById(id1);
String gatewayEUI = JSONUtil.parseObj(ioTDevice1.getConfiguration()).getStr("gatewayEUI");
if (StrUtil.isBlank(gatewayEUI)) {
return R.error("删除失败,缺少网关EUI");
}
for (String id : ids) {
IoTDevice ioTDevice = ioTDeviceMapper.selectDevInstanceById(id);
JSONObject downRequest = new JSONObject();
downRequest.set("appUnionId", "");
downRequest.set("productKey", ioTDevice.getProductKey());
downRequest.set("deviceId", ioTDevice.getDeviceId());
downRequest.set("cmd", DownCmd.DEV_DEL.name());
JSONObject ob = new JSONObject();
ob.set("gatewayEUI", gatewayEUI);
downRequest.set("data", ob);
IoTProduct ioTProduct = ioTProductMapper.getProductByProductKey(ioTDevice.getProductKey());
result = IoTDownlFactory.getIDown(ioTProduct.getThirdPlatform()).doAction(downRequest);
if (!result.getCode().equals(0)) {
return R.error(
"设备名称: " + ioTDevice.getDeviceName() + " 删除失败,原因: " + result.getMsg());
}
ioTDeviceFenceRelMapper.deleteFenceInstance(ioTDevice.getIotId());
}
return R.ok("删除成功");
}
@Override
public void reloadTcpClient(String applicationId) {
}
@Override
public List<NetworkBO> selectNetworkList(NetworkQuery query) {
// 验证类型参数
// 如果type参数不为空,将其转换为types列表
if (StrUtil.isNotBlank(query.getType())) {
List<String> types = NetworkTypeUtil.parseTypes(query.getType());
// 验证所有类型是否有效
if (!NetworkTypeUtil.isValidTypes(types)) {
List<String> invalidTypes = NetworkTypeUtil.getInvalidTypes(types);
throw new IoTException("无效的网络类型: " + String.join(", ", invalidTypes));
}
// 设置types列表,清空type字段
query.setTypes(types);
query.setType(null);
}
// 计算分页参数
Page<IoTProductVO> page = PageHelper.startPage(query.getPageNum(), query.getPageSize());
List<NetworkBO> list = networkMapper.selectNetworkList(query);
for (NetworkBO bo : list) {
// 启用/停用状态
bo.setEnableName(Boolean.TRUE.equals(bo.getState()) ? "已启用" : "已停用");
// 实际运行状态
String type = bo.getType();
String productKey = bo.getProductKey();
String unionId = bo.getUnionId();
if (NetworkType.TCP_CLIENT.getId().equals(type) || NetworkType.TCP_SERVER.getId()
.equals(type)) {
} else if (NetworkType.MQTT_CLIENT.getId().equals(type) || NetworkType.MQTT_SERVER.getId()
.equals(type)) {
if (mqttServerManager != null && mqttServerManager.isConnected(unionId)) {
bo.setStateName("已启动");
bo.setRunning(Boolean.TRUE);
} else {
bo.setStateName("未启动");
bo.setRunning(Boolean.FALSE);
}
} else {
bo.setStateName("未知");
bo.setRunning(Boolean.FALSE);
}
}
return list;
}
@Override
public NetworkVO selectNetworkById(Integer id) {
Network network = networkMapper.selectNetworkById(id);
if (network == null) {
return null;
}
NetworkVO vo = new NetworkVO();
// 复制基本属性
vo.setId(network.getId());
vo.setType(network.getType());
vo.setUnionId(network.getUnionId());
vo.setProductKey(network.getProductKey());
vo.setName(network.getName());
vo.setDescription(network.getDescription());
vo.setCreateDate(network.getCreateDate());
vo.setCreateUser(network.getCreateUser());
vo.setState(network.getState());
vo.setConfiguration(network.getConfiguration());
// 类型名称
switch (network.getType()) {
case NetworkTypeConstants.TCP_CLIENT:
vo.setTypeName(NetworkType.TCP_CLIENT.getDescription());
break;
case NetworkTypeConstants.TCP_SERVER:
vo.setTypeName(NetworkType.TCP_SERVER.getDescription());
break;
case NetworkTypeConstants.MQTT_CLIENT:
vo.setTypeName(NetworkType.MQTT_CLIENT.getDescription());
break;
case NetworkTypeConstants.MQTT_SERVER:
vo.setTypeName(NetworkType.MQTT_SERVER.getDescription());
break;
default:
vo.setTypeName(network.getType());
}
// 启用/停用状态
vo.setEnableName(Boolean.TRUE.equals(network.getState()) ? "已启用" : "已停用");
// 实际运行状态
String type = network.getType();
String productKey = network.getProductKey();
String unionId = network.getUnionId();
if (NetworkType.TCP_CLIENT.getId().equals(type) || NetworkType.TCP_SERVER.getId()
.equals(type)) {
//TODO 开源版本无
} else if (NetworkType.MQTT_CLIENT.getId().equals(type) || NetworkType.MQTT_SERVER.getId()
.equals(type)) {
if (mqttServerManager != null && mqttServerManager.isConnected(unionId)) {
vo.setStateName("已启动");
vo.setRunning(Boolean.TRUE);
} else {
vo.setStateName("未启动");
vo.setRunning(Boolean.FALSE);
}
} else {
vo.setStateName("未知");
vo.setRunning(Boolean.FALSE);
}
return vo;
}
/**
* 检查端口是否被占用或为常用端口
*/
private void checkPortAvailable(Network network, boolean isUpdate) {
if (StrUtil.isBlank(network.getConfiguration())) {
return;
}
JSONObject config = JSONUtil.parseObj(network.getConfiguration());
Integer port = config.getInt("port");
if (port == null) {
return;
}
// 常用端口
Set<Integer> commonPorts = new HashSet<>();
int[] arr = {80, 443, 3306, 6379, 8080, 22, 21, 25, 53, 123, 1521, 5432, 8888, 9000, 5000, 7001,
27017};
for (int p : arr) {
commonPorts.add(p);
}
if (commonPorts.contains(port)) {
throw new RuntimeException("端口 " + port + " 为常用端口,禁止使用");
}
// 检查数据库是否被占用
Integer excludeId = isUpdate ? network.getId() : null;
int count = networkMapper.selectTcpNetworkByPortCount(port, excludeId);
if (count > 0) {
throw new RuntimeException("端口 " + port + " 已被其他网络组件占用");
}
}
@Override
@Transactional
public int insertNetwork(Network network) {
// 验证配置
if (!validateNetworkConfig(network)) {
throw new RuntimeException("网络组件配置验证失败");
}
// 检查配置唯一性
checkConfigurationUniqueness(network, null);
// 设置默认值
if (network.getState() == null) {
network.setState(false);
}
// 设置创建用户(如果未设置)
if (StrUtil.isBlank(network.getCreateUser())) {
// 从当前登录用户获取 unionId
network.setCreateUser(SecurityUtils.getUnionId());
}
network.setUnionId(IdUtil.objectId());
network.setState(false);
// 新增TCP_SERVER时,必须指定productKey,并且不能重复绑定
if (NetworkType.TCP_SERVER.getId().equals(network.getType()) || NetworkType.TCP_CLIENT.getId()
.equals(network.getType())) {
if (StrUtil.isBlank(network.getProductKey())) {
throw new RuntimeException("TCP服务组件必须选择产品");
}
network.setUnionId(network.getProductKey());
// 检查是否已被绑定
int refCount = ioTProductMapper.countByNetworkUnionId(network.getProductKey());
if (refCount > 0) {
throw new RuntimeException("该产品已绑定其他TCP服务组件,不能重复绑定");
}
}
checkPortAvailable(network, false);
int result = networkMapper.insertNetwork(network);
// 新增成功后,若为TCP_SERVER,更新iot_product表
if (result > 0 && NetworkType.TCP_SERVER.getId().equals(network.getType())) {
ioTProductMapper.updateNetworkUnionIdByProductKey(network.getProductKey(),
network.getProductKey());
}
return result;
}
@Override
@Transactional
public int updateNetwork(Network network) {
if (network == null || network.getId() == null) {
throw new RuntimeException("网络组件配置验证失败");
}
// 验证配置
if (!validateNetworkConfig(network)) {
throw new RuntimeException("网络组件配置验证失败");
}
// 检查权限(只能操作自己创建的网络组件,除非是管理员)
Network existNetwork = networkMapper.selectNetworkById(network.getId());
if (existNetwork == null) {
throw new RuntimeException("网络组件不存在");
}
IoTUser currentUser = SecurityUtils.getIoTUnionUser();
if (!existNetwork.getCreateUser().equals(currentUser.getUnionId()) && !currentUser.isAdmin()) {
throw new RuntimeException("不能操作不是自己的网络组件");
}
String type = existNetwork.getType();
if ((NetworkType.TCP_SERVER.getId().equals(type) || NetworkType.TCP_CLIENT.getId()
.equals(type))) {
// 只有当unionId发生变化时,才做"已被产品关联且产品下有设备时禁止修改"的校验
String oldProductKey = existNetwork.getProductKey();
String networkProductKey = network.getProductKey();
String current = ioTProductMapper.findProductKeyByNetworkUnionId(oldProductKey);
if (current != null && !oldProductKey.equals(networkProductKey)) {
int deviceCount = ioTDeviceMapper.countByProductKey(oldProductKey);
if (deviceCount > 0) {
throw new RuntimeException("原关联产品有设备,无法变更");
}
}
}
// 检查配置唯一性(排除自己)
checkConfigurationUniqueness(network, network.getId());
checkPortAvailable(network, true);
int count = networkMapper.updateNetwork(network);
if (count > 0 && NetworkType.TCP_SERVER.getId().equals(network.getType())) {
ioTProductMapper.updateNetworkUnionIdByProductKey(network.getProductKey(),
network.getProductKey());
}
return count;
}
@Override
@Transactional
public int deleteNetworkById(Integer id) {
Network network = networkMapper.selectNetworkById(id);
if (network == null) {
throw new RuntimeException("网络组件不存在");
}
String type = network.getType();
// 只对TCP_SERVER和TCP_CLIENT做判断
if (NetworkType.TCP_SERVER.getId().equals(type) || NetworkType.TCP_CLIENT.getId()
.equals(type)) {
// 查找是否有关联产品
String productKey = ioTProductMapper.findProductKeyByNetworkUnionId(network.getUnionId());
if (productKey != null) {
// 查找该产品下是否有设备
int deviceCount = ioTDeviceMapper.countByProductKey(productKey);
if (deviceCount > 0) {
throw new RuntimeException("该网络组件已被产品关联且产品下存在设备,无法删除");
}
}
}
return networkMapper.deleteNetworkById(id);
}
@Override
@Transactional
public int deleteNetworkByIds(Integer[] ids) {
return networkMapper.deleteNetworkByIds(ids);
}
@Override
@Transactional
public int startNetwork(Integer id) {
Network network = networkMapper.selectNetworkById(id);
if (network == null) {
throw new RuntimeException("网络组件不存在");
}
String type = network.getType();
JSONObject config = JSONUtil.parseObj(network.getConfiguration());
if (NetworkType.TCP_CLIENT.getId().equals(type) || NetworkType.TCP_SERVER.getId()
.equals(type)) {
Integer port = config.getInt("port");
if (port == null) {
throw new RuntimeException("TCP网络组件启动/重启时端口不能为空");
}
} else if (NetworkType.MQTT_CLIENT.getId().equals(type) || NetworkType.MQTT_SERVER.getId()
.equals(type)) {
String host = config.getStr("host");
if (StrUtil.isBlank(host)) {
throw new RuntimeException("MQTT网络组件启动/重启时host不能为空");
}
}
try {
// 根据网络类型调用相应的启动逻辑
boolean success = startNetworkByType(network);
if (success) {
log.info("启动网络组件成功: {}", network.getName());
return 1;
} else {
throw new RuntimeException("启动失败: " + network.getName());
}
} catch (Exception e) {
log.error("启动网络组件失败: {}", network.getName(), e);
throw new RuntimeException("启动网络组件失败: " + e.getMessage());
}
}
@Override
@Transactional
public int stopNetwork(Integer id) {
Network network = networkMapper.selectNetworkById(id);
if (network == null) {
throw new RuntimeException("网络组件不存在");
}
try {
// 根据网络类型调用相应的停止逻辑
boolean success = stopNetworkByType(network);
if (success) {
log.info("停止网络组件成功: {}", network.getName());
return 1;
} else {
throw new RuntimeException("停止网络组件失败: " + network.getName());
}
} catch (Exception e) {
log.error("停止网络组件失败: {}", network.getName(), e);
throw new RuntimeException("停止网络组件失败: " + e.getMessage());
}
}
@Override
@Transactional
public int restartNetwork(Integer id) {
Network network = networkMapper.selectNetworkById(id);
if (network == null) {
throw new RuntimeException("网络组件不存在");
}
String type = network.getType();
JSONObject config = JSONUtil.parseObj(network.getConfiguration());
if (NetworkType.TCP_CLIENT.getId().equals(type) || NetworkType.TCP_SERVER.getId()
.equals(type)) {
Integer port = config.getInt("port");
if (port == null) {
throw new RuntimeException("TCP网络组件启动/重启时端口不能为空");
}
} else if (NetworkType.MQTT_CLIENT.getId().equals(type) || NetworkType.MQTT_SERVER.getId()
.equals(type)) {
String host = config.getStr("host");
if (StrUtil.isBlank(host)) {
throw new RuntimeException("MQTT网络组件启动/重启时host不能为空");
}
}
try {
// 根据网络类型调用相应的重启逻辑
boolean success = restartNetworkByType(network);
if (success) {
log.info("重启网络组件成功: {}", network.getName());
// 重启后状态应该是启动的
return 1;
} else {
throw new RuntimeException("重启网络组件失败: " + network.getName());
}
} catch (Exception e) {
log.error("重启网络组件失败: {}", network.getName(), e);
throw new RuntimeException("重启网络组件失败: " + e.getMessage());
}
}
@Override
public List<String> getNetworkTypes() {
return Arrays.asList(NetworkType.TCP_CLIENT.getId(), NetworkType.TCP_SERVER.getId(),
NetworkType.MQTT_CLIENT.getId(), NetworkType.MQTT_SERVER.getId());
}
/**
* 验证网络类型是否有效
*
* @param type 网络类型
* @return 是否有效
*/
private boolean isValidNetworkType(String type) {
return getNetworkTypes().contains(type);
}
/**
* 验证多个网络类型是否都有效
*
* @param types 网络类型列表
* @return 是否都有效
*/
private boolean isValidNetworkTypes(List<String> types) {
return types.stream().allMatch(this::isValidNetworkType);
}
@Override
public boolean validateNetworkConfig(Network network) {
if (network == null) {
return false;
}
// 验证必填字段
if (StrUtil.isBlank(network.getType()) || 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;
}
/**
* 检查网络组件配置的唯一性
*
* @param network 网络组件
* @param excludeId 排除的网络组件ID(用于更新时排除自己)
*/
private void checkConfigurationUniqueness(Network network, Integer excludeId) {
if (StrUtil.isBlank(network.getConfiguration())) {
return;
}
try {
JSONObject config = JSONUtil.parseObj(network.getConfiguration());
String type = network.getType();
// TCP类型检查端口唯一性
if (NetworkType.TCP_CLIENT.getId().equals(type) || NetworkType.TCP_SERVER.getId()
.equals(type)) {
Integer port = config.getInt("port");
if (port != null) {
List<Network> existingNetworks = networkMapper.selectTcpNetworkByPort(port, excludeId);
if (!existingNetworks.isEmpty()) {
Network existingNetwork = existingNetworks.get(0);
throw new RuntimeException(
"端口 " + port + " 已被 " + existingNetwork.getName() + " 网络组件使用");
}
}
}
// MQTT类型检查主机和用户名组合唯一性
if (NetworkType.MQTT_CLIENT.getId().equals(type) || NetworkType.MQTT_SERVER.getId()
.equals(type)) {
String host = config.getStr("host");
String username = config.getStr("username");
if (StrUtil.isNotBlank(host) && StrUtil.isNotBlank(username)) {
List<Network> existingNetworks = networkMapper.selectMqttNetworkByHostAndUsername(host,
username, excludeId);
if (!existingNetworks.isEmpty()) {
Network existingNetwork = existingNetworks.get(0);
throw new RuntimeException("主机 " + host + " 和用户名 " + username + " 的组合已被 "
+ existingNetwork.getName() + " 网络组件使用");
}
}
}
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw e;
}
log.warn("检查网络组件配置唯一性时发生错误: {}", e.getMessage());
}
}
/**
* 根据网络类型启动网络组件
*
* @param network 网络组件
* @return 是否成功
*/
private boolean startNetworkByType(Network network) {
String type = network.getType();
try {
switch (type) {
case NetworkTypeConstants.TCP_CLIENT:
case NetworkTypeConstants.TCP_SERVER:
return startTcpNetwork(network);
case NetworkTypeConstants.MQTT_CLIENT:
case NetworkTypeConstants.MQTT_SERVER:
return startMqttNetwork(network);
default:
log.warn("不支持的网络类型: {}", type);
return false;
}
} catch (Exception e) {
log.error("启动网络组件失败: type={}, name={}", type, network.getName(), e);
return false;
}
}
/**
* 根据网络类型停止网络组件
*
* @param network 网络组件
* @return 是否成功
*/
private boolean stopNetworkByType(Network network) {
String type = network.getType();
try {
switch (type) {
case NetworkTypeConstants.TCP_CLIENT:
case NetworkTypeConstants.TCP_SERVER:
return stopTcpNetwork(network);
case NetworkTypeConstants.MQTT_CLIENT:
case NetworkTypeConstants.MQTT_SERVER:
return stopMqttNetwork(network);
default:
log.warn("不支持的网络类型: {}", type);
return false;
}
} catch (Exception e) {
log.error("停止网络组件失败: type={}, name={}", type, network.getName(), e);
return false;
}
}
/**
* 根据网络类型重启网络组件
*
* @param network 网络组件
* @return 是否成功
*/
private boolean restartNetworkByType(Network network) {
String type = network.getType();
try {
switch (type) {
case NetworkTypeConstants.TCP_CLIENT:
case NetworkTypeConstants.TCP_SERVER:
return restartTcpNetwork(network);
case NetworkTypeConstants.MQTT_CLIENT:
case NetworkTypeConstants.MQTT_SERVER:
return restartMqttNetwork(network);
default:
log.warn("不支持的网络类型: {}", type);
return false;
}
} catch (Exception e) {
log.error("重启网络组件失败: type={}, name={}", type, network.getName(), e);
return false;
}
}
/**
* 启动TCP网络组件
*
* @param network 网络组件
* @return 是否成功
*/
private boolean startTcpNetwork(Network network) {
try {
//TODO 开源版本无
return true;
} catch (Exception e) {
log.error("启动TCP网络组件失败: productKey={}", "", e);
return false;
}
}
/**
* 停止TCP网络组件
*
* @param network 网络组件
* @return 是否成功
*/
private boolean stopTcpNetwork(Network network) {
String productKey = network.getProductKey();
if (StrUtil.isBlank(productKey)) {
log.error("TCP网络组件缺少productKey: {}", network.getName());
return false;
}
try {
return true;
} catch (Exception e) {
log.error("停止TCP网络组件失败: productKey={}", "", e);
return false;
}
}
/**
* 重启TCP网络组件
*
* @param network 网络组件
* @return 是否成功
*/
private boolean restartTcpNetwork(Network network) {
try {
return true;
} catch (Exception e) {
log.error("重启TCP网络组件失败: productKey={}", "", e);
return false;
}
}
/**
* 启动MQTT网络组件
*
* @param network 网络组件
* @return 是否成功
*/
private boolean startMqttNetwork(Network network) {
if (mqttServerManager == null) {
log.error("MQTT服务管理器未注入,无法启动MQTT网络组件: {}", network.getName());
return false;
}
String unionId = network.getUnionId();
if (StrUtil.isBlank(unionId)) {
log.error("MQTT网络组件缺少unionId: {}", network.getName());
return false;
}
try {
boolean success = mqttServerManager.startMqttClient(unionId);
if (success) {
log.info("MQTT网络组件启动成功: unionId={}", unionId);
} else {
log.error("MQTT网络组件启动失败: unionId={}", unionId);
}
return success;
} catch (Exception e) {
log.error("启动MQTT网络组件失败: unionId={}", unionId, e);
return false;
}
}
/**
* 停止MQTT网络组件
*
* @param network 网络组件
* @return 是否成功
*/
private boolean stopMqttNetwork(Network network) {
if (mqttServerManager == null) {
log.error("MQTT服务管理器未注入,无法停止MQTT网络组件: {}", network.getName());
return false;
}
String unionId = network.getUnionId();
if (StrUtil.isBlank(unionId)) {
log.error("MQTT网络组件缺少unionId: {}", network.getName());
return false;
}
try {
boolean success = mqttServerManager.stopMqttClient(unionId);
if (success) {
log.info("MQTT网络组件停止成功: unionId={}", unionId);
} else {
log.error("MQTT网络组件停止失败: unionId={}", unionId);
}
return success;
} catch (Exception e) {
log.error("停止MQTT网络组件失败: unionId={}", unionId, e);
return false;
}
}
/**
* 重启MQTT网络组件
*
* @param network 网络组件
* @return 是否成功
*/
private boolean restartMqttNetwork(Network network) {
if (mqttServerManager == null) {
log.error("MQTT服务管理器未注入,无法重启MQTT网络组件: {}", network.getName());
return false;
}
String unionId = network.getUnionId();
if (StrUtil.isBlank(unionId)) {
log.error("MQTT网络组件缺少unionId: {}", network.getName());
return false;
}
try {
boolean success = mqttServerManager.restartMqttClient(unionId);
if (success) {
log.info("MQTT网络组件重启成功: unionId={}", unionId);
} else {
log.error("MQTT网络组件重启失败: unionId={}", unionId);
}
return success;
} catch (Exception e) {
log.error("重启MQTT网络组件失败: unionId={}", unionId, e);
return false;
}
}
@Override
public IoTDevice getDeviceById(String id) {
return ioTDeviceMapper.selectDevInstanceById(id);
}
}
@@ -0,0 +1,243 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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;
/**
* 网络组件配置工具类
*
* @version 1.0 @Author Aleo
* @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,130 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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;
/**
* 网络类型工具类
*
* @version 1.0 @Author Aleo
* @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");
/**
* 解析网络类型字符串,支持逗号分隔的多个类型
*
* @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, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.universal.admin.network.utils;
import cn.universal.admin.common.utils.SecurityUtils;
import cn.universal.persistence.entity.IoTUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** 用户上下文工具类 用于获取当前登录用户信息 @Author Aleo */
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,461 @@
/*
*
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
*
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: Aleo
* @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.common.annotation.Log;
import cn.universal.admin.common.enums.BusinessType;
import cn.universal.admin.common.utils.SecurityUtils;
import cn.universal.admin.network.service.INetworkService;
import cn.universal.admin.system.web.BaseController;
import cn.universal.common.domain.R;
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 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
*
* @version 1.0 @Author Aleo
* @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
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
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}")
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}")
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}")
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}")
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}")
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("/milesight/list")
public TableDataInfo queryMileSightList(NetworkBO bo) {
startPage();
// 设置用户权限过滤
bo.setCreateUser(SecurityUtils.getUnionId());
List<IoTDevice> list = iNetworkService.queryMileSightList(bo);
return getDataTable(list);
}
/** 新增设备 */
@PostMapping("/milesight/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("/milesight/{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获取相关的信息进行权限校验
// 这里可能需要额外的业务逻辑来实现权限校验
iNetworkService.reloadTcpClient(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,182 @@
/*
*
* 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.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.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;
/**
* @version 1.0 @Author Aleo
* @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");
R r = IoTDownlFactory.getIDown(h.getThirdPlatform()).doAction(param);
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,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,41 @@
/*
*
* 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.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);
}

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