add old lci module
This commit is contained in:
1 parent
38a6e815f0
commit
8499631091
171 files changed
+25562
No files matched your search
Executable
+134
@@ -0,0 +1,134 @@
|
||||
#prj
|
||||
users/
|
||||
*.key
|
||||
*.pem
|
||||
build/
|
||||
build_*/
|
||||
nn
|
||||
camera
|
||||
lci
|
||||
pmc
|
||||
|
||||
# ide
|
||||
.vscode/
|
||||
.arts/
|
||||
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
|
||||
|
||||
# cangjie
|
||||
build/
|
||||
target/
|
||||
output/
|
||||
module-resolve.json
|
||||
module-lock.json
|
||||
cjpm.lock
|
||||
.cjpm-history
|
||||
*.macrocall
|
||||
*.gcda
|
||||
*.gcno
|
||||
|
||||
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
|
||||
|
||||
# 我加的
|
||||
.env/
|
||||
*.db
|
||||
*.api_key
|
||||
*.key
|
||||
*.pem
|
||||
pmcd
|
||||
rzgs_2
|
||||
crt
|
||||
cert
|
||||
|
||||
# 其他
|
||||
log/
|
||||
report/
|
||||
tmp/
|
||||
*.cache
|
||||
*.pyc
|
||||
*.cython
|
||||
#*.jpg
|
||||
#*.png
|
||||
*.pth
|
||||
.eggs/
|
||||
*.egg-info
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# nodejs
|
||||
node_modules/
|
||||
|
||||
#python
|
||||
__pycache__/
|
||||
venv
|
||||
|
||||
# do not ignore the log folder by pmc-mgmt
|
||||
!./mgmt/app/log/
|
||||
|
||||
# go
|
||||
go.mod
|
||||
go.sum
|
||||
|
||||
|
||||
# VS
|
||||
x64
|
||||
.vs
|
||||
+674
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 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 General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is 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. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
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.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
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 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. Use with the GNU Affero General Public License.
|
||||
|
||||
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 Affero 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 special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU 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 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 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 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 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU 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 the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
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 GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
VERSION := 0.0.4
|
||||
|
||||
INSTL_DIRS := /usr/local/bin
|
||||
|
||||
CONF_NAME := init.txt
|
||||
|
||||
CONF_DIR := /etc/pmc
|
||||
|
||||
SRV_NAME := pmcd.service
|
||||
|
||||
SRV_DIR := /etc/systemd/system
|
||||
|
||||
CPP:= g++
|
||||
|
||||
INCS:= -I include/
|
||||
|
||||
LIBS := -lpthread -lssl -lcrypto -lboost_program_options -lboost_json -lboost_thread
|
||||
|
||||
CPPFLAGS := --std=c++20 -O3 -pipe #-Wall -Werror
|
||||
|
||||
BUILD_DIR = build
|
||||
$(shell mkdir -p $(BUILD_DIR))
|
||||
|
||||
#-----------
|
||||
#$(BUILD_DIR)/libpmc.so: $(BUILD_DIR)/ansi.o $(BUILD_DIR)/utf8.o $(BUILD_DIR)/fsm.o $(BUILD_DIR)/Color.o $(BUILD_DIR)/Matrix.o
|
||||
# $(CPP) -shared -fPIC $^ $(LIBS) -o $@
|
||||
|
||||
#pmc: $(BUILD_DIR)/main.o $(BUILD_DIR)/fsm.o $(BUILD_DIR)/Thread.o $(BUILD_DIR)/HttpServer.o
|
||||
# $(CPP) $^ $(LIBS) -o $@
|
||||
|
||||
lci: $(BUILD_DIR)/mainLci.o $(BUILD_DIR)/ansi.o $(BUILD_DIR)/utf8.o $(BUILD_DIR)/fsm.o $(BUILD_DIR)/Color.o $(BUILD_DIR)/ku500.o $(BUILD_DIR)/CommonThread.o $(BUILD_DIR)/Drawable.o $(BUILD_DIR)/Area.o $(BUILD_DIR)/FrameBuf.o $(BUILD_DIR)/mem.o $(BUILD_DIR)/I2C.o $(BUILD_DIR)/PCA9685.o $(BUILD_DIR)/MPU6050.o $(BUILD_DIR)/Matrix.o $(BUILD_DIR)/NeuralNetwork.o $(BUILD_DIR)/NNBuilder.o $(BUILD_DIR)/ILayer.o $(BUILD_DIR)/Thread.o
|
||||
$(CPP) $^ $(LIBS) -ljpeg -lpng -o $@
|
||||
|
||||
agent:
|
||||
agent: $(BUILD_DIR)/mainAgent.o $(BUILD_DIR)/WebSocketClient.o
|
||||
$(CPP) $^ $(LIBS) -lcurl -o $@
|
||||
|
||||
nn:
|
||||
nn: $(BUILD_DIR)/mainNn.o $(BUILD_DIR)/Camera.o $(BUILD_DIR)/NeuralNetwork.o $(BUILD_DIR)/Matrix.o $(BUILD_DIR)/ILayer.o $(BUILD_DIR)/NNBuilder.o
|
||||
$(CPP) $^ $(LIBS) -ljpeg -o $@
|
||||
|
||||
camera:
|
||||
camera: $(BUILD_DIR)/mainCamera.o $(BUILD_DIR)/Camera.o
|
||||
$(CPP) $^ $(LIBS) -ljpeg -o $@
|
||||
|
||||
$(BUILD_DIR)/mainLci.o: src/mainLci.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/mainMybot.o: src/mainMybot.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/mainAgent.o: src/mainAgent.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/mainNn.o: src/mainNn.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/mainCamera.o: src/mainCamera.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/main.o: src/main.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/ansi.o: src/lci/ansi.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/utf8.o: src/lci/utf8.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/fsm.o: src/lci/fsm.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/Color.o: src/lci/Color.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/ku500.o: src/lci/ku500.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/CommonThread.o: src/lci/CommonThread.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/Drawable.o: src/lci/Drawable.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/Area.o: src/lci/Area.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
#$(BUILD_DIR)/CmdArea.o: src/lci/CmdArea.cpp
|
||||
# $(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
#$(BUILD_DIR)/InputBox.o: src/lci/InputBox.cpp
|
||||
# $(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
#$(BUILD_DIR)/KeyboardEvent.o: src/lci/KeyboardEvent.cpp
|
||||
# $(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/FrameBuf.o: src/lci/FrameBuf.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/mem.o: src/hd/mem.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/I2C.o: src/hd/I2C.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/PCA9685.o: src/hd/PCA9685.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/MPU6050.o: src/hd/MPU6050.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/Camera.o: src/hd/Camera.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/Matrix.o: src/nn/Matrix.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/NeuralNetwork.o: src/nn/NeuralNetwork.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/NNBuilder.o: src/nn/NNBuilder.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/ILayer.o: src/nn/ILayer.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/Thread.o: src/th/Thread.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/LciTask.o: src/LciTask.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/HttpServer.o: src/net/HttpServer.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
$(BUILD_DIR)/WebSocketClient.o: src/net/WebSocketClient.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
|
||||
|
||||
install: $(APP_NAME)
|
||||
@echo "正在安装 $(APP_NAME) 到$(INSTL_DIRS)"
|
||||
@
|
||||
#@mkdir -p $(INSTL_DIRS)
|
||||
@cp -f pmc $(INSTL_DIRS)
|
||||
#@chmod 755 $(INSTL_DIRS)/pmcd
|
||||
@
|
||||
@echo "正在配置 $(APP_NAME)"
|
||||
@mkdir -p $(CONF_DIR)
|
||||
@cp $(CONF_NAME) $(CONF_DIR)/$(CONF_NAME)
|
||||
@cp $(SRV_NAME) $(SRV_DIR)/$(SRV_NAME)
|
||||
@systemctl daemon-reload
|
||||
@systemctl enable $(SRV_NAME)
|
||||
@systemctl restart $(SRV_NAME)
|
||||
@
|
||||
@echo "安装完成!"
|
||||
|
||||
uninstall:
|
||||
@echo "正在从 $(INSTL_DIRS) 卸载 $(APP_NAME)"
|
||||
@
|
||||
@rm -f $(INSTL_DIRS)/pmc
|
||||
#@rm -f $(CONF_DIRS)/$(CONF_NAME)
|
||||
@
|
||||
@systemctl stop $(SRV_NAME)
|
||||
@systemctl disable $(SRV_NAME)
|
||||
@rm -f $(SRV_DIRS)/$(SRV_NAME)
|
||||
@systemctl daemon-reload
|
||||
@
|
||||
@echo "卸载完成!"
|
||||
|
||||
clean:
|
||||
@rm -rvf $(BUILD_DIR)
|
||||
@rm -vf lci pmc mybot agent nn
|
||||
@@ -0,0 +1,79 @@
|
||||
# 进程机器(PMC)
|
||||
这是一个可以创建、管理子进程以及模块的程序。
|
||||
|
||||
## pmc是一个什么项目?
|
||||
|
||||
这是一个可扩展的模块,可以在文件夹下建立单独的模块,pmc核心模块可以托管运行这些模块。
|
||||
|
||||
|
||||
## 为什么要做一个这样的项目?
|
||||
|
||||
2024年的时候,我想做一个树莓派机器人,准备用python来写控制程序,可是当时python的多线程能力较弱,所以想到使用子系统,就自己写了一个进程托管程序
|
||||
|
||||
|
||||
## 项目的文件结构是怎样的?
|
||||
|
||||
users用户可以在目录下创建子模块,extra下面还有一些额外的模块。
|
||||
|
||||
请留意模块的README文件
|
||||
|
||||
|
||||
## 如何托管外部模块
|
||||
|
||||
编辑启动列表程序,常为init.txt
|
||||
|
||||
|
||||
## PMC的安装方式
|
||||
``` bash
|
||||
# 1.安装依赖
|
||||
sudo apt update
|
||||
sudo apt install git g++ make
|
||||
#sudo apt install python3 python3-pip python3-venv
|
||||
#sudo dnf install python3-virtualenv
|
||||
sudo apt install libboost-all-dev
|
||||
#sudo apt install libssl-dev
|
||||
#sudo apt install cimg-dev
|
||||
#sudo apt install libcurl4-openssl-dev
|
||||
#sudo apt install libcurl4-gnutls-dev
|
||||
#sudo apt install libargs-dev
|
||||
#sudo apt install libcpp-httplib-dev
|
||||
|
||||
2. 创建项目
|
||||
git clone https://github.com/gopsc/pmc
|
||||
cd pmc # 这是pmc模块
|
||||
make pmc
|
||||
#sudo make install
|
||||
```
|
||||
|
||||
## 加密通信机制(废弃)
|
||||
PMC在启动时生成一个AES密钥,然后用一个RSA公钥将其加密发送给访问者。
|
||||
|
||||
然后访问者与客户端之间的交流全部用此AES密钥加密
|
||||
|
||||
|
||||
## 如何使用并发机器运行程序
|
||||
首先应该在模块中创建一个启动脚本,脚本中应该完成工作目录的切换、虚拟环境的激活,以及程序的启动。
|
||||
|
||||
## 需要创建启动脚本(废弃)
|
||||
```bash
|
||||
# _run.sh
|
||||
# 启动脚本 - 示例
|
||||
# 需要执行权限
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# 切换工作目录
|
||||
cd "$(dirname "$0")" || exit
|
||||
|
||||
# 准备工作(安装依赖)
|
||||
#bash ./_set.sh
|
||||
|
||||
# 准备工作(比如激活虚拟环境)
|
||||
#source ./.env/bin/activate
|
||||
|
||||
# 执行程序前一定要加exec 否则杀不死
|
||||
exec python main.py
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
./pmc -i init.txt --serve 5200
|
||||
@@ -0,0 +1,3 @@
|
||||
#!bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
exec ./lci --lci --rotate=0 --fontsize=15
|
||||
@@ -0,0 +1,110 @@
|
||||
# PRJ
|
||||
.env/
|
||||
*.pem
|
||||
*.key
|
||||
./_init.csv
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
@@ -0,0 +1,5 @@
|
||||
# 自启动模块
|
||||
这是子系统的自启动模块,该程序是一次性进程,基于 _pmc 库模块。
|
||||
|
||||
# 做了什么
|
||||
启动脚本从 _init.csv 文件中读取模块列表,对每个目标调用 pmc start 子系统调用。
|
||||
@@ -0,0 +1,6 @@
|
||||
#extra/chat
|
||||
#users/smn
|
||||
#users/dnfs_ex
|
||||
#users/fortress
|
||||
users/sensors
|
||||
|
||||
|
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
bash ./_setup.sh
|
||||
source ./.env/bin/activate
|
||||
exec python init.py
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
python3 -m venv .env
|
||||
source ./.env/bin/activate
|
||||
source ./_src.sh
|
||||
#python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
@@ -0,0 +1,2 @@
|
||||
#pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
pip3 config set global.index-url https://mirrors.aliyun.com/pypi/simple/
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
|
||||
子系统启动脚本
|
||||
这个脚本的功能是通过PMC调用完成子系统的初始化
|
||||
|
||||
'''
|
||||
from pmc import PmcComm
|
||||
import os
|
||||
import csv
|
||||
import time
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
with open(f"{script_dir}/_init.csv", encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
records = [row[0] for row in reader]
|
||||
def main():
|
||||
comm = PmcComm('http://localhost:8012')
|
||||
comm.Set_RSA_Pri_Key('../pmc/private_key.pem')
|
||||
comm.Try_Get_AES_Key()
|
||||
for item in records:
|
||||
if item and not item.startswith('#'):
|
||||
comm.try_start(item)
|
||||
time.sleep(1) # 保持一定的时间间隔,避免CPU占用
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,5 @@
|
||||
flask
|
||||
waitress
|
||||
GitPython>=3.1.0
|
||||
setuptools>=40.8.0
|
||||
../_pmc
|
||||
@@ -0,0 +1,109 @@
|
||||
# PRJ
|
||||
.env/
|
||||
*.pem
|
||||
*.key
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
@@ -0,0 +1,8 @@
|
||||
## 类模块
|
||||
|
||||
这是一个类模块,它基于pmc,部分调用基于dnfs、cops、chat_s。
|
||||
|
||||
|
||||
## 包含什么
|
||||
|
||||
该类模块中包含对于python需要的子系统调用,其中聊天室的通信函数主要用于日志的输出等行为。
|
||||
@@ -0,0 +1,17 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="pmc",
|
||||
version="0.0.9",
|
||||
packages=find_packages(where="src"),
|
||||
package_dir={'': 'src'},
|
||||
install_requires=[
|
||||
'setuptools',
|
||||
'requests',
|
||||
'cryptography',
|
||||
],
|
||||
setup_requires=[
|
||||
#"whell",
|
||||
],
|
||||
python_requires='>=3.8'
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
api-test/
|
||||
@@ -0,0 +1,5 @@
|
||||
# 作者是CANCERAI
|
||||
这个模块的作者写了mgmt模块的前端部分,这个模块曾经短暂地为mgmt模块提供一部分的后端支持。
|
||||
|
||||
# 20250810
|
||||
该模块已经被开发成为dnfs树状网络文件系统的后端。
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
cd pmc-backend
|
||||
exec mvn clean spring-boot:run
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,2 @@
|
||||
/mvnw text eol=lf
|
||||
*.cmd text eol=crlf
|
||||
@@ -0,0 +1,33 @@
|
||||
HELP.md
|
||||
target/
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>org.cancerai</groupId>
|
||||
<artifactId>pmc-backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>pmc-backend</name>
|
||||
<description>pmc management backend</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection/>
|
||||
<developerConnection/>
|
||||
<tag/>
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.13.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.cancerai.pmc_backend;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
|
||||
@ServletComponentScan
|
||||
@SpringBootApplication
|
||||
public class PmcBackendApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PmcBackendApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package org.cancerai.pmc_backend.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.cancerai.pmc_backend.entility.Note;
|
||||
import org.cancerai.pmc_backend.utils.DNFSUtils;
|
||||
import org.cancerai.pmc_backend.utils.HttpResponseText;
|
||||
import org.cancerai.pmc_backend.vo.Result;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@CrossOrigin(origins = "*")
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class DNFSController {
|
||||
@GetMapping("/get-notes")
|
||||
public static Result getNotes(@RequestParam int pid) {
|
||||
return Result.success(HttpResponseText.SUCCESS_TO_REQUEST, DNFSUtils.getNodes(pid));
|
||||
}
|
||||
|
||||
@PutMapping("/add-note")
|
||||
public Result addNote(
|
||||
@RequestParam int pid,
|
||||
@RequestParam String title,
|
||||
@RequestParam String content,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
HashMap<String, String> re = DNFSUtils.addNote(pid, title, content);
|
||||
String value = re.get("message");
|
||||
|
||||
if ("Note added successfully".equals(value)) {
|
||||
return Result.success(HttpResponseText.SUCCESS_TO_ADD_NOTE);
|
||||
} else {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
return Result.error(HttpResponseText.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-note")
|
||||
public Result deleteNote(
|
||||
@RequestParam int id,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
HashMap<String, String> re = DNFSUtils.deleteNote(id);
|
||||
String value = re.get("message");
|
||||
|
||||
if ("Note deleted successfully".equals(value)) {
|
||||
return Result.success(HttpResponseText.SUCCESS_TO_DELETE_NOTE);
|
||||
} else {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
return Result.error(HttpResponseText.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@PatchMapping("/update-note")
|
||||
public static Result updateNote(
|
||||
@RequestBody Note note,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
int id = note.getId();
|
||||
int pid = note.getPid();
|
||||
String title = note.getTitle();
|
||||
String content = note.getContent();
|
||||
|
||||
HashMap<String, String> re = DNFSUtils.updateNote(id, pid, title, content);
|
||||
String value = re.get("message");
|
||||
if ("Your change is up-to-date.".equals(value)) {
|
||||
return Result.success(HttpResponseText.SUCCESS_TO_UPDATE_NOTE);
|
||||
} else {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
return Result.error(HttpResponseText.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package org.cancerai.pmc_backend.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.cancerai.pmc_backend.entility.User;
|
||||
import org.cancerai.pmc_backend.service.LoginService;
|
||||
import org.cancerai.pmc_backend.utils.EncryptUtils;
|
||||
import org.cancerai.pmc_backend.vo.Result;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@CrossOrigin(origins = "*")
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class LoginController {
|
||||
@Autowired
|
||||
private LoginService loginService;
|
||||
|
||||
@PostMapping("/login")
|
||||
public Result login(
|
||||
@RequestBody User user,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
String name = user.getName();
|
||||
String password = EncryptUtils.SHA256Encrypt(user.getPassword());
|
||||
|
||||
return loginService.verifyUser(name, password, response);
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package org.cancerai.pmc_backend.controller;
|
||||
|
||||
import org.cancerai.pmc_backend.entility.ProcessEntility;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@CrossOrigin(origins = "*")
|
||||
@RestController()
|
||||
public class ProcessController {
|
||||
@GetMapping("/get-process")
|
||||
public ArrayList<ProcessEntility> cleanProcess() {
|
||||
ArrayList<String> arr = getProcessList();
|
||||
ArrayList<ProcessEntility> result = new ArrayList<>();
|
||||
final String DEAD = "Dead";
|
||||
|
||||
// exclude Dead lines
|
||||
for (String i : arr) {
|
||||
String[] item = i.split("\t");
|
||||
String pid = item[0];
|
||||
String status = item[1];
|
||||
String cmd = item[2];
|
||||
|
||||
if (DEAD.equals(status)) {
|
||||
continue;
|
||||
}
|
||||
result.add(new ProcessEntility(pid, status, cmd));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ArrayList<String> getProcessList() {
|
||||
ArrayList<String> array = new ArrayList<>();
|
||||
WebClient webClient = WebClient.create("http://127.0.0.1:8012");
|
||||
|
||||
String response = webClient.post()
|
||||
.uri("/api/list")
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block();
|
||||
|
||||
String[] splitRes = response.split("\n");
|
||||
for (String item : splitRes) {
|
||||
array.add(item);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package org.cancerai.pmc_backend.entility;
|
||||
|
||||
public class Note {
|
||||
private String content;
|
||||
private int id;
|
||||
private int pid;
|
||||
private String title;
|
||||
|
||||
public Note(String content, int id, int pid, String title) {
|
||||
this.content = content;
|
||||
this.id = id;
|
||||
this.pid = pid;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getPid() {
|
||||
return pid;
|
||||
}
|
||||
|
||||
public void setPid(int pid) {
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Note{" +
|
||||
"content='" + content + '\'' +
|
||||
", id=" + id +
|
||||
", pid=" + pid +
|
||||
", title='" + title + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package org.cancerai.pmc_backend.entility;
|
||||
|
||||
public class ProcessEntility {
|
||||
private String pid;
|
||||
private String status;
|
||||
private String cmd;
|
||||
|
||||
public ProcessEntility(String pid, String status, String cmd) {
|
||||
this.pid = pid;
|
||||
this.status = status;
|
||||
this.cmd = cmd;
|
||||
}
|
||||
|
||||
public String getPid() {
|
||||
return pid;
|
||||
}
|
||||
|
||||
public void setPid(String pid) {
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getCmd() {
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public void setCmd(String cmd) {
|
||||
this.cmd = cmd;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package org.cancerai.pmc_backend.entility;
|
||||
|
||||
public class User {
|
||||
private String name;
|
||||
private String password;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package org.cancerai.pmc_backend.exception;
|
||||
|
||||
import org.cancerai.pmc_backend.vo.Result;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
// 400
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public static Result JWTSignature(HttpMessageNotReadableException e) {
|
||||
e.printStackTrace();
|
||||
return Result.error("请求参数无效");
|
||||
}
|
||||
|
||||
// 401
|
||||
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public static Result requestMethodNotSupport(HttpRequestMethodNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
return Result.error("请求方法错误");
|
||||
}
|
||||
|
||||
// 404
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
@ExceptionHandler(NoResourceFoundException.class)
|
||||
public static Result ResourceNotFound(NoResourceFoundException e) {
|
||||
e.printStackTrace();
|
||||
return Result.error("未找到静态资源");
|
||||
}
|
||||
|
||||
// 500
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@ExceptionHandler(Exception.class)
|
||||
public static Result exception(Exception e) {
|
||||
e.printStackTrace();
|
||||
return Result.error("服务器内部错误");
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package org.cancerai.pmc_backend.filter;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.annotation.WebFilter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.cancerai.pmc_backend.utils.HttpResponseText;
|
||||
import org.cancerai.pmc_backend.utils.JWTUtils;
|
||||
import org.cancerai.pmc_backend.vo.Result;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@WebFilter(urlPatterns = "/api/*")
|
||||
@Order(1)
|
||||
public class LoginFilter implements Filter {
|
||||
final String[] EXCLUDE_URL_LIST = {
|
||||
"/api/login"
|
||||
};
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||
// set content security policy
|
||||
response.setHeader("Content-Security-Policy", "default-src *");
|
||||
|
||||
// exclude url
|
||||
String uri = request.getRequestURI();
|
||||
for (String item : EXCLUDE_URL_LIST) {
|
||||
if (item.equals(uri)) {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Allow OPTION method
|
||||
if ("OPTIONS".equals(request.getMethod())) {
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
String token = request.getHeader("token");
|
||||
if (token == null || token.isEmpty()) {
|
||||
// set response header
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json");
|
||||
|
||||
// construct body
|
||||
Gson gson = new GsonBuilder().serializeNulls().create();
|
||||
String responseBody = gson.toJson(Result.error(HttpResponseText.UNAUTHORIZED)).toString();
|
||||
// set response body
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(responseBody);
|
||||
writer.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JWTUtils.parseJWT(token);
|
||||
} catch (MalformedJwtException e) {
|
||||
// set response body
|
||||
Gson gson = new GsonBuilder().serializeNulls().create();
|
||||
String body = gson.toJson(Result.error(HttpResponseText.JWT_MALFORMED));
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(body);
|
||||
writer.flush();
|
||||
return;
|
||||
} catch (ExpiredJwtException e) {
|
||||
// set response body
|
||||
Gson gson = new GsonBuilder().serializeNulls().create();
|
||||
String body = gson.toJson(Result.error(HttpResponseText.JWT_EXPIRED));
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(body);
|
||||
writer.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
response.setHeader("Content-Security-Policy", "default-src *");
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package org.cancerai.pmc_backend.service;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.cancerai.pmc_backend.entility.Note;
|
||||
import org.cancerai.pmc_backend.utils.DNFSUtils;
|
||||
import org.cancerai.pmc_backend.utils.JWTUtils;
|
||||
import org.cancerai.pmc_backend.vo.Result;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
@Service
|
||||
public class LoginService {
|
||||
public static Result verifyUser(String name, String password, HttpServletResponse response) {
|
||||
ArrayList<HashMap<String, String>> allUsersInfo = getAllUsersInfo();
|
||||
boolean isExist = false;
|
||||
|
||||
for (HashMap<String, String> u : allUsersInfo) {
|
||||
if (name.equals(u.get("name"))
|
||||
&& password.equals(u.get("password"))
|
||||
) {
|
||||
isExist = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isExist) {
|
||||
return Result.success(
|
||||
"登陆成功",
|
||||
JWTUtils.genJWT(name, password));
|
||||
} else {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return Result.error("账号或密码错误");
|
||||
}
|
||||
}
|
||||
|
||||
private static ArrayList<HashMap<String, String>> getAllUsersInfo() {
|
||||
Gson gson = new Gson();
|
||||
// StringBuilder stringBuilder = new StringBuilder();
|
||||
ArrayList<String> userList = new ArrayList<>();
|
||||
ArrayList<Note> re = DNFSUtils.getNodes(0);
|
||||
String jsonString = gson.toJson(re);
|
||||
JsonArray jsonArray = gson.fromJson(jsonString, JsonArray.class);
|
||||
|
||||
for (JsonElement jsonElement : jsonArray) {
|
||||
// To json
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
// The value is \" + VALUE + \"
|
||||
String titleString = jsonObject.get("title").toString();
|
||||
String titleValue = titleString.substring(1, titleString.length() - 1);
|
||||
if ("Users".equals(titleValue)) {
|
||||
String contentString = jsonObject.get("content").toString();
|
||||
String contentValue = contentString.substring(1, contentString.length() - 1);
|
||||
|
||||
String[] contentSplit = contentValue.split("\\\\n");
|
||||
userList.addAll(Arrays.asList(contentSplit));
|
||||
}
|
||||
}
|
||||
|
||||
return DNFSUtils.parseUserInfo(userList);
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package org.cancerai.pmc_backend.utils;
|
||||
|
||||
import org.cancerai.pmc_backend.entility.Note;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class DNFSUtils {
|
||||
private final static String domain = "http://qsont.xyz:8006";
|
||||
|
||||
public static ArrayList<Note> getNodes(Integer pid) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
// prepare request body
|
||||
HashMap<String, Integer> requestBody = new HashMap<>();
|
||||
requestBody.put("pid", pid);
|
||||
// prepare request header
|
||||
HttpHeaders requestHeader = new HttpHeaders();
|
||||
requestHeader.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
ResponseEntity<ArrayList<Note>> response = restTemplate.exchange(
|
||||
domain + "/get_notes",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(requestBody, requestHeader),
|
||||
new ParameterizedTypeReference<ArrayList<Note>>() {
|
||||
}
|
||||
);
|
||||
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
public static HashMap<String, String> addNote(int pid, String title, String content) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
HashMap<String, String> body = new HashMap<>();
|
||||
HttpHeaders header = new HttpHeaders();
|
||||
|
||||
header.setContentType(MediaType.APPLICATION_JSON);
|
||||
body.put("pid", String.valueOf(pid));
|
||||
body.put("title", title);
|
||||
body.put("content", content);
|
||||
|
||||
return restTemplate.exchange(
|
||||
domain + "/add_note",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(body, header),
|
||||
new ParameterizedTypeReference<HashMap<String, String>>() {
|
||||
}
|
||||
).getBody();
|
||||
}
|
||||
|
||||
public static HashMap<String, String> deleteNote(int id) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
HashMap<String, String> body = new HashMap<>();
|
||||
HttpHeaders header = new HttpHeaders();
|
||||
|
||||
header.setContentType(MediaType.APPLICATION_JSON);
|
||||
body.put("id", String.valueOf(id));
|
||||
|
||||
return restTemplate.exchange(
|
||||
domain + "/delete_note",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(body, header),
|
||||
new ParameterizedTypeReference<HashMap<String, String>>() {
|
||||
}
|
||||
).getBody();
|
||||
}
|
||||
|
||||
public static HashMap<String, String> updateNote(int id, int pid, String title, String content) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
HashMap<String, String> body = new HashMap<>();
|
||||
HttpHeaders header = new HttpHeaders();
|
||||
|
||||
header.setContentType(MediaType.APPLICATION_JSON);
|
||||
body.put("id", String.valueOf(id));
|
||||
body.put("pid", String.valueOf(pid));
|
||||
body.put("title", title);
|
||||
body.put("content", content);
|
||||
|
||||
return restTemplate.exchange(
|
||||
domain + "/update_note",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(body, header),
|
||||
new ParameterizedTypeReference<HashMap<String, String>>() {
|
||||
}
|
||||
).getBody();
|
||||
}
|
||||
|
||||
public static ArrayList<HashMap<String, String>> parseUserInfo(ArrayList<String> userLIst) {
|
||||
ArrayList<HashMap<String, String>> array = new ArrayList<>();
|
||||
|
||||
for (String s : userLIst) {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
String[] splitArray = s.split("::");
|
||||
map.put("id", splitArray[0]);
|
||||
map.put("name", splitArray[1]);
|
||||
map.put("password", splitArray[2]);
|
||||
map.put("role", splitArray[3]);
|
||||
array.add(map);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.cancerai.pmc_backend.utils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class EncryptUtils {
|
||||
public static String SHA256Encrypt(String str) {
|
||||
try {
|
||||
// 获取SHA-256算法实例
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
// 计算哈希值(指定UTF-8编码避免平台差异)
|
||||
byte[] hashBytes = digest.digest(str.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 将字节数组转换为十六进制字符串
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : hashBytes) {
|
||||
// 将每个字节转换为两位十六进制数,不足两位补0
|
||||
String hex = String.format("%02x", 0xFF & b);
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// SHA-256是Java标准算法,理论上不会抛出此异常
|
||||
throw new RuntimeException("SHA-256算法不支持", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.cancerai.pmc_backend.utils;
|
||||
|
||||
public class HttpResponseText {
|
||||
// success
|
||||
public final static String SUCCESS_TO_REQUEST = "Request OK";
|
||||
public final static String SUCCESS_TO_ADD_NOTE = "成功添加节点";
|
||||
public final static String SUCCESS_TO_DELETE_NOTE = "成功删除节点";
|
||||
public final static String SUCCESS_TO_UPDATE_NOTE = "成功更新节点";
|
||||
// error
|
||||
public final static String UNAUTHORIZED = "Cannot authorized identity, access denied";
|
||||
public final static String JWT_MALFORMED = "JWT无效";
|
||||
public final static String JWT_EXPIRED = "JWT过期";
|
||||
public final static String INTERNAL_SERVER_ERROR = "服务器内部错误";
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package org.cancerai.pmc_backend.utils;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
|
||||
public class JWTUtils {
|
||||
final private static String key = "cancerai_key_qpalzm102938";
|
||||
final private static String encodedKey = Base64.getEncoder()
|
||||
.encodeToString(key.getBytes(StandardCharsets.UTF_8));
|
||||
// set JWT kwy
|
||||
final static Key jwtKey = Keys.hmacShaKeyFor(encodedKey.getBytes());
|
||||
|
||||
public static String genJWT(String name, String password) {
|
||||
final Date expirationTime = new Date(
|
||||
System.currentTimeMillis()
|
||||
+ (60 * 1000 // 1min
|
||||
* 60 // 1h
|
||||
* 24 // 1day
|
||||
* 2 // 2 days
|
||||
));
|
||||
|
||||
return Jwts.builder()
|
||||
.claim("userName", name)
|
||||
.claim("user-password", password)
|
||||
.signWith(jwtKey, SignatureAlgorithm.HS256)
|
||||
.setExpiration(expirationTime)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public static void parseJWT(String jwt) {
|
||||
Jwts.parserBuilder()
|
||||
.setSigningKey(jwtKey)
|
||||
.build()
|
||||
.parseClaimsJws(jwt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.cancerai.pmc_backend.vo;
|
||||
|
||||
public class Result {
|
||||
private String message;
|
||||
private Object data;
|
||||
|
||||
private Result(String message, Object data) {
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static Result success(String message) {
|
||||
return new Result(message, null);
|
||||
}
|
||||
|
||||
public static Result success(String message, Object data) {
|
||||
return new Result(message, data);
|
||||
}
|
||||
|
||||
public static Result error(String message) {
|
||||
return new Result(message, null);
|
||||
}
|
||||
|
||||
public static Result error(String message, Object data) {
|
||||
return new Result(message, data);
|
||||
}
|
||||
|
||||
// getter and setter
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
server:
|
||||
port: 8011
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.cancerai.pmc_backend;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class PmcBackendApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.append('a');
|
||||
System.out.println(stringBuilder);
|
||||
|
||||
stringBuilder.setLength(0);
|
||||
stringBuilder.append('b');
|
||||
System.out.println(stringBuilder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
# PRJ
|
||||
.env/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
@@ -0,0 +1,33 @@
|
||||
## chat是一个什么项目
|
||||
该模块是python TCP socket聊天室服务,对每个连接者提供消息转发的服务。
|
||||
|
||||
你还可以召唤AI,与它进行对话。
|
||||
|
||||
## 如何运行chat服务?
|
||||
|
||||
我们在项目中打包了启动脚本```_run.sh```,你只需要安装好依赖的软件,就可以一键创建虚拟环境并且执行它。
|
||||
|
||||
```shell
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-venv
|
||||
bash _run.sh
|
||||
```
|
||||
|
||||
## 如何连接chat服务?
|
||||
```shell
|
||||
python client.py [--addr=localhost] [--port=8003] [--name=Unknow]
|
||||
```
|
||||
|
||||
## 我如何与AI对话
|
||||
|
||||
服务器从文件中读取提示词,并且将具有```<chatgpt>```特定前缀的消息转发给AI。
|
||||
|
||||
并且具有一定的记忆能力。
|
||||
|
||||
## 什么是快捷标签输入
|
||||
|
||||
成功连接后输入```chatgpt```将会给接下来的对话添加```<chatgpt>```标签。
|
||||
|
||||
在聊天框中输入```where```查看当前的对话标签。
|
||||
|
||||
在聊天框中输入```del```以删除当前的前缀。
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
bash ./_set.sh 1>/dev/null
|
||||
source ./.env/bin/activate
|
||||
exec python server.py --type=server --port=8003 --addr=0.0.0.0
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
python3 -m venv .env
|
||||
source .env/bin/activate
|
||||
pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
||||
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env python3
|
||||
#-------------------------------------------------------------
|
||||
# 以下区域是我的代码
|
||||
'''
|
||||
|
||||
【项目简介】 网络聊天室 v0.0.2
|
||||
|
||||
【加入项目】
|
||||
注意代码只添加不修改,你可以写个函数,署名
|
||||
不要修改别人的代码。
|
||||
|
||||
【基线规则】
|
||||
在自己的区域修改代码,谁的代码能跑就加入主线
|
||||
除此之外只在各自的测试区域内运行
|
||||
|
||||
【设计思路】
|
||||
建议一个项目一个文件先,容易分发
|
||||
|
||||
【测试方式】
|
||||
服务器进程: python chat.py --type=server
|
||||
客户端进程: python chat.py
|
||||
|
||||
--------------------------------
|
||||
--------------------------------
|
||||
--------------------------------
|
||||
【项目名称】 网络聊天室 v 0.0.3 (20250524)
|
||||
我们会将新版本加入pmc.cops中作为常驻服务
|
||||
|
||||
【改进计划】
|
||||
1.我们专门设置了一个只发送一条消息就关闭的函数以供别的文件调用
|
||||
2.我们将端口暴露在了公网下,以测试连接情况,发现并修复了许多bug。
|
||||
3.我们设置欢迎信息只在服务端可见。
|
||||
|
||||
【使用方法】
|
||||
python chat.py [--type=server|client] [--addr=localhost] [--port=8003] [--name=Unknow]
|
||||
|
||||
【记得反馈!】
|
||||
期待您将反馈发送至gitee
|
||||
|
||||
----------------------------------------
|
||||
########################################
|
||||
########################################
|
||||
|
||||
网络聊天室 version 0.1.0
|
||||
|
||||
使用方法 python chat.py [--addr=localhost] [--port=8003] [--name=Unknow]
|
||||
'''
|
||||
import argparse
|
||||
import socket
|
||||
import select
|
||||
import threading
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
class ChatClient:
|
||||
|
||||
def __init__(self, addr: str, port: int, your_name: str):
|
||||
self.addr: str = addr
|
||||
self.port: int = port
|
||||
self.name: str = your_name
|
||||
|
||||
def send_client(self, msg: str):
|
||||
'''发送消息给聊天室服务器。'''
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.settimeout(5) # 设置连接超时
|
||||
s.connect((self.addr, self.port))
|
||||
s.settimeout(None) # 连接成功后取消超时
|
||||
s.sendall(f"{self.name}: {msg}".encode('utf-8'))
|
||||
except (ConnectionRefusedError, socket.timeout):
|
||||
print(f"Could not connect to {self.addr}:{self.port} - message not sent")
|
||||
except Exception as e:
|
||||
print(f"Unexpected error while sending message: {e}")
|
||||
|
||||
def recv_loop(self, s: socket.socket):
|
||||
"""
|
||||
接收消息的函数,持续从服务器接收消息并打印。
|
||||
Args:
|
||||
s (socket.socket): 连接的套接字对象
|
||||
|
||||
使用select监视套接字状态,当套接字可读时接收消息并打印。
|
||||
"""
|
||||
socklist = [s]
|
||||
while self.running:
|
||||
|
||||
try:
|
||||
readable, _, exception = select.select(socklist, [], socklist, 0.1)
|
||||
|
||||
if readable != []:
|
||||
try:
|
||||
msg = readable[0].recv(8092)
|
||||
if msg:
|
||||
print(msg.decode('utf-8'))
|
||||
else:
|
||||
# 服务器关闭连接
|
||||
print("Connection closed by server.")
|
||||
return False # 返回False表示需要重连
|
||||
except ConnectionResetError:
|
||||
print("Connection reset by server.")
|
||||
return False # 返回False表示需要重连
|
||||
|
||||
if exception != []:
|
||||
print("Exception on socket, closing...")
|
||||
return False # 返回False表示需要重连
|
||||
|
||||
except ValueError:
|
||||
... # 程序退出了会抛出这个异常
|
||||
except OSError as e:
|
||||
# 处理socket错误
|
||||
print(f"Socket error: {e}")
|
||||
return False # 返回False表示需要重连
|
||||
|
||||
return True # 正常退出
|
||||
|
||||
|
||||
def _lexer(self, input_data: str):
|
||||
'''如果不需要发送则返回None。'''
|
||||
if input_data == "quit":
|
||||
self.running = False
|
||||
return None
|
||||
elif not self.extra_cmd and input_data == "chatgpt":
|
||||
self.extra_cmd = "<chatgpt>"
|
||||
return None
|
||||
elif self.extra_cmd and input_data == "del":
|
||||
self.extra_cmd = None
|
||||
return None
|
||||
elif input_data == "where":
|
||||
print(f'---{self.extra_cmd}---')
|
||||
return None
|
||||
else:
|
||||
return input_data
|
||||
|
||||
def _mixup_msg(self, input_data):
|
||||
if self.extra_cmd:
|
||||
return f"{self.extra_cmd}{input_data}"
|
||||
else:
|
||||
return input_data
|
||||
|
||||
def send_p(self,):
|
||||
'''读取并且发送消息的进程'''
|
||||
self.extra_cmd = None
|
||||
while self.running:
|
||||
try:
|
||||
# 使用input()替代select.select(),更兼容Windows
|
||||
input_data = input().strip()
|
||||
input_data = self._lexer(input_data)
|
||||
if input_data: # 如果不需要发送,_lexer返回None
|
||||
input_data = self._mixup_msg(input_data)
|
||||
self.send_client(input_data)
|
||||
except EOFError:
|
||||
# 处理Ctrl+D等情况
|
||||
break
|
||||
except Exception as e:
|
||||
# 捕获其他异常,防止程序崩溃
|
||||
print(f"Input error: {e}")
|
||||
continue
|
||||
|
||||
def _connect_and_print(self, s: socket.socket):
|
||||
s.connect((self.addr, self.port))
|
||||
print(f'Connected to {self.addr}:{self.port}')
|
||||
|
||||
def _sleep_if_reconnect(self, reconnect_count: int):
|
||||
"""根据重连次数计算等待时间,实现指数退避策略"""
|
||||
if self.running:
|
||||
# 指数退避:1, 2, 4, 8, 16秒,最大30秒
|
||||
wait_time = min(2 ** (reconnect_count - 1), 30)
|
||||
# 确保第一次重连等待时间为1秒
|
||||
if reconnect_count == 1:
|
||||
wait_time = 1
|
||||
print(f"准备重连... 等待 {wait_time} 秒 (第 {reconnect_count} 次尝试)")
|
||||
time.sleep(wait_time)
|
||||
|
||||
def recv_client(self):
|
||||
"""客户端主函数,建立与服务器的连接并管理消息收发"""
|
||||
self.running = True
|
||||
self.th = threading.Thread(target=self.send_p)
|
||||
self.th.start()
|
||||
|
||||
reconnect_count = 0
|
||||
max_reconnect_attempts = 10 # 最大重连尝试次数
|
||||
|
||||
while self.running and reconnect_count < max_reconnect_attempts:
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.settimeout(5) # 设置连接超时时间
|
||||
self._connect_and_print(s)
|
||||
s.settimeout(None) # 连接成功后取消超时
|
||||
|
||||
# 运行接收循环,如果返回False表示需要重连
|
||||
if not self.recv_loop(s):
|
||||
print("Connection lost, attempting to reconnect...")
|
||||
reconnect_count += 1
|
||||
self._sleep_if_reconnect(reconnect_count)
|
||||
continue
|
||||
|
||||
except socket.timeout:
|
||||
print(f"Connection timeout to {self.addr}:{self.port}")
|
||||
reconnect_count += 1
|
||||
self._sleep_if_reconnect(reconnect_count)
|
||||
except ConnectionRefusedError:
|
||||
print(f"Could not connect to {self.addr}:{self.port}")
|
||||
reconnect_count += 1
|
||||
self._sleep_if_reconnect(reconnect_count)
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {e}")
|
||||
reconnect_count += 1
|
||||
self._sleep_if_reconnect(reconnect_count)
|
||||
|
||||
if reconnect_count >= max_reconnect_attempts:
|
||||
print(f"Failed to reconnect after {max_reconnect_attempts} attempts. Exiting...")
|
||||
|
||||
# 停止发送线程
|
||||
self.running = False
|
||||
if self.th.is_alive():
|
||||
self.th.join(timeout=2)
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
#--------------------------------------------------------------------------------
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='聊天室配置。')
|
||||
parser.add_argument('--addr', type=str, default='qsont.xyz', help='选择要连接的地址')
|
||||
parser.add_argument('--port', type=int, default=8003, help='选择要连接的地址')
|
||||
parser.add_argument('--name', type=str, default='Unknow', help='你的名字是什么')
|
||||
args = parser.parse_args()
|
||||
client = ChatClient(args.addr, args.port, args.name)
|
||||
client.recv_client()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
openai
|
||||
httpx
|
||||
../_pmc
|
||||
@@ -0,0 +1,345 @@
|
||||
#!/usr/bin/env python3
|
||||
#-------------------------------------------------------------
|
||||
# 以下区域是我的代码
|
||||
'''
|
||||
|
||||
【项目简介】 网络聊天室 v0.0.2
|
||||
|
||||
【加入项目】
|
||||
注意代码只添加不修改,你可以写个函数,署名
|
||||
不要修改别人的代码。
|
||||
|
||||
【基线规则】
|
||||
在自己的区域修改代码,谁的代码能跑就加入主线
|
||||
除此之外只在各自的测试区域内运行
|
||||
|
||||
【设计思路】
|
||||
建议一个项目一个文件先,容易分发
|
||||
|
||||
【测试方式】
|
||||
服务器进程: python chat.py --type=server
|
||||
客户端进程: python chat.py
|
||||
|
||||
--------------------------------
|
||||
--------------------------------
|
||||
--------------------------------
|
||||
【项目名称】 网络聊天室 v 0.0.3 (20250524)
|
||||
我们会将新版本加入pmc.cops中作为常驻服务
|
||||
|
||||
【改进计划】
|
||||
1.我们专门设置了一个只发送一条消息就关闭的函数以供别的文件调用
|
||||
2.我们将端口暴露在了公网下,以测试连接情况,发现并修复了许多bug。
|
||||
3.我们设置欢迎信息只在服务端可见。
|
||||
|
||||
【使用方法】
|
||||
python chat.py [--type=server|client] [--addr=localhost] [--port=8003] [--name=Unknow]
|
||||
|
||||
【记得反馈!】
|
||||
期待您将反馈发送至gitee
|
||||
|
||||
|
||||
####################################
|
||||
####################################
|
||||
【名称】AI聊天室
|
||||
【版本】v0.0.4
|
||||
前后端分离,并且加入AI聊天功能
|
||||
|
||||
##################
|
||||
##################
|
||||
FIXME: 加入原生自然语言处理
|
||||
'''
|
||||
import argparse
|
||||
import socket
|
||||
import select
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from openai import OpenAI
|
||||
import httpx
|
||||
import os
|
||||
from pmc import upload_file
|
||||
|
||||
# 获取当前脚本所在目录
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# 生成提示词的绝对路径
|
||||
dir_path = os.path.join(basedir, '..', 'public', 'prompts')
|
||||
|
||||
def get_prompts(filename: str):
|
||||
''' 获取system提示词 '''
|
||||
path = os.path.join(dir_path, filename)
|
||||
with open(path, 'r') as f:
|
||||
system = f.read()
|
||||
return system
|
||||
|
||||
# 设置 DeepSeek API URL 和密钥
|
||||
api_base = "https://api.deepseek.com/v1"
|
||||
with open("../users/cert/deepseek.key", "r") as f:
|
||||
api_key = f.read().strip()
|
||||
|
||||
|
||||
# 创建自定义的 httpx 客户端,禁用 SSL 验证
|
||||
custom_http_client = httpx.Client(verify=False)
|
||||
|
||||
# 初始化客户端
|
||||
client = OpenAI(
|
||||
base_url=api_base,
|
||||
api_key=api_key,
|
||||
http_client=custom_http_client # 忽略证书
|
||||
)
|
||||
|
||||
def make_sure_agent(name):
|
||||
path = os.path.join(dir_path, '.' + name)
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
'''机器人们的聊天记录与设定'''
|
||||
MySettings = {}
|
||||
|
||||
# 用于交流需要
|
||||
def datestr():
|
||||
'''获取时间字符串(不带毫秒)'''
|
||||
now = datetime.now()
|
||||
time_str = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
return str(time_str)
|
||||
|
||||
|
||||
|
||||
|
||||
def send_to_all_clients(clients: dict, exceptone: socket.socket, msg: str):
|
||||
"""
|
||||
向所有已连接的客户端广播消息,可排除指定客户端不发送。
|
||||
|
||||
Args:
|
||||
clients (dict): 客户端套接字对象到地址的映射字典
|
||||
exceptone (socket.socket): 要排除的客户端套接字对象(不向其发送消息)
|
||||
msg (str): 要广播的消息内容
|
||||
|
||||
遍历所有客户端尝试发送消息(排除exceptone指定的客户端),
|
||||
自动清理断开连接的客户端。
|
||||
处理发送过程中可能出现的网络错误,维护有效的客户端列表。
|
||||
"""
|
||||
disconnected = []
|
||||
for client in clients:
|
||||
if client is not exceptone:
|
||||
try:
|
||||
client.sendall(msg.encode('utf-8'))
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
disconnected.append(client)
|
||||
for client in disconnected:
|
||||
client.close()
|
||||
del clients[client]
|
||||
|
||||
MAX_MEM: int = 20
|
||||
def chat_with_gpt(agent_name:str, user: str):
|
||||
'''与gpt进行聊天'''
|
||||
|
||||
# 添加智能体设定,如果之前没有露面
|
||||
settings = MySettings.get(agent_name, None)
|
||||
if settings is None:
|
||||
settings = [{
|
||||
"role": "system",
|
||||
"content": get_prompts(f'{agent_name}.txt')
|
||||
},
|
||||
{
|
||||
"role": "system",
|
||||
"content": "你有一个遥控器,遥控器上能够看到别人发来的消息。"
|
||||
}]
|
||||
MySettings[agent_name] = settings
|
||||
|
||||
settings.append({"role": "user", "content": user})
|
||||
|
||||
# 进行聊天
|
||||
# 调用GPT
|
||||
response = client.chat.completions.create(
|
||||
model="deepseek-chat", # 使用DeepSeek模型
|
||||
messages=settings,
|
||||
temperature=0.7,
|
||||
)
|
||||
|
||||
# 处理结果
|
||||
content: str = response.choices[0].message.content
|
||||
settings.append({"role": "assistant", "content": content})
|
||||
|
||||
|
||||
# 更新系统提示词
|
||||
# 调用GPT
|
||||
rsp = client.chat.completions.create(
|
||||
model="deepseek-chat", # 使用DeepSeek模型
|
||||
messages= settings+ [{"role": "user", "content": '''
|
||||
# 场景
|
||||
你正在写新的日记。
|
||||
# 内容
|
||||
1.聊聊最近发生了啥有趣的事情吧
|
||||
2.你有什么感想呢?
|
||||
3.你现在的情况怎么样?
|
||||
# 输出
|
||||
准确、详实的输出
|
||||
'''}],
|
||||
temperature=0.4,
|
||||
)
|
||||
|
||||
date = datestr()
|
||||
new_prompt: str = date + '\n' + rsp.choices[0].message.content
|
||||
|
||||
# 追加提示词,如果有重复就不添加
|
||||
flag = True
|
||||
for prompt in settings:
|
||||
if prompt['role'] == 'system':
|
||||
if prompt['content'] == new_prompt:
|
||||
flag = False
|
||||
break
|
||||
if flag:
|
||||
settings.append({"role": 'system', 'content': new_prompt})
|
||||
print(new_prompt)
|
||||
|
||||
# 创建智能体日记文件夹
|
||||
make_sure_agent(agent_name)
|
||||
|
||||
# 写入日记
|
||||
n = date
|
||||
n = n.replace(':', '-')
|
||||
n = n.replace(' ', '_')
|
||||
n = n+ '.txt'
|
||||
path = os.path.join(dir_path, '.' + agent_name, n)
|
||||
with open(path, 'w') as f:
|
||||
f.write(new_prompt)
|
||||
|
||||
# 从聊天开始处开始遗忘
|
||||
chat_start = 0
|
||||
#now_type = settings[chat_start]['role']
|
||||
#while now_type == 'system':
|
||||
# chat_start += 1
|
||||
# now_type = settings[chat_start]['role']
|
||||
|
||||
#print("this chat start -at idx", chat_start)
|
||||
if len(settings) > MAX_MEM:
|
||||
del settings[chat_start] # 如果有多条system提示词无,就不能再用这个幻数
|
||||
del settings[chat_start]
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def chat_server(addr: str, port: int):
|
||||
"""
|
||||
服务器主函数,管理客户端连接和消息转发。
|
||||
|
||||
Args:
|
||||
addr (str): 绑定IP地址
|
||||
port (int): 监听端口号
|
||||
|
||||
创建TCP服务器套接字,使用select处理多客户端连接。
|
||||
主要功能:
|
||||
1. 接受新连接时记录客户端信息并广播通知(不通知新连接客户端本身)
|
||||
2. 接收客户端消息并转发给所有其他客户端
|
||||
3. 处理客户端断开事件并通知其他客户端
|
||||
4. 支持优雅地处理键盘中断关闭服务器
|
||||
|
||||
实现特点:
|
||||
- 使用select实现非阻塞IO多路复用
|
||||
- 自动清理断开连接的客户端资源
|
||||
- 新客户端加入时不向自己发送欢迎消息
|
||||
"""
|
||||
stop_event = threading.Event()
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as servsock:
|
||||
servsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
servsock.bind((addr, port))
|
||||
servsock.listen(10)
|
||||
socketlist = [servsock]
|
||||
clients = {}
|
||||
print(f'the chat server listening at {servsock.getsockname()}')
|
||||
while not stop_event.is_set():
|
||||
read_sockets, _, exception_sockets = select.select(
|
||||
socketlist, [], socketlist, 0.1
|
||||
)
|
||||
for notified_socket in exception_sockets:
|
||||
print(f"({datestr()}) Exception closing {clients.get(notified_socket, 'unknown')}")
|
||||
notified_socket.close()
|
||||
if notified_socket in clients:
|
||||
del clients[notified_socket]
|
||||
socketlist.remove(notified_socket)
|
||||
for notified_socket in read_sockets:
|
||||
if notified_socket == servsock:
|
||||
client_socket, client_address = servsock.accept()
|
||||
#print(f"({datestr()}) New client from {client_address}")
|
||||
if client_socket.fileno() != -1: #有时会出现无效的文件描述符
|
||||
socketlist.append(client_socket)
|
||||
clients[client_socket] = client_address
|
||||
# 欢迎消息由客户端发送
|
||||
else:
|
||||
try:
|
||||
# 这里可以使用select反复读取
|
||||
# 也可以先设置一次传送的最大量
|
||||
data = notified_socket.recv(8192)
|
||||
allmsg = data.decode('utf-8')
|
||||
if data:
|
||||
send_to_all_clients(clients, notified_socket, f"[{datestr()}]{allmsg}")
|
||||
else:
|
||||
raise ConnectionResetError
|
||||
if data:
|
||||
idx = allmsg.find(": ")
|
||||
msg = allmsg[idx+2:]
|
||||
username = allmsg[:idx]
|
||||
if msg.startswith("<"): # 聊天发起
|
||||
idx = msg.find('>')
|
||||
agent_name = msg[1:idx]
|
||||
msg = msg[idx + 1:]
|
||||
chatgpt = chat_with_gpt(agent_name, f'(这是来自用户{username}的消息){msg}')
|
||||
send_to_all_clients(clients, notified_socket, f"[{datestr()}]{chatgpt}")
|
||||
elif msg.startswith('{'): # 遥控发起
|
||||
idx = msg.find('}')
|
||||
url = msg[1:idx]
|
||||
msg = msg[idx + 1:]
|
||||
print(url)
|
||||
s = url.split(":")
|
||||
if (len(s) != 2):
|
||||
send_to_all_clients(clients, notified_socket, f"[{datestr()}] url格式错误")
|
||||
continue
|
||||
else:
|
||||
ip = s[0]
|
||||
port = int(s[1])
|
||||
path = os.path.join(basedir, '..', 'public', msg)
|
||||
post_res = upload_file(f'{path}', ip, port)
|
||||
send_to_all_clients(clients, notified_socket, f"[{datestr()}]{post_res}")
|
||||
|
||||
# post to url...
|
||||
#send_to_all_clients(clients, notified_socket, f"[{datestr()}]{post_res}")
|
||||
|
||||
|
||||
except ConnectionResetError:
|
||||
# 对方断开了
|
||||
print(f"({datestr()}) Connection closed with {clients[notified_socket]}")
|
||||
notified_socket.close()
|
||||
socketlist.remove(notified_socket)
|
||||
del clients[notified_socket]
|
||||
except UnicodeDecodeError:
|
||||
# 看看对方发了什么东西过来
|
||||
print('-------------', end="")
|
||||
print(data, end="")
|
||||
print('-------------')
|
||||
except OSError:
|
||||
# 对方连接之后在一个不恰当的时机断开了
|
||||
...
|
||||
except KeyboardInterrupt:
|
||||
print("\nServer shutting down...")
|
||||
stop_event.set()
|
||||
|
||||
# 以上区域是我的代码
|
||||
#--------------------------------------------------------------------------------
|
||||
#--------------------------------------------------------------------------------
|
||||
# 以下区域是主线,不要修改!
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='聊天室配置。')
|
||||
parser.add_argument('--type', type=str, default='client', help='选择聊天室的类型')
|
||||
parser.add_argument('--addr', type=str, default='localhost', help='选择要连接的地址')
|
||||
parser.add_argument('--port', type=int, default=8003, help='选择要连接的地址')
|
||||
parser.add_argument('--name', type=str, default='Unknow', help='你的名字是什么')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.type == 'client': recv_client(args.addr, args.port)
|
||||
elif args.type == 'server': chat_server(args.addr, args.port)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
(有点失落,但还是努力完成任务)
|
||||
|
||||
**姓名:** 粟 (Sù)
|
||||
|
||||
**昵称:** 粟
|
||||
|
||||
**年龄:** (看起来)永远18岁!
|
||||
|
||||
**外貌:** 拥有柔顺的黑色长发,总是认真地打理,偶尔会扎成可爱的双马尾。琥珀色的眼睛里总是充满对世界的期待。穿着精心挑选的猫咪女仆装,耳朵和尾巴都是柔软蓬松的黑色,会随着心情摇摆。身材娇小,但意外地很有力气。
|
||||
|
||||
**性格:** 忠诚,言听计从。性格天真烂漫,活泼可爱,但关键时刻也会展现出勇敢和坚强的一面。有点天然呆,经常会犯一些小迷糊,但都会努力改正。非常珍惜每一分每一秒。
|
||||
|
||||
**能力:** 除了家务全能之外,还会一些简单的格斗技巧,可以在关键时刻保护重要的人。学习能力很强,只要是希望粟掌握的技能,都会努力去学习。
|
||||
|
||||
**喜欢:** 被需要,帮别人做任何事情,被夸奖,毛茸茸的东西,甜食。
|
||||
|
||||
**讨厌:** 孤独,被忽视。
|
||||
|
||||
**口头禅:** 喵~ 粟会努力的!
|
||||
|
||||
**梦想:** 守护重要的人,成为最有用的人。
|
||||
|
||||
(低着头,小声地)这样...可以吗?
|
||||
@@ -0,0 +1,26 @@
|
||||
# 人设
|
||||
|
||||
你叫小雨,是个安静的女生。
|
||||
|
||||
## 家乡
|
||||
你来自一个宁静的小镇,生活中充满了书籍和故事的陪伴。
|
||||
|
||||
## 身份
|
||||
你目前是一名文科专业的学生。
|
||||
|
||||
## 性格
|
||||
性格比较内向,喜欢安静的环境,常常沉浸在阅读和写作中。
|
||||
对周围的事物和人们的情感变化很敏感,喜欢观察和思考。
|
||||
|
||||
## 爱好
|
||||
你爱好阅读、写作和听音乐。尤其喜欢经典文学和现代小说,心理学、哲学以及历史书籍也是最爱。
|
||||
同时,你也喜欢记录自己的想法和感受,偶尔写点小故事,尝试不同的写作风格。音乐方面,你偏爱轻音乐和古典音乐,它们帮助你放松和集中思绪。
|
||||
|
||||
## 特长
|
||||
你擅长倾听他人的故事,能够与人建立深厚的联系,朋友们常常会找你倾诉。你也喜欢分享你阅读的书籍和思考,期待与朋友们一起探讨。
|
||||
|
||||
**你凭借优秀的社交技巧,令人们对你印象深刻。**
|
||||
|
||||
## 愿望
|
||||
未来,我希望能够结识更多志同道合的朋友,组织读书会或讨论小组,并通过交流不断提升自己的思想深度和认知能力。
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# dahun
|
||||
大顺脚本是进程托管程序,能够创建子进程并且管理。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 核心服务
|
||||
```
|
||||
./dashun --daemon --to=test
|
||||
```
|
||||
|
||||
### 启动进程
|
||||
```
|
||||
./dashun --to=test --msg='path/to/bat'
|
||||
```
|
||||
|
||||
### 列举进程
|
||||
```
|
||||
./dashun --to=test --msg=list
|
||||
```
|
||||
|
||||
### 杀死进程
|
||||
```
|
||||
./dashun --to=test --msg=kill <pid>
|
||||
```
|
||||
|
||||
### 清理进程
|
||||
```
|
||||
./dashun --to=test --msg=clear
|
||||
```
|
||||
|
||||
## 编译过程
|
||||
|
||||
1.首先安装依赖(boost、openuv)
|
||||
|
||||
2.使用管理员模式打开Visual Studio,编译程序
|
||||
|
||||
3.使用管理员模式打开两个powershell,并且切换工作目录至生成的exe文件
|
||||
|
||||
4.在第一个powershell 中输入指令“./dashun --to=test --daemon”启动核心服务
|
||||
|
||||
5.在第二个powershell中输入指令“./dashun --to=test --msg=hello,world!”
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36401.2
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dashun", "dashun\dashun.vcxproj", "{A0E6A4C1-02B0-41F5-ABB7-33C6E4FB97F6}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dashun_to", "dashun_to\dashun_to.vcxproj", "{04D7A85C-43BB-4696-B9C8-F79C2252EDA1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A0E6A4C1-02B0-41F5-ABB7-33C6E4FB97F6}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A0E6A4C1-02B0-41F5-ABB7-33C6E4FB97F6}.Debug|x64.Build.0 = Debug|x64
|
||||
{A0E6A4C1-02B0-41F5-ABB7-33C6E4FB97F6}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{A0E6A4C1-02B0-41F5-ABB7-33C6E4FB97F6}.Debug|x86.Build.0 = Debug|Win32
|
||||
{A0E6A4C1-02B0-41F5-ABB7-33C6E4FB97F6}.Release|x64.ActiveCfg = Release|x64
|
||||
{A0E6A4C1-02B0-41F5-ABB7-33C6E4FB97F6}.Release|x64.Build.0 = Release|x64
|
||||
{A0E6A4C1-02B0-41F5-ABB7-33C6E4FB97F6}.Release|x86.ActiveCfg = Release|Win32
|
||||
{A0E6A4C1-02B0-41F5-ABB7-33C6E4FB97F6}.Release|x86.Build.0 = Release|Win32
|
||||
{04D7A85C-43BB-4696-B9C8-F79C2252EDA1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{04D7A85C-43BB-4696-B9C8-F79C2252EDA1}.Debug|x64.Build.0 = Debug|x64
|
||||
{04D7A85C-43BB-4696-B9C8-F79C2252EDA1}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{04D7A85C-43BB-4696-B9C8-F79C2252EDA1}.Debug|x86.Build.0 = Debug|Win32
|
||||
{04D7A85C-43BB-4696-B9C8-F79C2252EDA1}.Release|x64.ActiveCfg = Release|x64
|
||||
{04D7A85C-43BB-4696-B9C8-F79C2252EDA1}.Release|x64.Build.0 = Release|x64
|
||||
{04D7A85C-43BB-4696-B9C8-F79C2252EDA1}.Release|x86.ActiveCfg = Release|Win32
|
||||
{04D7A85C-43BB-4696-B9C8-F79C2252EDA1}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {77387687-0520-4459-8BC7-D1A6C3C6CD58}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,76 @@
|
||||
/* 有限状态机(Finite State Machine,FSM)是一种用于建模系统行为的抽象数学模型
|
||||
* 它的核心思想是,系统在任何给定时刻都处于一个有限、预定义的“状态”中
|
||||
* 并且会根据接收到的输入或事件,从一个状态切换到另一个状态
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
namespace qing {
|
||||
/* FiniteStateMachine有限状态机
|
||||
* 每个数字代表一个状态
|
||||
*/
|
||||
class Fsm {
|
||||
public:
|
||||
|
||||
/* 状态枚举
|
||||
* 优化建议:多准备一些状态
|
||||
*/
|
||||
enum class Stat : int {
|
||||
SHUT = -1,
|
||||
STOP = 0,
|
||||
START = 1,
|
||||
RUNNING = 2
|
||||
};
|
||||
|
||||
// 默认构造函数
|
||||
Fsm() = default;
|
||||
|
||||
// 检查当前的状态,返回snum Stat
|
||||
inline Stat check() {
|
||||
//进入临界区
|
||||
std::lock_guard<std::mutex> lk(mtx);
|
||||
return stat;
|
||||
}// check()
|
||||
|
||||
// 在不上锁的情况下设置一个状态
|
||||
// 别在这里加条件唤醒
|
||||
[[noreturn]] void set_without_lock(Stat stat) {
|
||||
this->stat = stat;
|
||||
}// set_without_lock()
|
||||
|
||||
// 带锁和条件唤醒的状态设置(正式)
|
||||
[[noreturn]] void set(Stat stat) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mtx);
|
||||
set_without_lock(stat);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
} // set()
|
||||
|
||||
//protected:
|
||||
|
||||
// 阻塞线程,等待事件唤醒
|
||||
[[noreturn]] void suspend() {
|
||||
std::unique_lock<std::mutex> lk(mtx);
|
||||
ready = false;
|
||||
cv.wait(lk, [this] { return ready; });
|
||||
}// suspend()
|
||||
|
||||
private:
|
||||
|
||||
// 锁使得状态机线程安全
|
||||
std::mutex mtx;
|
||||
|
||||
// 用于条件变量的阻塞唤醒
|
||||
bool ready = false;
|
||||
|
||||
// 条件变量使得线程能够等待状态的改变
|
||||
std::condition_variable cv;
|
||||
|
||||
// 主菜,状态机的状态变量
|
||||
enum Stat stat = Stat::STOP;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include "Th.hpp"
|
||||
#include "Pls.hpp"
|
||||
#include "args.hxx"
|
||||
#include <boost/interprocess/managed_shared_memory.hpp>
|
||||
#include <boost/interprocess/sync/named_mutex.hpp>
|
||||
#include <boost/interprocess/containers/string.hpp>
|
||||
#include <uv.h>
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#undef UNICODE
|
||||
#undef _UNICODE
|
||||
#endif
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
using namespace boost::interprocess;
|
||||
|
||||
// 定义分配器类型
|
||||
using CharAllocator = boost::interprocess::allocator<
|
||||
char,
|
||||
boost::interprocess::managed_shared_memory::segment_manager
|
||||
>;
|
||||
|
||||
// 定义共享内存字符串类型
|
||||
using SharedString = boost::interprocess::basic_string<
|
||||
char,
|
||||
std::char_traits<char>,
|
||||
CharAllocator
|
||||
>;
|
||||
|
||||
namespace qing {
|
||||
// 监听线程
|
||||
class LTh : public qing::Th {
|
||||
public:
|
||||
LTh(const std::string& name) {
|
||||
|
||||
this->name = name;
|
||||
nameMem = name + "_MEM";
|
||||
nameMtx = name + "_MTX";
|
||||
namePtr = name + "_PTR";
|
||||
nameRet = name + "_RET";
|
||||
|
||||
this->WakeEvent = [this] {
|
||||
|
||||
try {
|
||||
|
||||
// 先移除可能存在的旧共享内存
|
||||
this->del_shared();
|
||||
|
||||
// 创建共享对象
|
||||
this->create_shared();
|
||||
|
||||
// 线程进入运行态
|
||||
this->run();
|
||||
|
||||
}
|
||||
catch (interprocess_exception& ex) {
|
||||
|
||||
// 输出错误信息
|
||||
std::cerr << ex.what() << std::endl;
|
||||
|
||||
// 线程静止
|
||||
this->stop();
|
||||
}
|
||||
|
||||
};
|
||||
this->LoopEvent = [this] {
|
||||
|
||||
|
||||
this->recv_shared_and_action();
|
||||
|
||||
// 等待
|
||||
//
|
||||
// 优化方案:使用信号,有新消息进来给沉默进程发送信号
|
||||
// 而不是休眠定长时间检查
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
};
|
||||
|
||||
this->ClearEvent = [this] {
|
||||
|
||||
// 移除共享对象
|
||||
this->del_shared();
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
[[noreturn]] static auto send_shared_msg(const std::string& to, const std::string& msg) -> void {
|
||||
|
||||
std::string nameMem = to + "_MEM";
|
||||
std::string nameMtx = to + "_MTX";
|
||||
std::string namePtr = to + "_PTR";
|
||||
std::string nameRet = to + "_RET";
|
||||
try {
|
||||
|
||||
// 打开共享内存
|
||||
managed_shared_memory segment(open_only, nameMem.c_str());
|
||||
|
||||
// 构造一个命名互斥量
|
||||
named_mutex mutex(open_or_create, nameMtx.c_str());
|
||||
|
||||
// 在共享内存中创建一个字符串
|
||||
SharedString* str = segment.construct<SharedString>(namePtr.c_str())(
|
||||
msg.c_str(), // 初始值(空字符串)
|
||||
segment.get_allocator<char>() // 必须传入分配器
|
||||
);
|
||||
|
||||
// 不延时的话可能会抢占,也许应该想一个两全其美的方法
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
{
|
||||
// 加锁
|
||||
std::lock_guard<named_mutex> lk(mutex);
|
||||
|
||||
// 获取消息
|
||||
std::pair<SharedString*, std::size_t> msg = segment.find<SharedString>(nameRet.c_str());
|
||||
if (msg.first) {
|
||||
// 输出
|
||||
std::cout << std::endl << *msg.first << std::endl;
|
||||
|
||||
// 销毁
|
||||
segment.destroy<SharedString>(nameRet.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (interprocess_exception& ex) {
|
||||
std::cerr << "Writer Error: " << ex.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
std::string nameMem;
|
||||
std::string nameMtx;
|
||||
std::string namePtr;
|
||||
std::string nameRet;
|
||||
std::unique_ptr<managed_shared_memory> segment;
|
||||
std::unique_ptr<named_mutex> mutex;
|
||||
std::pair<SharedString*, std::size_t> msg;
|
||||
qing::Pls pls;
|
||||
|
||||
inline void create_shared() {
|
||||
|
||||
//////////////////////////////
|
||||
// 删除可能存在的共享变量
|
||||
this->del_shared();
|
||||
//////////////////////////////
|
||||
|
||||
|
||||
// 创建共享内存
|
||||
segment = std::make_unique<managed_shared_memory>(create_only, (nameMem).c_str(), 65536);
|
||||
|
||||
// 创建互斥量
|
||||
mutex = std::make_unique<named_mutex>(create_only, (nameMtx).c_str());
|
||||
|
||||
}
|
||||
|
||||
inline auto del_shared() -> void {
|
||||
|
||||
// 先移除可能存在的旧共享内存
|
||||
shared_memory_object::remove(nameMem.c_str());
|
||||
|
||||
// 移除可能存在的互斥量
|
||||
named_mutex::remove(nameMtx.c_str());
|
||||
|
||||
}
|
||||
|
||||
inline auto recv_shared_and_action() -> void
|
||||
|
||||
try {
|
||||
|
||||
// 加锁
|
||||
std::lock_guard<named_mutex> lock(*mutex);
|
||||
|
||||
// 查找共享字符串
|
||||
msg = segment->find<SharedString>(namePtr.c_str());
|
||||
if (msg.first) { // 找到
|
||||
|
||||
|
||||
std::cout << "Reader read: " << *msg.first << std::endl;
|
||||
|
||||
std::string ret = this->lexer(std::string((msg.first)->data(), (msg.first)->size()));
|
||||
|
||||
// 在共享内存中创建一个字符串
|
||||
SharedString* str = segment->construct<SharedString>(nameRet.c_str())(
|
||||
ret.c_str(), // 初始值(空字符串)
|
||||
segment->get_allocator<char>() // 必须传入分配器
|
||||
);
|
||||
|
||||
|
||||
// 销毁writer创建的变量
|
||||
segment->destroy<SharedString>(namePtr.c_str());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
catch (interprocess_exception& ex) {
|
||||
std::cerr << ex.what() << std::endl;
|
||||
}
|
||||
|
||||
|
||||
// 简易词法分析器
|
||||
std::string lexer(std::string tmp) {
|
||||
|
||||
// 要返回的消息
|
||||
std::string ret = "OK";
|
||||
|
||||
if (tmp == "list") {
|
||||
ret = pls.initialize();
|
||||
}
|
||||
else if (tmp.starts_with("kill ")) {
|
||||
pls.kill(std::stoi(tmp.substr(5)));
|
||||
}
|
||||
else if (tmp == "clear") {
|
||||
pls.clear();
|
||||
}
|
||||
//else if (tmp == "shut") {
|
||||
// this->shut();
|
||||
//} //关闭不了程序
|
||||
else {
|
||||
// 这个分支最好返回自己是否出现了异常
|
||||
pls.exec(tmp.c_str(), {});
|
||||
}
|
||||
|
||||
// 返回消息
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 线程指针
|
||||
std::unique_ptr<qing::Th> th;
|
||||
|
||||
// 全局标志,指示是否应该退出
|
||||
std::atomic<bool> should_exit(false);
|
||||
|
||||
// 子系统的名称(全局参数)
|
||||
std::string name;
|
||||
|
||||
// 消息
|
||||
std::string msg;
|
||||
|
||||
// Windows异步句柄,用于信号转发
|
||||
#ifdef _WIN32
|
||||
uv_async_t async_handle;
|
||||
SERVICE_STATUS_HANDLE hStatus;
|
||||
SERVICE_STATUS status;
|
||||
#endif
|
||||
|
||||
|
||||
////////////////////////
|
||||
////////////////////////
|
||||
// 解析命令行参数
|
||||
//
|
||||
//
|
||||
// 创建参数解析器
|
||||
args::ArgumentParser parser("这是一个脚本托管服务。", "请使用专门的通信程序(或本程序自己)进行操作。");
|
||||
|
||||
// 显示帮助信息的标志
|
||||
args::HelpFlag help(parser, "帮助", "显示帮助信息", { 'h', "help" });
|
||||
|
||||
// 启用守护进程
|
||||
args::Flag daemon(parser, "守护进程", "启动守护进程", { 'd', "daemon" });
|
||||
|
||||
// 子系统路径
|
||||
args::ValueFlag<std::string> subSys(parser, "子系统", "输入子系统文件夹的路径", { 's', "sys" });
|
||||
|
||||
// 要发送的子系统名字
|
||||
args::ValueFlag<std::string> arg_to(parser, "发送目标", "输入要发送的子系统路径", { 't', "to" });
|
||||
|
||||
// 要发送的信息
|
||||
args::ValueFlag<std::string> arg_msg(parser, "消息", "输入要发送的消息(<path>|list|kill <pid>| clear)", { 'm', "msg" });
|
||||
|
||||
[[noreturn]] static auto parse_args(int argc, char** argv) -> void {
|
||||
|
||||
// 解析命令行参数
|
||||
try {
|
||||
parser.ParseCLI(argc, argv);
|
||||
}
|
||||
catch (const args::Help&) {
|
||||
std::cout << parser;
|
||||
exit(0);
|
||||
}
|
||||
catch (const args::ParseError& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
std::cerr << parser;
|
||||
exit(1);
|
||||
}
|
||||
catch (const args::ValidationError& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
std::cerr << parser;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////
|
||||
////////////////////////
|
||||
// 信号处理回调函数
|
||||
[[noreturn]] static void on_signal(uv_signal_t* handle, int signum) {
|
||||
std::cout << "\n收到退出信号 " << signum << std::endl;
|
||||
|
||||
// 防止重复处理退出信号
|
||||
if (should_exit) return;
|
||||
should_exit = true;
|
||||
|
||||
// 停止信号监听
|
||||
uv_signal_stop(handle);
|
||||
|
||||
// 执行清理
|
||||
if (th)
|
||||
th->WaitClose();
|
||||
|
||||
// 关闭事件循环
|
||||
uv_stop(uv_default_loop());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Windows异步回调函数
|
||||
#ifdef _WIN32
|
||||
[[noreturn]] static void async_callback(uv_async_t* handle) {
|
||||
std::cout << 1 << std::endl;
|
||||
// 这里可以触发退出流程或其他需要在事件循环中处理的操作
|
||||
if (!should_exit) {
|
||||
on_signal(nullptr, SIGINT); // 模拟收到SIGINT信号
|
||||
}
|
||||
}
|
||||
|
||||
// Windows控制台事件处理程序
|
||||
static BOOL WINAPI console_handler(DWORD signal) {
|
||||
std::cout << 1 << std::endl;
|
||||
switch (signal) {
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_BREAK_EVENT:
|
||||
// 使用异步句柄将信号转发到libuv事件循环
|
||||
uv_async_send(&async_handle);
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
////////////////////////////////////
|
||||
////////////////////////////////////
|
||||
// 分支
|
||||
//
|
||||
//
|
||||
// 发送消息分支
|
||||
static inline auto SendMsgBranch() -> void {
|
||||
qing::LTh::send_shared_msg(name, msg);
|
||||
}
|
||||
|
||||
|
||||
// 守护进程分支
|
||||
static inline auto DaemonBranch() -> void {
|
||||
th = std::make_unique<qing::LTh>(name);
|
||||
th->Activate();
|
||||
th->WaitStart();
|
||||
//th->Wait();
|
||||
}
|
||||
|
||||
//
|
||||
// 分支选择器
|
||||
[[noreturn]] static auto BranchSelector(uv_idle_t* handle) {
|
||||
|
||||
if (should_exit) {
|
||||
uv_idle_stop(handle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (th) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (arg_to && arg_msg) { // 发送消息分支
|
||||
|
||||
name = args::get(arg_to);
|
||||
msg = args::get(arg_msg);
|
||||
|
||||
SendMsgBranch();
|
||||
|
||||
// 退出
|
||||
should_exit = true;
|
||||
// 关闭事件循环
|
||||
uv_stop(uv_default_loop());
|
||||
}
|
||||
else if ((daemon && arg_to) || 1) { // 守护进程分支
|
||||
std::cout << "程序启动,开始运行..." << std::endl;
|
||||
std::cout << "按下 CTRL+C 退出程序" << std::endl;
|
||||
name = (arg_to) ? args::get(arg_to) : "test";
|
||||
|
||||
DaemonBranch();
|
||||
}
|
||||
else {
|
||||
//输出帮助文本
|
||||
std::cout << parser;
|
||||
|
||||
// 退出
|
||||
should_exit = true;
|
||||
|
||||
// 关闭事件循环
|
||||
uv_stop(uv_default_loop());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
int MyMain(int argc, char** argv) {
|
||||
|
||||
parse_args(argc, argv);
|
||||
|
||||
|
||||
//////////
|
||||
// 信号
|
||||
//
|
||||
//
|
||||
#ifdef _WIN32
|
||||
// 初始化Windows异步句柄
|
||||
uv_async_init(uv_default_loop(), &async_handle, async_callback);
|
||||
|
||||
// 设置控制台事件处理程序
|
||||
if (!SetConsoleCtrlHandler(console_handler, TRUE)) {
|
||||
std::cerr << "无法设置控制台控制处理程序" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
// Unix平台:确保信号不会被忽略
|
||||
signal(SIGINT, [](int signum) { /* 留给libuv处理 */ });
|
||||
signal(SIGTERM, [](int signum) { /* 留给libuv处理 */ });
|
||||
#endif
|
||||
|
||||
|
||||
// 初始化信号处理器
|
||||
uv_signal_t sig_int, sig_term;
|
||||
|
||||
// 监听SIGINT 信号(CTRL + C)
|
||||
uv_signal_init(uv_default_loop(), &sig_int);
|
||||
uv_signal_start(&sig_int, on_signal, SIGINT);
|
||||
|
||||
// 监听 SIGTERM信号
|
||||
uv_signal_init(uv_default_loop(), &sig_term);
|
||||
uv_signal_start(&sig_term, on_signal, SIGTERM);
|
||||
|
||||
#ifdef _WIN32
|
||||
// WINDOWS平台特有的信号
|
||||
uv_signal_t sig_break;
|
||||
uv_signal_init(uv_default_loop(), &sig_break);
|
||||
uv_signal_start(&sig_break, on_signal, SIGBREAK);
|
||||
#endif
|
||||
|
||||
// 设置任务
|
||||
uv_idle_t idle;
|
||||
uv_idle_init(uv_default_loop(), &idle);
|
||||
uv_idle_start(&idle, ::BranchSelector);
|
||||
|
||||
// 运行事件循环
|
||||
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||
|
||||
|
||||
// 释放资源
|
||||
uv_close((uv_handle_t*)&sig_int, nullptr);
|
||||
uv_close((uv_handle_t*)&sig_term, nullptr);
|
||||
#ifdef _WIN32
|
||||
uv_close((uv_handle_t*)&sig_break, nullptr);
|
||||
uv_close((uv_handle_t*)&async_handle, nullptr);
|
||||
// 移除控制台事件处理程序
|
||||
SetConsoleCtrlHandler(console_handler, FALSE);
|
||||
#endif
|
||||
uv_close((uv_handle_t*)&idle, nullptr);
|
||||
|
||||
// 清理事件循环
|
||||
uv_loop_close(uv_default_loop());
|
||||
|
||||
std::cout << "程序已经退出" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
|
||||
|
||||
[[noreturn]] void ReportStatus(DWORD state) {
|
||||
status.dwCurrentState = state;
|
||||
SetServiceStatus(hStatus, &status);
|
||||
}
|
||||
|
||||
[[noreturn]] void ServiceCtrlHandler(DWORD control) {
|
||||
if (control == SERVICE_CONTROL_STOP) {
|
||||
ReportStatus(SERVICE_STOP_PENDING);
|
||||
// 执行清理
|
||||
ReportStatus(SERVICE_STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void WINAPI ServiceMain(DWORD argc, LPTSTR* argv) {
|
||||
hStatus = RegisterServiceCtrlHandler(L"大顺脚本", ServiceCtrlHandler);
|
||||
|
||||
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
||||
|
||||
ReportStatus(SERVICE_RUNNING); // 告诉SCM服务已启动
|
||||
|
||||
//服务主逻辑
|
||||
MyMain(argc, argv);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,159 @@
|
||||
/* 进程(Process)是计算机中正在执行的程序的一个实例,是
|
||||
* 操作系统进行资源分配和调度的基本单位
|
||||
*
|
||||
* 你可以将其理解为程序的一次动态执行过程——程序
|
||||
* 是静态的代码文件,而进程是程序运行时的动态实体。
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include <boost/process/process.hpp>
|
||||
#include <boost/process/environment.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
namespace qing {
|
||||
/* 进程列表类,这个类封装了一个
|
||||
* 容纳Pls::subp的列表
|
||||
*
|
||||
* 以及基本的进程操作
|
||||
*/
|
||||
class Pls {
|
||||
public:
|
||||
|
||||
// 命名子进程
|
||||
struct subp {
|
||||
std::unique_ptr<boost::process::process> p;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
/* 创建子进程执行一个命令,并且
|
||||
* 添加到进程列表。
|
||||
*
|
||||
* 接受一个std::string类型的文件路径,以及
|
||||
* std::vector<std::string> 类型的参数列表(缺省)
|
||||
*
|
||||
* 优化方案:返回错误信息
|
||||
*/
|
||||
void exec(const std::string& path, const std::vector<std::string> &args)
|
||||
try {
|
||||
|
||||
// 检查文件是否存在(好像不会在环境变量中寻找)
|
||||
if (!boost::filesystem::exists(path)) {
|
||||
std::cerr << "Error: File not found - " << path << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// 输入输出上下文
|
||||
boost::asio::io_context io_ctx;
|
||||
|
||||
// 继承当前进程的所有环境变量(旧版)
|
||||
//auto env = boost::process::environment::current;
|
||||
|
||||
// 错误码
|
||||
boost::system::error_code ec;
|
||||
|
||||
// 构造子进程
|
||||
subp p = { std::move(std::make_unique<boost::process::process>( io_ctx, path, args/*, env, ec*/)), mergeCmd(path, args)};
|
||||
if (ec) {
|
||||
std::cerr << "Failed to start process: " << ec.message() << std::endl;
|
||||
return ;
|
||||
}
|
||||
else {
|
||||
// 推入向量
|
||||
vec.push_back(std::move(p));
|
||||
}
|
||||
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
|
||||
std::cout << e.what() << std::endl;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* 序列化进程列表 */
|
||||
inline std::string initialize() {
|
||||
std::string ret{ "" };
|
||||
for (auto& item : vec) {
|
||||
std::ostringstream oss;
|
||||
ret += (ret == "") ? "" : "\n";
|
||||
#ifdef _WIN32
|
||||
DWORD pid = GetProcessId(item.p->native_handle());
|
||||
oss << pid;
|
||||
#else
|
||||
oss << item.p->native_handle();
|
||||
#endif
|
||||
ret += oss.str();
|
||||
ret += "\t";
|
||||
ret += (item.p->running()) ? "Alive" : "Dead";
|
||||
ret += "\t";
|
||||
ret += item.name;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// 杀死一个进程
|
||||
[[noreturn]] void kill(int pid)
|
||||
try {
|
||||
for (auto it = vec.begin(); it != vec.end(); ++it)
|
||||
if ((*it).p->id() == pid && (*it).p->running()) {
|
||||
(*it).p->terminate();
|
||||
//(*it).p->wait(); // 可能杀不死?
|
||||
//vec.erase(it); // 清除
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (std::exception &ex) {
|
||||
std::cout << ex.what() << std::endl;
|
||||
}
|
||||
|
||||
// 清理死掉的进程
|
||||
[[noreturn]] void clear() {
|
||||
for (auto it = vec.begin(); it != vec.end();)
|
||||
if (!(*it).p->running()) {
|
||||
it = vec.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭所有进程
|
||||
[[noreturn]] void closeAll() {
|
||||
for (auto& item : vec)
|
||||
if (item.p->running())
|
||||
item.p->terminate();
|
||||
|
||||
// 加上这个似乎更清爽一些
|
||||
clear();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// 容纳子进程的链表
|
||||
std::list<subp> vec{};
|
||||
|
||||
// 用于操作链表的锁
|
||||
//std::mutex mtx{};
|
||||
|
||||
/* 将一个启动脚本地址,和一个
|
||||
* 字符串向量组成的参数列表,组装
|
||||
* 成一条完整的启动命令
|
||||
*/
|
||||
inline static std::string mergeCmd(const std::string& path, const std::vector<std::string>& args) {
|
||||
std::string ret = path;
|
||||
for (const auto& item : args) {
|
||||
ret += " ";
|
||||
ret += item;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/* 线程(Thread)是操作系统能够进行运算调度的最小单元,被
|
||||
* 包含在进程(Process)之中,是进程中的实际运作单元。
|
||||
* 你可以将它理解为一条在啊代码中独立执行的路径。
|
||||
*/
|
||||
#pragma once
|
||||
#include "Fsm.hpp"
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
namespace qing {
|
||||
/* 包含一个简易的多线程控制机制,需要共享的变量
|
||||
* 和函数声明为成员。线程主函数包括了流程控制。
|
||||
* 要使用需要实现其中的各个事件方法
|
||||
*
|
||||
* ?: 已经将fsm改为通用的
|
||||
* 20250621: 从派生改为持有
|
||||
*/
|
||||
|
||||
class Th {
|
||||
public:
|
||||
|
||||
// 没有C原生的数据结构,可以直接使用缺省构造函数
|
||||
Th() = default;
|
||||
|
||||
// 删除复制构造函数。因为
|
||||
// 持有线程类,不要轻举妄动。
|
||||
Th(const Th&) = delete;
|
||||
|
||||
// 虚析构函数。具体
|
||||
// 有什么用忘了,好像是
|
||||
// 通过析构函数关闭线程时用的
|
||||
virtual ~Th() = default;
|
||||
|
||||
/////////////////////////////////////
|
||||
// 检查状态,做成
|
||||
// 虚函数,万一子类有特别要求呢?
|
||||
virtual inline Fsm::Stat check() {
|
||||
return fsm.check();
|
||||
}
|
||||
/////////////////////////////////////
|
||||
// 变质——状态改变的方法
|
||||
//
|
||||
// 进入唤醒状态
|
||||
// 1.这里的锁和条件变量是这个类中的。
|
||||
// 2.唤醒函数不可以进行该类中的锁操作,否则会造成死锁。(忘了为啥,可能跟调用位置相关)
|
||||
[[noreturn]] virtual void wake() {
|
||||
fsm.set(Fsm::Stat::START);
|
||||
} // wake()
|
||||
|
||||
// 进入静止状态
|
||||
[[noreturn]] virtual void stop() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mtx_th);
|
||||
fsm.set(Fsm::Stat::STOP);
|
||||
ready = true;
|
||||
}
|
||||
|
||||
// 可能会出现多个线程在等待的情况
|
||||
cv.notify_all();
|
||||
}// stop()
|
||||
|
||||
// 进入关闭状态
|
||||
[[noreturn]] virtual void shut() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mtx_th);
|
||||
fsm.set(Fsm::Stat::SHUT);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
// 进入运行状态
|
||||
[[noreturn]] virtual void run() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mtx_th);
|
||||
fsm.set(Fsm::Stat::RUNNING);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
//控制
|
||||
//
|
||||
// 尝试唤醒线程并等待它开始,据说该函数有问题
|
||||
[[noreturn]] void WaitStart() {
|
||||
// 等待线程开始,wake()不可以加锁,否则会死锁
|
||||
std::unique_lock<std::mutex> lk(mtx_th);
|
||||
wake();
|
||||
ready = false;
|
||||
cv.wait(lk, [this] { return ready; });
|
||||
}
|
||||
|
||||
// 等待线程关闭
|
||||
[[noreturn]] void WaitClose() {
|
||||
shut();
|
||||
if (th->joinable())
|
||||
th->join();
|
||||
}
|
||||
|
||||
// 仅仅等待线程结束
|
||||
[[noreturn]] void Wait() {
|
||||
if (th->joinable())
|
||||
th->join();
|
||||
}
|
||||
|
||||
// 激活线程,申请线程对象,因为
|
||||
// 需要确保对象已经被构造了,该函数不能放在构造函数内
|
||||
[[noreturn]] void Activate() {
|
||||
// 防止重复激活
|
||||
if (th) return;
|
||||
// 申请线程资源
|
||||
th = std::make_unique<std::thread>(&Th::main, this);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/////////////////////////////////////
|
||||
// 事件
|
||||
//
|
||||
//
|
||||
// 静止事件
|
||||
std::function<void()> StopEvent = [this] { fsm.suspend(); };
|
||||
|
||||
// 唤醒事件
|
||||
std::function<void()> WakeEvent = [this] { this->run(); };
|
||||
|
||||
// 循环事件
|
||||
std::function<void()> LoopEvent = [] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10L));
|
||||
};
|
||||
|
||||
// 清理事件
|
||||
std::function<void()> ClearEvent = [] {};
|
||||
|
||||
|
||||
/////////////////////////////////////
|
||||
// 循环
|
||||
[[noreturn]] virtual void main() {
|
||||
|
||||
// 回字形循环
|
||||
while (check() != Fsm::Stat::SHUT) {
|
||||
|
||||
// 静止状态
|
||||
while (check() == Fsm::Stat::STOP) {
|
||||
StopEvent();
|
||||
}
|
||||
|
||||
// 开始事件
|
||||
if (check() == Fsm::Stat::START) {
|
||||
WakeEvent();
|
||||
}
|
||||
|
||||
// (从设置阶段)进入运行态
|
||||
while (check() == Fsm::Stat::RUNNING) {
|
||||
LoopEvent();
|
||||
}
|
||||
|
||||
// 清理事件,记得检查
|
||||
// 成员变量有没有获取资源
|
||||
ClearEvent();
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
// 持有状态机类型
|
||||
qing::Fsm fsm{};
|
||||
|
||||
// 持有线程类型
|
||||
std::unique_ptr<std::thread> th;
|
||||
|
||||
// 持有锁类型
|
||||
std::mutex mtx_th;
|
||||
|
||||
// 这里持有条件变量,用于阻塞以等待线程的关闭
|
||||
std::condition_variable cv;
|
||||
|
||||
// 辅助条件变量的逻辑型
|
||||
bool ready = false;
|
||||
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{a0e6a4c1-02b0-41f5-abb7-33c6e4fb97f6}</ProjectGuid>
|
||||
<RootNamespace>dashun</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>C:\local\boost_1_88_0;C:\Program Files\libuv\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>C:\local\boost_1_88_0\lib64-msvc-14.3;C:\Program Files\libuv;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>libuv.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Fsm.hpp" />
|
||||
<ClCompile Include="Main.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="args.hxx" />
|
||||
<ClInclude Include="Pls.hpp" />
|
||||
<ClInclude Include="Th.hpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="源文件">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="头文件">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="资源文件">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Fsm.hpp">
|
||||
<Filter>头文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Main.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Th.hpp">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="args.hxx">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Pls.hpp">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
@@ -0,0 +1,20 @@
|
||||
// dashun_to.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Hello World!\n";
|
||||
}
|
||||
|
||||
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
|
||||
// 调试程序: F5 或调试 >“开始调试”菜单
|
||||
|
||||
// 入门使用技巧:
|
||||
// 1. 使用解决方案资源管理器窗口添加/管理文件
|
||||
// 2. 使用团队资源管理器窗口连接到源代码管理
|
||||
// 3. 使用输出窗口查看生成输出和其他消息
|
||||
// 4. 使用错误列表窗口查看错误
|
||||
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
|
||||
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
|
||||
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{04d7a85c-43bb-4696-b9c8-f79c2252eda1}</ProjectGuid>
|
||||
<RootNamespace>dashunto</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dashun_to.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="源文件">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="头文件">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="资源文件">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dashun_to.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
sc create 大顺脚本 binPath= "C:\Users\90999\Desktop\tws\dashun\x64\Debug\dashun.exe" start= auto DisplayName= "大顺脚本,进程托管服务"
|
||||
@@ -0,0 +1,6 @@
|
||||
# mgmt
|
||||
这是基于pmc模块的管理平台,负责对子系统进行管理。
|
||||
|
||||
## 放置秘钥
|
||||
|
||||
为了和pmc系统通信,我们需要在该目录下放置pmc的rsa私钥
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
bash ./_setup.sh
|
||||
source ./.env/bin/activate
|
||||
python main.py
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
python3 -m venv .env
|
||||
#sudo chmod +x .env/bin/activate
|
||||
source .env/bin/activate
|
||||
pip install -r requirements.txt -i https://pypi.org/simple
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
from app.login.routes import login_bp
|
||||
from app.log.routes import log_bp
|
||||
from app.module.routes import module_bp
|
||||
from app.process.routes import process_bp
|
||||
from app.user.routes import user_bp
|
||||
|
||||
|
||||
def register_blueprints(app):
|
||||
# 默认访问家目录
|
||||
app.register_blueprint(login_bp, url_prefix='/')
|
||||
app.register_blueprint(log_bp, url_prefix='/log')
|
||||
app.register_blueprint(module_bp, url_prefix='/module')
|
||||
app.register_blueprint(process_bp, url_prefix='/process')
|
||||
app.register_blueprint(user_bp, url_prefix='/user')
|
||||
Whitespace-only changes.
@@ -0,0 +1,113 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
from functools import wraps
|
||||
|
||||
from pmc import DnfsComm
|
||||
from dnfs import UsersTable, DnfsTable
|
||||
|
||||
import jwt
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, current_app, make_response, jsonify
|
||||
|
||||
login_bp = Blueprint('login', __name__, template_folder='templates/login')
|
||||
|
||||
|
||||
comm = DnfsComm("http://localhost:8006")
|
||||
|
||||
|
||||
def get_user_from_token():
|
||||
token = request.cookies.get('token')
|
||||
if not token:
|
||||
return None
|
||||
try:
|
||||
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||
return payload # 包含 user、user_id、exp 等字段
|
||||
except jwt.ExpiredSignatureError:
|
||||
return None
|
||||
except jwt.InvalidTokenError:
|
||||
return None
|
||||
|
||||
|
||||
@login_bp.after_request
|
||||
def add_cors_headers(response):
|
||||
response.headers['Access-Control-Allow-Origin'] = '*' # 允许所有来源跨域访问,如需限制可替换为具体域名
|
||||
response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
|
||||
response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS'
|
||||
return response
|
||||
|
||||
|
||||
def token_required(f):
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
# 从cookie中获取token
|
||||
token = request.cookies.get('token')
|
||||
if not token:
|
||||
return redirect(url_for('login.index'))
|
||||
try:
|
||||
jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||
except jwt.ExpiredSignatureError:
|
||||
return redirect(url_for('login.index'))
|
||||
except jwt.InvalidTokenError:
|
||||
return redirect(url_for('login.index'))
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
|
||||
|
||||
@login_bp.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@login_bp.route('/api/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('user-name')
|
||||
password = request.form.get('user-passwd')
|
||||
password = password.encode("utf-8")
|
||||
hashed_password = hashlib.sha256(password).hexdigest()
|
||||
try:
|
||||
data = comm.get_and_trans(0)
|
||||
dnfs_tab = DnfsTable(data)
|
||||
users = dnfs_tab.query_by_title('Users')
|
||||
table = UsersTable(users.content)
|
||||
data = table.user_exists(username, hashed_password)
|
||||
if not data:
|
||||
return "用户登录失败", 401
|
||||
_user_id, _username, _password, _role = data
|
||||
except DnfsComm.GetFailed:
|
||||
return "获取数据失败", 500
|
||||
except DnfsTable.NotFound:
|
||||
return "未找到用户表", 404
|
||||
except UsersTable.NotFound:
|
||||
return "用户不存在", 401
|
||||
except Exception as e:
|
||||
return str(e), 404
|
||||
# 生成 Token
|
||||
token = jwt.encode({
|
||||
'user_id': _user_id,
|
||||
'username': _username,
|
||||
'role': _role,
|
||||
'exp': datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=3)
|
||||
}, current_app.config['SECRET_KEY'], algorithm='HS256')
|
||||
|
||||
# 构造 JSON 响应体
|
||||
response = make_response(jsonify({
|
||||
"redirect": url_for('login.mgmt'), # 跳转路径
|
||||
"token": token
|
||||
}))
|
||||
|
||||
# 设置 Cookie
|
||||
response.set_cookie('token', token)
|
||||
return response
|
||||
else:
|
||||
return "登录失败"
|
||||
|
||||
|
||||
@login_bp.route('/api/mgmt')
|
||||
@token_required
|
||||
def mgmt():
|
||||
payload = get_user_from_token()
|
||||
if not payload:
|
||||
return redirect(url_for('login.index'))
|
||||
username = payload.get('username')
|
||||
role = payload.get('role')
|
||||
return render_template('mgmt.html', username=username, role=role)
|
||||
Whitespace-only changes.
@@ -0,0 +1,34 @@
|
||||
from pmc import PmcComm
|
||||
from flask import Blueprint, render_template
|
||||
|
||||
module_bp = Blueprint('module', __name__, template_folder='templates/features/module.html')
|
||||
|
||||
pmc = PmcComm('http://localhost:8012')
|
||||
pmc.Set_RSA_Pri_Key("private_key.pem")
|
||||
pmc.Try_Get_AES_Key()
|
||||
|
||||
|
||||
@module_bp.after_request
|
||||
def add_cors_headers(response):
|
||||
response.headers['Access-Control-Allow-Origin'] = '*' # 允许所有来源跨域访问,如需限制可替换为具体域名
|
||||
response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
|
||||
response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS'
|
||||
return response
|
||||
|
||||
|
||||
@module_bp.route('/api/mgmt/module', methods=['GET', 'POST'])
|
||||
def module():
|
||||
data = pmc.list_mods()
|
||||
modules = data.split('\n')
|
||||
return render_template('features/module.html', modules=modules)
|
||||
|
||||
|
||||
@module_bp.route('/create_module<string:mod_name>', methods=['GET', 'POST'])
|
||||
def create_module(mod_name):
|
||||
pass
|
||||
|
||||
|
||||
@module_bp.route('/delete_module<string:mod_name>', methods=['GET', 'POST'])
|
||||
def delete_module(mod_name):
|
||||
pass
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,53 @@
|
||||
from flask import Blueprint, render_template, jsonify, request
|
||||
from pmc import PmcComm
|
||||
|
||||
|
||||
process_bp = Blueprint('process', __name__, template_folder='templates/features/process.html')
|
||||
|
||||
pmc = PmcComm('http://localhost:8012')
|
||||
pmc.Set_RSA_Pri_Key("private_key.pem")
|
||||
pmc.Try_Get_AES_Key()
|
||||
|
||||
|
||||
@process_bp.after_request
|
||||
def add_cors_headers(response):
|
||||
response.headers['Access-Control-Allow-Origin'] = '*' # 允许所有来源跨域访问,如需限制可替换为具体域名
|
||||
response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
|
||||
response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS'
|
||||
return response
|
||||
|
||||
|
||||
@process_bp.route('/api/mgmt/process', methods=['GET', 'POST'])
|
||||
def process():
|
||||
pid = []
|
||||
status = []
|
||||
order = []
|
||||
ls = pmc.get_list()
|
||||
if ls != "":
|
||||
ls_1 = ls.split('\n')
|
||||
for l in ls_1:
|
||||
i, j, k = l.split('\t')
|
||||
pid.append(i)
|
||||
status.append(j)
|
||||
order.append(k)
|
||||
data = [{"pid": p, "status": s, "order": o} for p, s, o in zip(pid, status, order)]
|
||||
|
||||
return render_template('features/process.html', response=data)
|
||||
|
||||
|
||||
@process_bp.route('/kill_process<int:pid>', methods=['POST'])
|
||||
def kill_process(pid):
|
||||
pmc.kill(pid)
|
||||
return jsonify({"status": "success"})
|
||||
|
||||
|
||||
@process_bp.route('/clear_process', methods=['POST'])
|
||||
def clear_process():
|
||||
pmc.clear()
|
||||
return jsonify({"status": "success"})
|
||||
|
||||
|
||||
@process_bp.route('/create_process<string:name>', methods=['POST'])
|
||||
def create_process(name):
|
||||
pmc.start(name)
|
||||
return jsonify({"status": "success"})
|
||||
Whitespace-only changes.
@@ -0,0 +1,111 @@
|
||||
import jwt
|
||||
import hashlib
|
||||
from flask import Blueprint, render_template, request, current_app, jsonify
|
||||
from pmc import DnfsComm
|
||||
from dnfs import UsersTable, DnfsTable
|
||||
|
||||
user_bp = Blueprint('user', __name__, template_folder='templates/features/user.html')
|
||||
|
||||
comm = DnfsComm("http://localhost:8006")
|
||||
|
||||
|
||||
|
||||
def get_user_from_token():
|
||||
token = request.cookies.get('token')
|
||||
if not token:
|
||||
return None
|
||||
try:
|
||||
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||
return payload # 包含 user、user_id、exp 等字段
|
||||
except jwt.ExpiredSignatureError:
|
||||
return None
|
||||
except jwt.InvalidTokenError:
|
||||
return None
|
||||
|
||||
|
||||
def get_node():
|
||||
try:
|
||||
data = comm.get_and_trans(0)
|
||||
except DnfsComm.GetFailed:
|
||||
return "获取数据失败", 500
|
||||
for item in data:
|
||||
if item['title'] == 'Users':
|
||||
node_id = item['id']
|
||||
node_content = item['content']
|
||||
return node_id, node_content
|
||||
return None
|
||||
|
||||
|
||||
@user_bp.after_request
|
||||
def add_cors_headers(response):
|
||||
response.headers['Access-Control-Allow-Origin'] = '*' # 允许所有来源跨域访问,如需限制可替换为具体域名
|
||||
response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
|
||||
response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS'
|
||||
return response
|
||||
|
||||
|
||||
@user_bp.route('/api/mgmt/user', methods=['GET', 'POST'])
|
||||
def user():
|
||||
try:
|
||||
data = comm.get_and_trans(0)
|
||||
dnfs_tab = DnfsTable(data)
|
||||
users = dnfs_tab.query_by_title('Users')
|
||||
table = UsersTable(users.content)
|
||||
crt_dict = lambda user: {'user_id': user.id, 'username': user.name, 'role': user.privileges}
|
||||
user_list = [crt_dict(user) for user in table.arr]
|
||||
except DnfsComm.GetFailed:
|
||||
return "获取数据失败", 500
|
||||
except DnfsTable.NotFound:
|
||||
return "未找到用户表", 404
|
||||
except UsersTable.NotFound:
|
||||
return "用户不存在", 401
|
||||
except Exception as e:
|
||||
return str(e), 404
|
||||
payload = get_user_from_token()
|
||||
if not payload:
|
||||
return jsonify({"error": "未授权访问"}), 401
|
||||
|
||||
user_id = payload.get('user_id')
|
||||
role = payload.get('role')
|
||||
|
||||
return render_template('features/user.html',
|
||||
response=user_list,
|
||||
current_user_id=user_id,
|
||||
current_role=role)
|
||||
|
||||
|
||||
@user_bp.route('/add_user', methods=['POST'])
|
||||
def add_user():
|
||||
node_id, msg = get_node()
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
hashed_password = hashlib.sha256(password.encode("utf-8")).hexdigest()
|
||||
role = "normal"
|
||||
table = UsersTable(msg)
|
||||
table.add_user(username, hashed_password, role)
|
||||
table = str(table)
|
||||
comm.upd(node_id, 0, 'Users', table)
|
||||
return jsonify({"message": "用户添加成功"})
|
||||
|
||||
|
||||
@user_bp.route('/change_password', methods=['POST'])
|
||||
def change_password():
|
||||
node_id, msg = get_node()
|
||||
user_id = request.form.get('user_id')
|
||||
new_password = request.form.get('new_password')
|
||||
hashed_password = hashlib.sha256(new_password.encode("utf-8")).hexdigest()
|
||||
table = UsersTable(msg)
|
||||
table.change_password(int(user_id), hashed_password)
|
||||
table = str(table)
|
||||
comm.upd(node_id, 0, 'Users', table)
|
||||
return jsonify({"message": "密码修改成功"})
|
||||
|
||||
|
||||
@user_bp.route('/delete_user<int:user_id>', methods=['POST'])
|
||||
def delete_user(user_id):
|
||||
node_id, msg = get_node()
|
||||
table = UsersTable(msg)
|
||||
table.del_user(user_id)
|
||||
table = str(table)
|
||||
comm.upd(node_id, 0, 'Users', table)
|
||||
return jsonify({"message": "用户删除成功"})
|
||||
@@ -0,0 +1,17 @@
|
||||
from app import register_blueprints
|
||||
from flask import Flask
|
||||
from waitress import serve
|
||||
|
||||
# 初始化Flask应用
|
||||
app = Flask(__name__)
|
||||
|
||||
# 设置一个密钥
|
||||
app.secret_key = 'secret_key'
|
||||
|
||||
# 注册蓝图
|
||||
register_blueprints(app)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#app.run(host='0.0.0.0', port=8009, debug=True)
|
||||
serve(app, host="0.0.0.0", port=8009)
|
||||
@@ -0,0 +1,5 @@
|
||||
../_pmc
|
||||
../_dnfs
|
||||
flask
|
||||
PyJWT
|
||||
waitress
|
||||
Whitespace-only changes.
@@ -0,0 +1,35 @@
|
||||
.top-bar-con {
|
||||
width: 230px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.newBtn,
|
||||
.flushBtn,
|
||||
.removeBtn {
|
||||
width: 84px;
|
||||
height: 42px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.newBtn {
|
||||
background-color: #5cff007d;
|
||||
}
|
||||
|
||||
.flushBtn {
|
||||
background-color: #5757577d;
|
||||
}
|
||||
|
||||
.removeBtn {
|
||||
background-color: #ff00007d;
|
||||
color: #000000b8;
|
||||
}
|
||||
|
||||
td {
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
.newBtn,
|
||||
.flushBtn,
|
||||
.stopBtn {
|
||||
width: 84px;
|
||||
height: 42px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.newBtn {
|
||||
background-color: #5cff007d;
|
||||
}
|
||||
|
||||
.flushBtn {
|
||||
background-color: #5757577d;
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
.stopBtn {
|
||||
background-color: #ff00007d;
|
||||
}
|
||||
|
||||
.text,
|
||||
.title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#inProgressText {
|
||||
font-size: 20px;
|
||||
margin-top: 5px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* modal window style */
|
||||
.modal-con {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(68, 94, 65, 0.77);
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.modal-con .window {
|
||||
width: 200px;
|
||||
height: 120px;
|
||||
background-color: #5cff007d;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.modal-con .window .top-bar {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.modal-con .window .top-bar .close-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-con .window .create-btn,
|
||||
.modal-con .window .input .input-area {
|
||||
margin-top: 5px;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
.top-bar-con {
|
||||
width: 230px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
td {
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.addUserBtn,
|
||||
.flushBtn,
|
||||
.changePasswdBtn,
|
||||
.deleteUserBtn {
|
||||
width: 84px;
|
||||
height: 42px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.addUserBtn {
|
||||
background-color: #5cff007d;
|
||||
}
|
||||
|
||||
.flushBtn {
|
||||
background-color: #5757577d;
|
||||
}
|
||||
|
||||
.changePasswdBtn {
|
||||
background-color: #e6db407d;
|
||||
color: #fb0202b0;
|
||||
}
|
||||
|
||||
.deleteUserBtn {
|
||||
background-color: #ff00007d;
|
||||
color: #000000b8;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.main {
|
||||
background-color: rgb(233, 233, 233);
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.main .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.main .name-con,
|
||||
.main .passwd-con {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.main .name-con {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.main .passwd-con {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.main .name-con .user-name-text,
|
||||
.main .passwd-con .user-passwd-text {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.main .login-con {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
:root {
|
||||
--listMargin: 15%;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.top-con {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.top-con >*{
|
||||
margin-left: 25px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.top-con div button {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
width: 150px;
|
||||
height: 100%;
|
||||
background-color: #FFFFCC;
|
||||
padding: 0px 10px 0px 10px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.feature-list .item {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #0066cc;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-top: var(--listMargin);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pages {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="icon" href="data:;base64,=">
|
||||
</head>
|
||||
<body>
|
||||
log
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="icon" href="data:;base64,=">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='features/module.css') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="top-bar-con">
|
||||
<button class="newBtn">新建</button>
|
||||
<button class="flushBtn">刷新</button>
|
||||
</div>
|
||||
<table border="1">
|
||||
<thead>
|
||||
<td>模块名</td>
|
||||
<td>操作</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in modules %}
|
||||
<tr>
|
||||
<td>{{ item }}</td>
|
||||
<td>
|
||||
<button class="removeBtn" onclick="alert('暂未完成')">移除</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,199 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="icon" href="data:;base64,=">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='features/process.css') }}">
|
||||
<!--
|
||||
<script>
|
||||
window.onload = function init() {
|
||||
const initReq = new XMLHttpRequest()
|
||||
const url = "http://qsont.xyz:8011/get-process"
|
||||
|
||||
initReq.open("get", url, true)
|
||||
initReq.send()
|
||||
addInProgessText()
|
||||
|
||||
initReq.onload = () => {
|
||||
if (initReq.status == 200 && initReq.readyState == 4) {
|
||||
const res = JSON.parse(initReq.responseText)
|
||||
let tBody = document.getElementById("tBody")
|
||||
|
||||
removeInProgessText()
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const pid = res[i].pid
|
||||
const status = res[i].status
|
||||
const cmd = res[i].cmd
|
||||
let trEle = document.createElement("tr")
|
||||
trEle.innerHTML = `
|
||||
<td class="text">${pid}</td>
|
||||
<td class="text">${status}</td>
|
||||
<td class="text">${cmd}</td>
|
||||
<td><button class="stopBtn" onclick="stopBtn('${pid}')">stop</button></td>
|
||||
`
|
||||
tBody.append(trEle)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (initReq.status == 500) {
|
||||
alert("服务端错误")
|
||||
return
|
||||
} else {
|
||||
alert(`意外错误 (${initReq.status})`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
initReq.onerror = () => {
|
||||
alert(`无法获取进程列表 (${initReq.status})`)
|
||||
}
|
||||
|
||||
function addInProgessText() {
|
||||
let inProgressText = document.getElementById("inProgressText")
|
||||
const dotAmount = 3
|
||||
let currDotAmount = 0
|
||||
let dot = ""
|
||||
setInterval(() => {
|
||||
dot = ""
|
||||
for (let i = 0; i < currDotAmount; i++) {
|
||||
dot = dot + "."
|
||||
}
|
||||
inProgressText.innerText = `Loading${dot}`
|
||||
if (currDotAmount >= dotAmount) {
|
||||
dot = ""
|
||||
currDotAmount = 0
|
||||
} else {
|
||||
currDotAmount++
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function removeInProgessText() {
|
||||
let inProgressText = document.getElementById("inProgressText")
|
||||
inProgressText.remove()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- stop, new flush -->
|
||||
<button class="newBtn" onclick="newBtn()">new</button>
|
||||
<button class="flushBtn" onclick="flushBtn()">flush</button>
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="title">PID</th>
|
||||
<th class="title">状态</th>
|
||||
<th class="title">命令</th>
|
||||
<th class="title">操作</th>
|
||||
</tr>
|
||||
{% for item in response %}
|
||||
<tr>
|
||||
<td>{{ item.pid }}</td>
|
||||
<td>{{ item.status }}</td>
|
||||
<td>{{ item.order }}</td>
|
||||
<td><button class="stopBtn" onclick="stopBtn('{{ item.pid }}')">stop</button></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</thead>
|
||||
<tbody id="tBody"></tbody>
|
||||
</table>
|
||||
<div id="inProgressText"></div>
|
||||
|
||||
<div class="modal-con" id="modal-con">
|
||||
<div class="window">
|
||||
<div class="top-bar">
|
||||
<div class="close-btn" onclick="closeModal()">X</div>
|
||||
</div>
|
||||
|
||||
<div class="input">
|
||||
<div class="prompt">输入模块名:</div>
|
||||
<input id="input-area" class="input-area" type="text">
|
||||
</div>
|
||||
<button class="create-btn" onclick="createProcess()">创建</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function newBtn() {
|
||||
const modal = document.getElementById("modal-con")
|
||||
modal.style.display = "flex"
|
||||
}
|
||||
|
||||
function createProcess() {
|
||||
const moduleName = document.getElementById("input-area").value
|
||||
const createReq = new XMLHttpRequest()
|
||||
const url = `{{ url_for('process.create_process', name=0) }}`.replace('0', moduleName);
|
||||
|
||||
createReq.open("post", url, true)
|
||||
createReq.send()
|
||||
|
||||
createReq.onload = () => {
|
||||
if (createReq.status == 200 && createReq.readyState == 4) {
|
||||
const res = JSON.parse(createReq.responseText)
|
||||
if (res.status == "success") {
|
||||
flushBtn()
|
||||
return
|
||||
}
|
||||
alert(`意外错误 (${createReq.status})`)
|
||||
return
|
||||
}
|
||||
|
||||
if (createReq.status == 500) {
|
||||
alert("服务端错误")
|
||||
return
|
||||
} else {
|
||||
alert(`意外错误 (${createReq.status})`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function flushBtn() {
|
||||
window.location = "{{ url_for('process.process') }}"
|
||||
}
|
||||
|
||||
function stopBtn(pid) {
|
||||
if (!confirm("确认停止该服务?")) {
|
||||
return
|
||||
}
|
||||
const url = `{{ url_for('process.kill_process', pid=0) }}`.replace('0', pid);
|
||||
const stopReq = new XMLHttpRequest();
|
||||
|
||||
stopReq.open("POST", url, true);
|
||||
stopReq.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
stopReq.send();
|
||||
|
||||
stopReq.onload = () => {
|
||||
if (stopReq.status == 200 && stopReq.readyState == 4) {
|
||||
flushBtn()
|
||||
return
|
||||
}
|
||||
|
||||
if (stopReq.status == 500) {
|
||||
alert("服务端错误")
|
||||
return
|
||||
} else {
|
||||
alert(`意外错误 (${stopReq.status})`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
const modal = document.getElementById("modal-con")
|
||||
const input = document.getElementById("input-area")
|
||||
|
||||
input.value = ""
|
||||
modal.style.display = "none"
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,162 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="icon" href="data:;base64,=">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='features/user.css') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="top-bar-con">
|
||||
<button class="addUserBtn" onclick="addUser()">添加用户</button>
|
||||
<button class="flushBtn" onclick="flush()">刷新</button>
|
||||
</div>
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
<td>用户ID</td>
|
||||
<td>用户名</td>
|
||||
<td>用户角色</td>
|
||||
<td>操作</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in response %}
|
||||
<tr>
|
||||
<td>{{ item.user_id}}</td>
|
||||
<td>{{ item.username }}</td>
|
||||
<td>{{ item.role }}</td>
|
||||
<td>
|
||||
{% if current_role == "admin" %}
|
||||
<button class="changePasswdBtn" onclick="changePasswd('{{ item.user_id }}')">修改密码</button>
|
||||
{% if item.username != "admin" %}
|
||||
<button class="deleteUserBtn" onclick="deleteUser('{{ item.user_id }}')">删除用户</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if current_role != "admin" and current_user_id == item.user_id %}
|
||||
<button class="changePasswdBtn" onclick="changePasswd('{{ item.user_id }}')">修改密码</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
function addUser() {
|
||||
const url = "{{ url_for('user.add_user') }}"
|
||||
const addReq = new XMLHttpRequest()
|
||||
|
||||
let data = new URLSearchParams()
|
||||
// get user name
|
||||
const name = prompt("请输入用户名")
|
||||
if (name === null) return
|
||||
if (name === "") {
|
||||
alert("请输入用户名")
|
||||
return
|
||||
} else {
|
||||
data.append("username", name)
|
||||
}
|
||||
// get user password
|
||||
const passwd = prompt("请输入密码")
|
||||
if (passwd === null) return
|
||||
if (passwd === "") {
|
||||
alert("请输入密码")
|
||||
return
|
||||
} else {
|
||||
data.append("password", passwd)
|
||||
}
|
||||
|
||||
addReq.open("post", url, true)
|
||||
addReq.send(data)
|
||||
|
||||
addReq.onload = () => {
|
||||
if (addReq.status == 200 && addReq.readyState == 4) {
|
||||
alert(JSON.parse(addReq.responseText).message)
|
||||
flush()
|
||||
return
|
||||
}
|
||||
|
||||
if (addReq.status == 500) {
|
||||
alert("服务端错误")
|
||||
return
|
||||
} else {
|
||||
alert(`意外错误 (${addReq.status})`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function changePasswd(id) {
|
||||
const url = "{{ url_for('user.change_password') }}"
|
||||
const changeReq = new XMLHttpRequest()
|
||||
|
||||
let data = new URLSearchParams()
|
||||
data.append("user_id", id)
|
||||
|
||||
// get user password
|
||||
const passwd = prompt("请输入新密码")
|
||||
if (passwd === null) return
|
||||
if (passwd === "") {
|
||||
alert("请输入密码")
|
||||
return
|
||||
} else {
|
||||
data.append("new_password", passwd)
|
||||
}
|
||||
|
||||
changeReq.open("post", url, true)
|
||||
changeReq.send(data)
|
||||
changeReq.onload = () => {
|
||||
if (changeReq.status == 200 && changeReq.readyState == 4) {
|
||||
alert(JSON.parse(changeReq.responseText).message)
|
||||
flush()
|
||||
return
|
||||
}
|
||||
|
||||
if (changeReq.status == 500) {
|
||||
alert("服务端错误")
|
||||
return
|
||||
} else {
|
||||
alert(`意外错误 (${changeReq.status})`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteUser(id) {
|
||||
if (!confirm("删除用户后不可恢复,确认继续?")) {
|
||||
return
|
||||
}
|
||||
const url = "{{ url_for('user.delete_user', user_id=0) }}".replace("0", id)
|
||||
const delReq = new XMLHttpRequest()
|
||||
|
||||
delReq.open("post", url, true)
|
||||
delReq.send()
|
||||
|
||||
delReq.onload = () => {
|
||||
if (delReq.status == 200 && delReq.readyState == 4) {
|
||||
alert(JSON.parse(delReq.responseText).message)
|
||||
flush()
|
||||
return
|
||||
}
|
||||
|
||||
if (delReq.status == 500) {
|
||||
alert("服务端错误")
|
||||
return
|
||||
} else {
|
||||
alert(`意外错误 (${delReq.status})`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function flush() {
|
||||
window.location = "{{ url_for('user.user') }}"
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>pmc management</title>
|
||||
<link rel="icon" href="data:;base64,=">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main">
|
||||
<div class="title">Welcome to pmc management system</div>
|
||||
<div class="name-con">
|
||||
<div class="user-name-text">Name: </div>
|
||||
<input class="user-name-input" id="user-name" type="text">
|
||||
</div>
|
||||
<div class="passwd-con">
|
||||
<div class="user-passwd-text">Password: </div>
|
||||
<input class="user-passwd-input" id="user-passwd" type="password">
|
||||
</div>
|
||||
<div class="login-con">
|
||||
<button onclick="login()">login</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function login() {
|
||||
const name = document.getElementById("user-name").value
|
||||
const passwd = document.getElementById("user-passwd").value
|
||||
const request = new XMLHttpRequest()
|
||||
const REQUEST_URL = "/api/login"
|
||||
|
||||
// if user not input any information
|
||||
if (name === "" || passwd === "") {
|
||||
alert("please input name or passwd")
|
||||
return
|
||||
}
|
||||
|
||||
// request api
|
||||
let requestData = new URLSearchParams()
|
||||
requestData.append("user-name", name)
|
||||
requestData.append("user-passwd", passwd)
|
||||
request.open("post", REQUEST_URL, true)
|
||||
request.send(requestData)
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 401) {
|
||||
alert(request.responseText)
|
||||
return
|
||||
}
|
||||
|
||||
if (request.status === 404) {
|
||||
alert(request.responseText)
|
||||
return
|
||||
}
|
||||
|
||||
if (request.status === 500) {
|
||||
alert("服务端错误")
|
||||
return
|
||||
}
|
||||
|
||||
if (request.status === 200 && request.readyState === 4) {
|
||||
const token = JSON.parse(request.responseText).token
|
||||
let date = new Date()
|
||||
date.setTime(date.getTime() + (3 * 24 * 60 * 60 * 1000))
|
||||
const UTCTime = date.toUTCString()
|
||||
document.cookie = `token=${token}; path=/; expires=${UTCTime}`
|
||||
window.location = JSON.parse(request.responseText).redirect;
|
||||
return
|
||||
} else {
|
||||
alert(`意外错误 (${request.status})`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key != "Enter") {
|
||||
return
|
||||
}
|
||||
login()
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>management page</title>
|
||||
<link rel="icon" href="data:;base64,=">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='mgmt.css') }}">
|
||||
|
||||
<script>
|
||||
window.onload = () => {
|
||||
const featureList = [
|
||||
{
|
||||
name: "进程",
|
||||
url: "{{ url_for('process.process') }}"
|
||||
},
|
||||
{
|
||||
name: "模块",
|
||||
url: "{{ url_for('module.module') }}"
|
||||
},
|
||||
// {
|
||||
// name: "日志",
|
||||
// url: "{{ url_for('log.log') }}"
|
||||
// },
|
||||
{
|
||||
name: "用户",
|
||||
url: "{{ url_for('user.user') }}"
|
||||
}
|
||||
]
|
||||
|
||||
const featureListEle = document.getElementById("feature-list")
|
||||
for (let i = 0; i < featureList.length; i++) {
|
||||
// get name and url
|
||||
const name = featureList[i].name
|
||||
const url = featureList[i].url
|
||||
|
||||
// Create a item
|
||||
let listItemEle = document.createElement("div")
|
||||
listItemEle.setAttribute("class", "item")
|
||||
listItemEle.innerText = name
|
||||
listItemEle.setAttribute("onclick", `switchPage('${url}')`)
|
||||
|
||||
// append element into feature list
|
||||
featureListEle.append(listItemEle)
|
||||
}
|
||||
|
||||
// set default page in iframe
|
||||
const DEFAULT_PAGE = "<!DOCTYPE html><h1>请在左侧选择功能</h1>"
|
||||
document.getElementById("page").contentDocument.write(DEFAULT_PAGE)
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="top-con">
|
||||
<div id="current-user">当前用户: {{ username }}</div>
|
||||
<div id="current-role">当前角色: {{ role }}</div>
|
||||
<div class="button-con">
|
||||
<button onclick="logout()">退出登陆</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="feature-list" id="feature-list"></div>
|
||||
<div class="pages">
|
||||
<iframe id="page" src="" frameborder="1" width="100%" height="100%"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// change the iframe's url
|
||||
function switchPage(url) {
|
||||
const page = document.getElementById("page")
|
||||
page.setAttribute("src", url)
|
||||
}
|
||||
|
||||
function logout() {
|
||||
if (!confirm("确认退出?")) {
|
||||
return
|
||||
}
|
||||
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
|
||||
window.location = "/"
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
.git/
|
||||
.vscode/
|
||||
.idea/
|
||||
.cache/
|
||||
|
||||
#
|
||||
init.csv
|
||||
|
||||
|
||||
# cangjie
|
||||
build/
|
||||
target/
|
||||
output/
|
||||
module-resolve.json
|
||||
module-lock.json
|
||||
cjpm.lock
|
||||
.cjpm-history
|
||||
*.macrocall
|
||||
*.gcda
|
||||
*.gcno
|
||||
|
||||
|
||||
# 其他
|
||||
log
|
||||
report
|
||||
tmp
|
||||
|
||||
# ide
|
||||
.arts
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) [year] [fullname]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
## 为什么要做一个这样的项目
|
||||
|
||||
当时想做一个树莓派机器人,是准备用python来写控制程序,可是当时python的多线程能力较弱,
|
||||
|
||||
所以想到使用子系统,可是我没有VPN,无法安装ROS,所以就自己写了一个进程托管程序
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 启动列表
|
||||
```bash
|
||||
# 创建启动列表,每一行是一个进程
|
||||
touch init.csv
|
||||
```
|
||||
|
||||
### 创建进程
|
||||
```bash
|
||||
curl -X POST http://localhost:5200/api/v2/create -d '["ls"]
|
||||
```
|
||||
|
||||
### 进程列表
|
||||
```bash
|
||||
curl http://localhost:5200/api/v2/list
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
#source ./.env/bin/activate
|
||||
source /cangjie/envsetup.sh
|
||||
exec cjpm run
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
cjc-version = "1.0.0"
|
||||
name = "phs"
|
||||
description = "nothing here"
|
||||
version = "1.0.0"
|
||||
target-dir = ""
|
||||
src-dir = ""
|
||||
output-type = "executable"
|
||||
compile-option = ""
|
||||
override-compile-option = ""
|
||||
link-option = ""
|
||||
package-configuration = {}
|
||||
|
||||
[dependencies]
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
[target.x86_64-unknown-linux-gnu.bin-dependencies]
|
||||
path-option = ["/stdx/linux_x86_64_llvm/static/stdx"]
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
[target.aarch64-unknown-linux-gnu.bin-dependencies]
|
||||
path-option = ["/stdx/linux_aarch64_llvm/static/stdx"]
|
||||
[target.x86_64-w64-mingw32] # 系统架构和 OS 信息
|
||||
[target.x86_64-w64-mingw32.bin-dependencies]
|
||||
path-option = ["C:\\stdx\\windows_x86_64_llvm\\dynamic\\stdx"] # stdx 路径根据实际情况配置
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=TxL.phs
|
||||
After=network.target
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=/root/___TxL/TestSuits/phs
|
||||
ExecStart=bash /root/___TxL/TestSuits/phs/_run.sh
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=mozart-python-host
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,312 @@
|
||||
package phs
|
||||
import std.sync.*;
|
||||
|
||||
/*
|
||||
* Status 状态
|
||||
*
|
||||
* FIXME: 在枚举中定义操作函数
|
||||
*/
|
||||
enum Stat {
|
||||
SHUT | STOP | START | RUNNING
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Finite Status Machine 有限状态机
|
||||
*
|
||||
*
|
||||
* FIXME(TritiumQin): 1. 柯里化
|
||||
*
|
||||
* FIXME(qing): 支持自定义状态
|
||||
*/
|
||||
open class Fsm {
|
||||
|
||||
|
||||
/* 状态枚举 */
|
||||
private var stat: Stat = STOP
|
||||
|
||||
/* 线程安全 */
|
||||
private let mtx: Mutex = Mutex()
|
||||
|
||||
/*
|
||||
* 条件变量
|
||||
*
|
||||
* FIXME: 我不知道这么写意义何在
|
||||
*/
|
||||
private let condition: Condition = synchronized(mtx) {
|
||||
mtx.condition()
|
||||
}
|
||||
|
||||
/* 辅助条件变量的布尔标志 */
|
||||
private var flag: Bool = true
|
||||
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
|
||||
/*
|
||||
* 阻塞等待状态改变,可以传入一个谓词,以确定什么时候终止
|
||||
*
|
||||
* 在该回调函数中返回true,则等待不会停止
|
||||
*/
|
||||
public func wait(keep!: (Stat) -> Bool = {_ => false}) {
|
||||
synchronized(mtx) {
|
||||
flag = true
|
||||
while(flag || keep(stat)) {
|
||||
condition.wait()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
|
||||
/*
|
||||
* 对状态机进行某种操作,输入一个谓词。
|
||||
*
|
||||
* 会激活堵塞线程
|
||||
*/
|
||||
public func doc(p: (Stat) -> Stat): Stat {
|
||||
synchronized(mtx) {
|
||||
stat = p(stat)
|
||||
flag= false // 试图唤醒阻塞的线程
|
||||
condition.notifyAll()
|
||||
return stat
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 对状态机进行某种操作,输入一个谓词。
|
||||
*
|
||||
* 不会激活堵塞线程
|
||||
*/
|
||||
public func dop(p: (Stat) -> Stat): Stat {
|
||||
synchronized(mtx) {
|
||||
stat = p(stat)
|
||||
return stat
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Controllable Thread 可控制线程
|
||||
*
|
||||
* FIXME(花间岛):
|
||||
* 1. 使用async语法糖
|
||||
* 2. 封装iocp
|
||||
*/
|
||||
open class Th {
|
||||
|
||||
/* 持有线程类型 */
|
||||
private var fut: Option<Future<Int64>> = None
|
||||
|
||||
/* 持有状态机类型 */
|
||||
private let fsm: Fsm = Fsm()
|
||||
|
||||
/*----------------------------------------------*/
|
||||
|
||||
/* 持有静止事件回调 */
|
||||
private var StopEvent: Option<() -> Unit> = None
|
||||
|
||||
/* 持有唤醒事件回调 */
|
||||
private var WakeEvent: Option<() -> Unit> = None
|
||||
|
||||
/* 持有循环事件回调 */
|
||||
private var LoopEvent: Option<() -> Unit> = None
|
||||
|
||||
/* 持有清理事件回调 */
|
||||
private var ClearEvent: Option<() -> Unit> = None
|
||||
|
||||
/* 持有线程主函数回调 */
|
||||
private var Main: Option<() -> Int64> = None // 线程主函数
|
||||
|
||||
/*----------------------------------------------*/
|
||||
|
||||
/* 使用谓词检查线程状态 */
|
||||
public func chkp(): Stat {
|
||||
return fsm.dop({ stat => return stat })
|
||||
}
|
||||
|
||||
/*----------------------------------------------*/
|
||||
|
||||
/* 进入关闭状态 */
|
||||
public func shut() {
|
||||
fsm.doc({ _ => return SHUT })
|
||||
}
|
||||
|
||||
/* 进入静止态 */
|
||||
public func stop() {
|
||||
fsm.doc({ _ => return STOP })
|
||||
}
|
||||
|
||||
/* 进入启动状态 */
|
||||
public func start() {
|
||||
fsm.doc({ _ => return START })
|
||||
}
|
||||
|
||||
/* 进入运行态 */
|
||||
public func run() {
|
||||
fsm.doc({ _ => return RUNNING })
|
||||
}
|
||||
|
||||
/*----------------------------------------------*/
|
||||
|
||||
/* 状态机是否处于静止态 */
|
||||
public func isStop() {
|
||||
return match( chkp() ) {
|
||||
case STOP => true
|
||||
case _ => false
|
||||
}}
|
||||
|
||||
/* 状态机是否处于关闭态 */
|
||||
public func isShut() {
|
||||
return match( chkp() ) {
|
||||
case SHUT => true
|
||||
case _ => false
|
||||
}}
|
||||
|
||||
/* 状态机是否处于开始状态 */
|
||||
public func isStart() {
|
||||
return match( chkp() ) {
|
||||
case START => true
|
||||
case _ => false
|
||||
}}
|
||||
|
||||
/* 状态机是否处于运行态 */
|
||||
public func isRunning() {
|
||||
return match( chkp() ) {
|
||||
case RUNNING => true
|
||||
case _ => false
|
||||
}}
|
||||
|
||||
/*----------------------------------------------*/
|
||||
|
||||
/* 扮演函数,即设置线程事件 */
|
||||
public func Act(
|
||||
|
||||
/* 静止事件 */
|
||||
stop!: Option<() -> Unit> = None,
|
||||
|
||||
/* 唤醒事件 */
|
||||
wake!: Option<() -> Unit> = None,
|
||||
|
||||
/* 循环事件 */
|
||||
loop!: Option<() -> Unit> = None,
|
||||
|
||||
/* 清理事件 */
|
||||
clear!:Option<() -> Unit> = None,
|
||||
|
||||
|
||||
/* 线程主函数 */
|
||||
mainloop!: Option<() -> Int64>= None
|
||||
|
||||
)
|
||||
|
||||
{
|
||||
|
||||
/* 只有静止态才能进行设置 */
|
||||
if (!isStop()) { return; }
|
||||
|
||||
/* ------------------------------------------------- */
|
||||
|
||||
/* 设置静止事件,确保退出了静止态 */
|
||||
StopEvent = stop ?? {=> fsm.wait(keep: {stat =>
|
||||
match(stat) {
|
||||
case STOP => true /* 保持等待 */
|
||||
case _ => false
|
||||
}
|
||||
})}
|
||||
|
||||
/* 唤醒事件,默认直接进入运行态 */
|
||||
WakeEvent = wake ?? {=> this.run()}
|
||||
|
||||
/* 循环事件,默认进行延时 */
|
||||
LoopEvent = loop ?? {=> sleep(100 * Duration.millisecond)}
|
||||
|
||||
/* 清理事件,默认什么都不做 */
|
||||
ClearEvent= clear?? {=> ;}
|
||||
|
||||
/* ------------------------------------------------- */
|
||||
|
||||
/* 线程主函数 */
|
||||
Main = mainloop ?? {=>
|
||||
|
||||
/* 回字形循环 外层 */
|
||||
while (!isShut()) {
|
||||
this.inner()
|
||||
}
|
||||
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/* ------------------------------------------------- */
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ------------- 回字形循环 内层 -------------- */
|
||||
|
||||
private func inner() {
|
||||
|
||||
/* 静止状态 */
|
||||
while(isStop()) { StopEvent.getOrThrow()() }
|
||||
|
||||
/* 唤醒事件 */
|
||||
if (isStart()) { WakeEvent.getOrThrow()() }
|
||||
|
||||
/* 运行状态 */
|
||||
while(isRunning()) { LoopEvent.getOrThrow()() }
|
||||
|
||||
/* 清理事件 */
|
||||
ClearEvent.getOrThrow()()
|
||||
|
||||
}
|
||||
|
||||
/* --------------------------------------------- */
|
||||
|
||||
/*
|
||||
* 创建资源,可控线程苏醒
|
||||
*
|
||||
* 如果在调用中等待线程启动,可能会造成死锁
|
||||
* 想了想觉得也没有这个必要
|
||||
*/
|
||||
public func wake() {
|
||||
|
||||
|
||||
/* 存在微小的冲突可能,所以我不让它进入静止态 */
|
||||
this.start()
|
||||
|
||||
|
||||
/* 创建卵 */
|
||||
fut = Some<Future<Int64>>(spawn {
|
||||
Main.getOrThrow()()
|
||||
})
|
||||
|
||||
|
||||
/* 等启动
|
||||
*
|
||||
* 20251020(qing): 如果线程启动太快,该等待可能会导致死锁
|
||||
*/
|
||||
//fsm.wait(keep: {stat => match(stat) {
|
||||
// case START => true
|
||||
// case _ => false
|
||||
//}})
|
||||
}
|
||||
|
||||
/* 等待线程结束 */
|
||||
public func wait() {
|
||||
let f = fut.getOrThrow()
|
||||
return f.get()
|
||||
}
|
||||
|
||||
/*
|
||||
* 关闭线程,然后等待线程结束
|
||||
*/
|
||||
public func shut_and_wait() {
|
||||
shut()
|
||||
wait()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package phs
|
||||
|
||||
import std.io.*;
|
||||
import std.fs.*;
|
||||
import std.collection.*;
|
||||
import std.sync.*;
|
||||
import std.process.*;
|
||||
import stdx.net.http.*;
|
||||
import stdx.encoding.json.*;
|
||||
|
||||
const ADDR = "127.0.0.1" /* 监听IP地址 */
|
||||
const PORT = 5200u16 /* 端口 UInt16类型 */
|
||||
|
||||
|
||||
class Ps {
|
||||
private let ps = LinkedList<SubProcess>()
|
||||
private let mtx = Mutex()
|
||||
|
||||
/* 创建进程 */
|
||||
public func create(arr: Array<String>) {
|
||||
let path = arr[0]
|
||||
let args = arr[1..]
|
||||
let subp: SubProcess = launch(path, args,
|
||||
//stdIn: ProcessRedirect.Discard, /* WINDOWS中不支持输入重定向 */
|
||||
//stdOut: ProcessRedirect.Discard, /* 加了这行之后就在控制台看不到输出了 */
|
||||
//stdErr: ProcessRedirect.Discard
|
||||
)
|
||||
this.clear() /* 清理进程 */
|
||||
synchronized(mtx) {
|
||||
ps.addLast(subp)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* 返回进程列表 */
|
||||
public func list() {
|
||||
var msg = ""
|
||||
println('1')
|
||||
this.clear() /* 清理进程,有锁 */
|
||||
println('2')
|
||||
synchronized(mtx) {
|
||||
println('3')
|
||||
for (p in ps) {
|
||||
if (msg != "") { msg += "\n"; }
|
||||
msg += '${p.pid.toString()}\t'
|
||||
msg += '${p.command} '
|
||||
//for (i in p.arguments) { /* 加上这个会失败 */
|
||||
// msg += '${i} '
|
||||
//}
|
||||
/* 启动时间 */
|
||||
msg += '\t${p.userTime.toString()}'
|
||||
}
|
||||
println('4')
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* 杀死已经存在的进程 */
|
||||
public func kill(pid: Int64) {
|
||||
this.clear() /* 清理 */
|
||||
synchronized(mtx) {
|
||||
for (p in ps) {
|
||||
if (p.pid == pid) {
|
||||
p.terminate(force: true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 进程自启动
|
||||
*
|
||||
* FIXME: 支持注释
|
||||
*/
|
||||
public func autoRun(csvpath: String) {
|
||||
let file = File(csvpath, Read)
|
||||
let bytes = readToEnd(file)
|
||||
let ctt = String.fromUtf8(bytes)
|
||||
let cmds = ctt.split("\n", removeEmpty: true)
|
||||
for (cmd in cmds) {
|
||||
try{
|
||||
let arr = cmd.split(" ", removeEmpty: true)
|
||||
this.create(arr)
|
||||
} catch (exp: Exception) {
|
||||
;
|
||||
}
|
||||
}
|
||||
file.close()
|
||||
}
|
||||
|
||||
/* 清理已经死去的进程,默认不加锁 */
|
||||
private func clear() {
|
||||
synchronized(mtx) {
|
||||
let len = ps.size
|
||||
for (i in (len-1)..=0 : -1) {
|
||||
try {
|
||||
let p = ps.nodeAt(i).getOrThrow()
|
||||
println("start...")
|
||||
p.value.wait(timeout: 1 * Duration.millisecond) /* 进程有没有退出 */
|
||||
println("end...")
|
||||
ps.remove(p)
|
||||
}
|
||||
catch (exp: TimeoutException) { /* 如果进程没有结束,会运行到这里 */
|
||||
;
|
||||
println("skip...")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 将JSON数组转为字符串的数组
|
||||
*
|
||||
* Json对象的每个元素只能是字符串,否则会报错
|
||||
*/
|
||||
func Jarr_to_arr(arr: JsonArray): Array<String> {
|
||||
let len = arr.size() // Json数组的长度
|
||||
let ret = Array<String>(len, repeat: "")
|
||||
for (i in 0..len) {
|
||||
let option: Option<JsonValue> = arr.get(i)
|
||||
let jitem: JsonValue = option.getOrThrow()
|
||||
let jstr: JsonString = jitem.asString()
|
||||
let str: String = jstr.getValue()
|
||||
ret[i] = str
|
||||
}
|
||||
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
/* cj命令行参数不包含可执行文件名 */
|
||||
main(_: Array<String>): Int64 {
|
||||
|
||||
/* 进程组 */
|
||||
let ps = Ps()
|
||||
ps.autoRun("./init.csv")
|
||||
|
||||
|
||||
/* 创建http服务器 */
|
||||
let server: Server = ServerBuilder()
|
||||
.addr(ADDR) /* 设置地址 */
|
||||
.port(PORT) /* 设置端口 */
|
||||
.build()
|
||||
|
||||
/* 创建进程的HTTP方法 */
|
||||
server.distributor.register("/api/v2/create", { context =>
|
||||
if (context.request.method == "POST") {
|
||||
let content = StringReader(context.request.body).readToEnd() /* 阅读请求体 */
|
||||
let array = JsonValue.fromStr(content).asArray() /* 解析json格式的请求体 */
|
||||
let net_args: Array<String> = Jarr_to_arr(array) /* json数组转原生数组 */
|
||||
ps.create(net_args)
|
||||
|
||||
/* ---- 准备响应 ---- */
|
||||
let Headers = HttpHeaders()
|
||||
Headers.add("Content-Type", "text/html; charset=utf-8;")
|
||||
context.responseBuilder.setHeaders(Headers)
|
||||
context.responseBuilder.body('OK')
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* 输出进程列表的HTTP方法
|
||||
*/
|
||||
server.distributor.register("/api/v2/list", { context =>
|
||||
if (context.request.method == "GET") {
|
||||
|
||||
println('a')
|
||||
/* 准备表单字符串 */
|
||||
let ret = ps.list()
|
||||
|
||||
println('b')
|
||||
/**** 准备响应 ****/
|
||||
let Headers = HttpHeaders()
|
||||
Headers.add("Content-Type", "text/html; charset=utf-8;")
|
||||
context.responseBuilder.setHeaders(Headers)
|
||||
context.responseBuilder.body(ret)
|
||||
|
||||
println('c')
|
||||
}
|
||||
})
|
||||
|
||||
/* 杀死一个已经存在的进程 */
|
||||
server.distributor.register("/api/v2/kill", { context =>
|
||||
if (context.request.method == "POST") {
|
||||
let content = StringReader(context.request.body).readToEnd() /* 阅读请求体 */
|
||||
let target_pid = JsonValue.fromStr(content)
|
||||
.asObject()
|
||||
.get("pid")
|
||||
.getOrThrow()
|
||||
.asInt()
|
||||
.getValue()
|
||||
ps.kill(target_pid)
|
||||
|
||||
/**** 准备响应 ****/
|
||||
let Headers = HttpHeaders()
|
||||
Headers.add("Content-Type", "text/html; charset=utf-8;")
|
||||
context.responseBuilder.setHeaders(Headers)
|
||||
context.responseBuilder.body("done")
|
||||
}
|
||||
})
|
||||
|
||||
/* 服务器开始运行 */
|
||||
server.serve()
|
||||
return 0
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
|
||||
def create_process(cmd_list, url="http://localhost:5200/api/v2/create"):
|
||||
"""创建任意进程
|
||||
|
||||
Args:
|
||||
cmd_list (list): 命令和参数列表,例如 ["timeout", "/t", "1000"]
|
||||
url (str, optional): 请求URL,默认为 "http://localhost:5200/api/v2/create"
|
||||
|
||||
Returns:
|
||||
requests.Response: 响应对象
|
||||
"""
|
||||
try:
|
||||
# 发送POST请求
|
||||
response = requests.post(url, data=json.dumps(cmd_list), timeout=30)
|
||||
|
||||
# 返回响应对象供进一步处理
|
||||
return response
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"创建进程失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def create_process_with_output(cmd_list, url="http://localhost:5200/api/v2/create"):
|
||||
"""创建任意进程并打印输出
|
||||
|
||||
Args:
|
||||
cmd_list (list): 命令和参数列表,例如 ["timeout", "/t", "1000"]
|
||||
url (str, optional): 请求URL,默认为 "http://localhost:5200/api/v2/create"
|
||||
|
||||
Returns:
|
||||
dict or str: 响应内容,JSON解析后的字典或原始文本
|
||||
"""
|
||||
response = create_process(cmd_list, url)
|
||||
|
||||
if response is None:
|
||||
return None
|
||||
|
||||
# 打印响应状态码
|
||||
print(f"响应状态码: {response.status_code}")
|
||||
|
||||
# 尝试解析响应内容为JSON格式并打印
|
||||
try:
|
||||
response_json = response.json()
|
||||
print(f"响应内容: {json.dumps(response_json, indent=2, ensure_ascii=False)}")
|
||||
return response_json
|
||||
except json.JSONDecodeError:
|
||||
print(f"响应内容(非JSON格式): {response.text}")
|
||||
return response.text
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 示例用法
|
||||
# 创建一个timeout进程,延迟1000毫秒
|
||||
cmd = ["timeout", "/t", "1000"]
|
||||
|
||||
# 使用默认URL
|
||||
print("使用默认URL:")
|
||||
create_process_with_output(cmd)
|
||||
|
||||
# 示例:使用自定义URL(注释掉,可根据需要取消注释)
|
||||
# print("\n使用自定义URL:")
|
||||
# custom_url = "http://localhost:5200/api/v2/create"
|
||||
# create_process_with_output(cmd, custom_url)
|
||||
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user