合并最新版本代码
@@ -0,0 +1,65 @@
|
||||
git######################################################################
|
||||
|
||||
classpath.txt
|
||||
|
||||
# 1. 清除所有缓存(保留本地文件)
|
||||
#git rm -r --cached .
|
||||
# 或清除单个文件:git rm --cached <文件名>
|
||||
|
||||
# 2. 重新添加所有文件(应用新规则)
|
||||
#git add .
|
||||
|
||||
# 3. 提交更改
|
||||
#git commit -m "更新 .gitignore 规则"
|
||||
|
||||
###########################################
|
||||
.DS_Store
|
||||
.DS_Store/
|
||||
.gradle
|
||||
/build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
######################################################################
|
||||
# IDE
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
uploadPath/
|
||||
|
||||
### NetBeans ###
|
||||
nbproject/private/
|
||||
build/*
|
||||
nbbuild/
|
||||
dist/
|
||||
nbdist/
|
||||
.nb-gradle/
|
||||
######################################################################
|
||||
# Others
|
||||
*.log
|
||||
*.xml.versionsBackup
|
||||
|
||||
!*/build/*.java
|
||||
!*/build/*.html
|
||||
!*/build/*.xml
|
||||
/.vscode/settings.json
|
||||
/logs/
|
||||
/.specstory/
|
||||
/cn-universal-web/logs/cn-universal/sys-error.*.gz
|
||||
/cn-universal-web/logs/cn-universal/sys-info.*.gz
|
||||
/cn-universal-web/logs/cn-universal/tcp.2025-*.gz
|
||||
/cn-universal-web/logs/cn-universal/api.2025-*.gz
|
||||
/.cursorindexingignore
|
||||
@@ -0,0 +1,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/>.
|
||||
@@ -0,0 +1,345 @@
|
||||
# 🚀 NexIoT物联网平台
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 一款基于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动手实践更高效,你在实际操作中会有更多收获
|
||||
|
||||
### 当然你也可以购买与作者同款设备进行学习,另外请注意用电安全
|
||||
|
||||
| | | |
|
||||
|------------------------------|------------------------------|-----------------------------|
|
||||
|  |  |  |
|
||||
|
||||
## 🎥 视频教程
|
||||
|
||||
我们提供了详细的视频教程,帮助您快速上手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电表的编写
|
||||
|
||||
|
||||
### 🎯 更多教程
|
||||
|
||||
我们正在持续制作更多设备的接入教程,尽情期待
|
||||
|
||||
## 📸 界面展示
|
||||
|
||||
### 🏗️ 架构图
|
||||
|
||||

|
||||
|
||||
### 🚀 部署图
|
||||
|
||||

|
||||
|
||||
### 🖥️ 功能截图
|
||||
|
||||
#### 🌐 直接使用天翼产品
|
||||
|
||||

|
||||
|
||||
#### 🔄 流程编排
|
||||
|
||||

|
||||
|
||||
#### 🛠️ 协议统一管理与IDE(推荐)
|
||||
|
||||

|
||||
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|
||||
|  |  |  |
|
||||
|
||||
### 🔧 强大调试器
|
||||
|
||||
|  |  |  |
|
||||
|
||||
### 💬 社区交流
|
||||
|
||||
有任何问题加入QQ群
|
||||
|
||||
|  |  |
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
我们欢迎所有形式的贡献!无论是代码、文档、问题反馈还是功能建议。
|
||||
|
||||
### 🚀 如何贡献
|
||||
|
||||
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 等物联网平台
|
||||
- **社区支持**:所有贡献者和用户的支持与反馈
|
||||
|
After Width: | Height: | Size: 398 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 27 MiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 333 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 730 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 956 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 394 KiB |
|
After Width: | Height: | Size: 466 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 151 KiB |
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||