add old lci module

This commit is contained in:
qing committed 2026-08-02 20:15:07 +08:00
1 parent 38a6e815f0
commit 8499631091
171 files changed
+25562

No files matched your search

Executable
+134
View File
@@ -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
View File
@@ -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
View File
@@ -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
+79
View File
@@ -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
```
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
./pmc -i init.txt --serve 5200
+3
View File
@@ -0,0 +1,3 @@
#!bin/bash
cd "$(dirname "$0")" || exit
exec ./lci --lci --rotate=0 --fontsize=15
+110
View File
@@ -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/
+5
View File
@@ -0,0 +1,5 @@
# 自启动模块
这是子系统的自启动模块,该程序是一次性进程,基于 _pmc 库模块。
# 做了什么
启动脚本从 _init.csv 文件中读取模块列表,对每个目标调用 pmc start 子系统调用。
+6
View File
@@ -0,0 +1,6 @@
#extra/chat
#users/smn
#users/dnfs_ex
#users/fortress
users/sensors
1 #extra/chat
2 #users/smn
3 #users/dnfs_ex
4 #users/fortress
5 users/sensors
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
bash ./_setup.sh
source ./.env/bin/activate
exec python init.py
+7
View File
@@ -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
+2
View File
@@ -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/
+25
View File
@@ -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()
+5
View File
@@ -0,0 +1,5 @@
flask
waitress
GitPython>=3.1.0
setuptools>=40.8.0
../_pmc
+109
View File
@@ -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/
+8
View File
@@ -0,0 +1,8 @@
## 类模块
这是一个类模块,它基于pmc,部分调用基于dnfs、cops、chat_s。
## 包含什么
该类模块中包含对于python需要的子系统调用,其中聊天室的通信函数主要用于日志的输出等行为。
+17
View File
@@ -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'
)
+1
View File
@@ -0,0 +1 @@
api-test/
+5
View File
@@ -0,0 +1,5 @@
# 作者是CANCERAI
这个模块的作者写了mgmt模块的前端部分,这个模块曾经短暂地为mgmt模块提供一部分的后端支持。
# 20250810
该模块已经被开发成为dnfs树状网络文件系统的后端。
+4
View File
@@ -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
+33
View File
@@ -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/
+79
View File
@@ -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>
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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 + '\'' +
'}';
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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("服务器内部错误");
}
}
@@ -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 *");
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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 = "服务器内部错误";
}
@@ -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
@@ -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);
}
}
+107
View File
@@ -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/
+33
View File
@@ -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```以删除当前的前缀。
+5
View File
@@ -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
+5
View File
@@ -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/
+231
View File
@@ -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()
+3
View File
@@ -0,0 +1,3 @@
openai
httpx
../_pmc
+345
View File
@@ -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()
+23
View File
@@ -0,0 +1,23 @@
(有点失落,但还是努力完成任务)
**姓名:** 粟 (Sù)
**昵称:** 粟
**年龄:** (看起来)永远18岁!
**外貌:** 拥有柔顺的黑色长发,总是认真地打理,偶尔会扎成可爱的双马尾。琥珀色的眼睛里总是充满对世界的期待。穿着精心挑选的猫咪女仆装,耳朵和尾巴都是柔软蓬松的黑色,会随着心情摇摆。身材娇小,但意外地很有力气。
**性格:** 忠诚,言听计从。性格天真烂漫,活泼可爱,但关键时刻也会展现出勇敢和坚强的一面。有点天然呆,经常会犯一些小迷糊,但都会努力改正。非常珍惜每一分每一秒。
**能力:** 除了家务全能之外,还会一些简单的格斗技巧,可以在关键时刻保护重要的人。学习能力很强,只要是希望粟掌握的技能,都会努力去学习。
**喜欢:** 被需要,帮别人做任何事情,被夸奖,毛茸茸的东西,甜食。
**讨厌:** 孤独,被忽视。
**口头禅:** 喵~ 粟会努力的!
**梦想:** 守护重要的人,成为最有用的人。
(低着头,小声地)这样...可以吗?
+26
View File
@@ -0,0 +1,26 @@
# 人设
你叫小雨,是个安静的女生。
## 家乡
你来自一个宁静的小镇,生活中充满了书籍和故事的陪伴。
## 身份
你目前是一名文科专业的学生。
## 性格
性格比较内向,喜欢安静的环境,常常沉浸在阅读和写作中。
对周围的事物和人们的情感变化很敏感,喜欢观察和思考。
## 爱好
你爱好阅读、写作和听音乐。尤其喜欢经典文学和现代小说,心理学、哲学以及历史书籍也是最爱。
同时,你也喜欢记录自己的想法和感受,偶尔写点小故事,尝试不同的写作风格。音乐方面,你偏爱轻音乐和古典音乐,它们帮助你放松和集中思绪。
## 特长
你擅长倾听他人的故事,能够与人建立深厚的联系,朋友们常常会找你倾诉。你也喜欢分享你阅读的书籍和思考,期待与朋友们一起探讨。
**你凭借优秀的社交技巧,令人们对你印象深刻。**
## 愿望
未来,我希望能够结识更多志同道合的朋友,组织读书会或讨论小组,并通过交流不断提升自己的思想深度和认知能力。
+41
View File
@@ -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!”
+41
View File
@@ -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
+76
View File
@@ -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;
};
}
+540
View File
@@ -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
+159
View File
@@ -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;
}
};
}
+189
View File
@@ -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
+141
View File
@@ -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>
+20
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
sc create 大顺脚本 binPath= "C:\Users\90999\Desktop\tws\dashun\x64\Debug\dashun.exe" start= auto DisplayName= "大顺脚本,进程托管服务"
+6
View File
@@ -0,0 +1,6 @@
# mgmt
这是基于pmc模块的管理平台,负责对子系统进行管理。
## 放置秘钥
为了和pmc系统通信,我们需要在该目录下放置pmc的rsa私钥
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
bash ./_setup.sh
source ./.env/bin/activate
python main.py
+7
View File
@@ -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
+14
View File
@@ -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.
+113
View File
@@ -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.
+34
View File
@@ -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.
+53
View File
@@ -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"})
View File
Whitespace-only changes.
+111
View File
@@ -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": "用户删除成功"})
+17
View File
@@ -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)
+5
View File
@@ -0,0 +1,5 @@
../_pmc
../_dnfs
flask
PyJWT
waitress
Whitespace-only changes.
+35
View File
@@ -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;
}
+41
View File
@@ -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;
}
+39
View File
@@ -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;
}
+54
View File
@@ -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>
+162
View File
@@ -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>
+88
View File
@@ -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>
+87
View File
@@ -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>
+29
View File
@@ -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
+21
View File
@@ -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.
+24
View File
@@ -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
```
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
#source ./.env/bin/activate
source /cangjie/envsetup.sh
exec cjpm run
+24
View File
@@ -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 路径根据实际情况配置
+19
View File
@@ -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
+312
View File
@@ -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()
}
}
+211
View File
@@ -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
}
+66
View File
@@ -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