This commit is contained in:
zy 2025-11-11 17:12:53 +08:00
commit da1874cac7
281 changed files with 50721 additions and 0 deletions

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
*.js linguist-language=Java
*.css linguist-language=Java
*.html linguist-language=Java
*.ftl linguist-language=Java

60
.gitignore vendored Normal file
View File

@ -0,0 +1,60 @@
### gradle ###
.gradle
!gradle/wrapper/gradle-wrapper.jar
### STS ###
.settings/
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
*.lock
rebel.xml
### NetBeans ###
nbproject/private/
nbbuild/
dist/
nbdist/
.nb-gradle/
### maven ###
target/
*.war
*.ear
*.zip
*.tar
*.tar.gz
### logs ####
/logs/
*.log
### temp ignore ###
*.cache
*.diff
*.patch
*.tmp
*.java~
*.properties~
*.xml~
### system ignore ###
.DS_Store
Thumbs.db
Servers
.metadata
upload
gen_code
### node ###
node_modules

674
LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://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 <http://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:
{project} Copyright (C) {year} {fullname}
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
<http://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
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

32
NOTICE Normal file
View File

@ -0,0 +1,32 @@
Copyright (c) 2015-present, xuxueli.
Dependencies:
================================================================
Spring:
* LICENSE:
* http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0)
* HOMEPAGE:
* http://www.springsource.org
Netty:
* LICENSE:
* http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0)
* HOMEPAGE:
* https://github.com/netty/netty
Mybatis:
* LICENSE:
* http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0)
* HOMEPAGE:
* https://mybatis.org/mybatis-3/
SLF4J:
* LICENSE:
* http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0)
* HOMEPAGE:
* http://www.slf4j.org

829
README.md Normal file
View File

@ -0,0 +1,829 @@
<p align="center" >
<img src="https://www.xuxueli.com/doc/static/xxl-job/images/xxl-logo.jpg" width="150">
<h3 align="center">XXL-JOB</h3>
<p align="center">
XXL-JOB, a distributed task scheduling framework.
<br>
<a href="https://www.xuxueli.com/xxl-job/"><strong>-- Home Page --</strong></a>
<br>
<br>
<a href="https://github.com/xuxueli/xxl-job/actions">
<img src="https://github.com/xuxueli/xxl-job/workflows/Java%20CI/badge.svg" >
</a>
<a href="https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/">
<img src="https://maven-badges.herokuapp.com/maven-central/com.xuxueli/xxl-job/badge.svg" >
</a>
<a href="https://github.com/xuxueli/xxl-job/releases">
<img src="https://img.shields.io/github/release/xuxueli/xxl-job.svg" >
</a>
<a href="https://github.com/xuxueli/xxl-job/">
<img src="https://img.shields.io/github/stars/xuxueli/xxl-job" >
</a>
<a href="https://hub.docker.com/r/xuxueli/xxl-job-admin/">
<img src="https://img.shields.io/docker/pulls/xuxueli/xxl-job-admin" >
</a>
<a href="http://www.gnu.org/licenses/gpl-3.0.html">
<img src="https://img.shields.io/badge/license-GPLv3-blue.svg" >
</a>
<a href="https://www.xuxueli.com/page/donate.html">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat" >
</a>
</p>
</p>
## Introduction
XXL-JOB is a distributed task scheduling framework.
It's core design goal is to develop quickly and learn simple, lightweight, and easy to expand.
Now, it's already open source, and many companies use it in production environments, real "out-of-the-box".
XXL-JOB是一个分布式任务调度平台其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线开箱即用。
## Sponsor
XXL-JOB is an open source and free project, with its ongoing development made possible entirely by the support of these awesome backers.
XXL-JOB 是一个开源且免费项目,其正在进行的开发完全得益于支持者的支持。开源不易,[前往赞助项目开发](https://www.xuxueli.com/page/donate.html )
## Documentation
- [中文文档](https://www.xuxueli.com/xxl-job/)
- [English Documentation](https://www.xuxueli.com/xxl-job/en/)
## Communication
- [社区交流](https://www.xuxueli.com/page/community.html)
## Features
- 1、简单支持通过Web页面对任务进行CRUD操作操作简单一分钟上手
- 2、动态支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
- 3、调度中心HA中心式调度采用中心式设计“调度中心”自研调度组件并支持集群部署可保证调度中心HA
- 4、执行器HA分布式任务分布式执行任务"执行器"支持集群部署可保证任务执行HA
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容一旦有新执行器机器上线或者下线下次调度时将会重新分配任务
- 7、触发策略提供丰富的任务触发策略包括Cron触发、固定间隔触发、固定延时触发、API事件触发、人工触发、父子任务触发
- 8、调度过期策略调度中心错过调度时间的补偿处理策略包括忽略、立即补偿触发一次等
- 9、阻塞处理策略调度过于密集执行器来不及处理时的处理策略策略包括单机串行默认、丢弃后续调度、覆盖之前调度
- 10、任务超时控制支持自定义任务超时时间任务运行超时将会主动中断任务
- 11、任务失败重试支持自定义任务失败重试次数当任务失败时将会按照预设的失败重试次数主动进行重试其中分片任务支持分片粒度的失败重试
- 12、任务失败告警默认提供邮件方式失败告警同时预留扩展接口可方便的扩展短信、钉钉等告警方式
- 13、路由策略执行器集群部署时提供丰富的路由策略包括第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等
- 14、分片广播任务执行器集群部署时任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务;
- 15、动态分片分片广播任务以执行器为维度进行分片支持动态扩容执行器集群从而动态增加分片数量协同进行业务处理在进行大数据量业务操作时可显著提升任务处理能力和速度。
- 16、故障转移任务路由策略选择"故障转移"情况下如果执行器集群中某一台机器故障将会自动Failover切换到一台正常的执行器发送调度请求。
- 17、任务进度监控支持实时监控任务进度
- 18、Rolling实时日志支持在线查看调度结果并且支持以Rolling方式实时查看执行器输出的完整的执行日志
- 19、GLUE提供Web IDE支持在线开发任务逻辑代码动态发布实时编译生效省略部署上线的过程。支持30个版本的历史版本回溯。
- 20、脚本任务支持以GLUE模式开发和运行脚本任务包括Shell、Python、NodeJS、PHP、PowerShell等类型脚本;
- 21、命令行任务原生提供通用命令行任务HandlerBean任务"CommandJobHandler");业务方只需要提供命令行即可;
- 22、任务依赖支持配置子任务依赖当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
- 23、一致性“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
- 24、自定义任务参数支持在线配置调度任务入参即时生效
- 25、调度线程池调度系统多线程触发调度运行确保调度精确执行不被堵塞
- 26、数据加密调度中心和执行器之间的通讯进行数据加密提升调度信息安全性
- 27、邮件报警任务失败时支持邮件报警支持配置多邮件地址群发报警邮件
- 28、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
- 29、运行报表支持实时查看运行数据如任务数量、调度次数、执行器数量等以及调度报表如调度日期分布图调度成功分布图等
- 30、全异步任务调度流程全异步化设计实现如异步调度、异步运行、异步回调等有效对密集调度进行流量削峰理论上支持任意时长任务的运行
- 31、跨语言调度中心与执行器提供语言无关的 RESTful API 服务,第三方任意语言可据此对接调度中心或者实现执行器。除此之外,还提供了 “多任务模式”和“httpJobHandler”等其他跨语言方案
- 32、国际化调度中心支持国际化设置提供中文、英文两种可选语言默认为中文
- 33、容器化提供官方docker镜像并实时更新推送dockerhub进一步实现产品开箱即用
- 34、线程池隔离调度线程池进行隔离拆分慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性;
- 35、用户管理支持在线管理系统用户存在管理员、普通用户两种角色
- 36、权限控制执行器维度进行权限控制管理员拥有全量权限普通用户需要分配执行器权限后才允许相关操作
## Development
于2015年中我在github上创建XXL-JOB项目仓库并提交第一个commit随之进行系统结构设计UI选型交互设计……
于2015-11月XXL-JOB终于RELEASE了第一个大版本V1.0 随后我将之发布到OSCHINAXXL-JOB在OSCHINA上获得了@红薯的热门推荐同期分别达到了OSCHINA的“热门动弹”排行第一和git.oschina的开源软件月热度排行第一在此特别感谢红薯感谢大家的关注和支持。
于2015-12月我将XXL-JOB发表到我司内部知识库并且得到内部同事认可。
于2016-01月我司展开XXL-JOB的内部接入和定制工作在此感谢袁某和尹某两位同事的贡献同时也感谢内部其他给与关注与支持的同事。
于2017-05-13在上海举办的 "[第62期开源中国源创会](https://www.oschina.net/event/2236961)" 的 "放码过来" 环节我登台对XXL-JOB做了演讲台下五百位在场观众反响热烈[图文回顾](https://www.oschina.net/question/2686220_2242120) )。
于2017-10-22又拍云 Open Talk 联合 Spring Cloud 中国社区举办的 "[进击的微服务实战派上海站](https://opentalk.upyun.com/303.html)"我登台对XXL-JOB做了演讲现场观众反响热烈并在会后与XXL-JOB用户热烈讨论交流。
于2017-12-11XXL-JOB有幸参会《[InfoQ ArchSummit全球架构师峰会](http://bj2017.archsummit.com/)》,并被拍拍贷架构总监"杨波老师"在专题 "[微服务原理、基础架构和开源实践](http://bj2017.archsummit.com/training/2)" 中现场介绍。
于2017-12-18XXL-JOB参与"[2017年度最受欢迎中国开源软件](http://www.oschina.net/project/top_cn_2017?sort=1)"评比在当时已录入的约九千个国产开源项目中角逐最终进入了前30强。
于2018-01-15XXL-JOB参与"[2017码云最火开源项目](https://www.oschina.net/news/92438/2017-mayun-top-50)"评比在当时已录入的约六千五百个码云项目中角逐最终进去了前20强。
于2018-04-14iTechPlus在上海举办的 "[2018互联网开发者大会](http://www.itdks.com/eventlist/detail/2065)"我登台对XXL-JOB做了演讲现场观众反响热烈并在会后与XXL-JOB用户热烈讨论交流。
于2018-05-27在上海举办的 "[第75期开源中国源创会](https://www.oschina.net/event/2278742)" 的 "架构" 主题专场,我登台进行“基础架构与中间件图谱”主题演讲,台下上千位在场观众反响热烈([图文回顾](https://www.oschina.net/question/3802184_2280606) )。
于2018-12-05XXL-JOB参与"[2018年度最受欢迎中国开源软件](https://www.oschina.net/project/top_cn_2018?sort=1)"评比在当时已录入的一万多个开源项目中角逐最终排名第19名。
于2019-12-10XXL-JOB参与"[2019年度最受欢迎中国开源软件](https://www.oschina.net/project/top_cn_2019)"评比,在当时已录入的一万多个开源项目中角逐,最终排名"开发框架和基础组件类"第9名。
于2020-11-16XXL-JOB参与"[2020年度最受欢迎中国开源软件](https://www.oschina.net/project/top_cn_2020)"评比,在当时已录入的一万多个开源项目中角逐,最终排名"开发框架和基础组件类"第8名。
于2021-12-06XXL-JOB参与"[2021年度OSC中国开源项目评选](https://www.oschina.net/project/top_cn_2021) "评比,在当时已录入的一万多个开源项目中角逐,最终当选"最受欢迎项目"。
> 我司大众点评目前已接入XXL-JOB内部别名《Ferrari》Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。
据最新统计, 自2016-01-21接入至2017-12-01期间该系统已调度约100万次表现优异。新接入应用推荐使用最新版本因为经过数十个版本的更新系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升核心功能更加稳定高效。
至今XXL-JOB已接入多家公司的线上产品线接入场景如电商业务O2O业务和大数据作业等截止最新统计时间为止XXL-JOB已接入的公司包括不限于
- 1、大众点评【美团点评】
- 2、山东学而网络科技有限公司
- 3、安徽慧通互联科技有限公司
- 4、人人聚财金服
- 5、上海棠棣信息科技股份有限公司
- 6、运满满【运满满】
- 7、米其林 (中国区)【米其林】
- 8、妈妈联盟
- 9、九樱天下北京信息技术有限公司
- 10、万普拉斯科技有限公司【一加手机】
- 11、上海亿保健康管理有限公司
- 12、海尔馨厨【海尔】
- 13、河南大红包电子商务有限公司
- 14、成都顺点科技有限公司
- 15、深圳市怡亚通
- 16、深圳麦亚信科技股份有限公司
- 17、上海博莹科技信息技术有限公司
- 18、中国平安科技有限公司【中国平安】
- 19、杭州知时信息科技有限公司
- 20、博莹科技上海有限公司
- 21、成都依能股份有限责任公司
- 22、湖南高阳通联信息技术有限公司
- 23、深圳市邦德文化发展有限公司
- 24、福建阿思可网络教育有限公司
- 25、优信二手车【优信】
- 26、上海悠游堂投资发展股份有限公司【悠游堂】
- 27、北京粉笔蓝天科技有限公司
- 28、中秀科技(无锡)有限公司
- 29、武汉空心科技有限公司
- 30、北京蚂蚁风暴科技有限公司
- 31、四川互宜达科技有限公司
- 32、钱包行云北京科技有限公司
- 33、重庆欣才集团
- 34、咪咕互动娱乐有限公司【中国移动】
- 35、北京诺亦腾科技有限公司
- 36、增长引擎(北京)信息技术有限公司
- 37、北京英贝思科技有限公司
- 38、刚泰集团
- 39、深圳泰久信息系统股份有限公司
- 40、随行付支付有限公司
- 41、广州瀚农网络科技有限公司
- 42、享点科技有限公司
- 43、杭州比智科技有限公司
- 44、圳临界线网络科技有限公司
- 45、广州知识圈网络科技有限公司
- 46、国誉商业上海有限公司
- 47、海尔消费金融有限公司嗨付、够花【海尔】
- 48、广州巴图鲁信息科技有限公司
- 49、深圳市鹏海运电子数据交换有限公司
- 50、深圳市亚飞电子商务有限公司
- 51、上海趣医网络有限公司
- 52、聚金资本
- 53、北京父母邦网络科技有限公司
- 54、中山元赫软件科技有限公司
- 55、中商惠民(北京)电子商务有限公司
- 56、凯京集团
- 57、华夏票联北京科技有限公司
- 58、拍拍贷【拍拍贷】
- 59、北京尚德机构在线教育有限公司
- 60、任子行股份有限公司
- 61、北京时态电子商务有限公司
- 62、深圳卷皮网络科技有限公司
- 63、北京安博通科技股份有限公司
- 64、未来无线网
- 65、厦门瓷禧网络有限公司
- 66、北京递蓝科软件股份有限公司
- 67、郑州创海软件科技公司
- 68、北京国槐信息科技有限公司
- 69、浪潮软件集团
- 70、多立恒(北京)信息技术有限公司
- 71、广州极迅客信息科技有限公司
- 72、赫基中国集团股份有限公司
- 73、海投汇
- 74、上海润益创业孵化器管理股份有限公司
- 75、汉纳森厦门数据股份有限公司
- 76、安信信托
- 77、岚儒财富
- 78、捷道软件
- 79、湖北享七网络科技有限公司
- 80、湖南创发科技责任有限公司
- 81、深圳小安时代互联网金融服务有限公司
- 82、湖北享七网络科技有限公司
- 83、钱包行云(北京)科技有限公司
- 84、360金融【360】
- 85、易企秀
- 86、摩贝上海生物科技有限公司
- 87、广东芯智慧科技有限公司
- 88、联想集团【联想】
- 89、怪兽充电
- 90、行圆汽车
- 91、深圳店店通科技邮箱公司
- 92、京东【京东】
- 93、米庄理财
- 94、咖啡易融
- 95、梧桐诚选
- 96、恒大地产【恒大】
- 97、昆明龙慧
- 98、上海涩瑶软件
- 99、易信【网易】
- 100、铜板街
- 101、杭州云若网络科技有限公司
- 102、特百惠中国有限公司
- 103、常山众卡运力供应链管理有限公司
- 104、深圳立创电子商务有限公司
- 105、杭州智诺科技股份有限公司
- 106、北京云漾信息科技有限公司
- 107、深圳市多银科技有限公司
- 108、亲宝宝
- 109、上海博卡软件科技有限公司
- 110、智慧树在线教育平台
- 111、米族金融
- 112、北京辰森世纪
- 113、云南滇医通
- 114、广州市分领网络科技有限责任公司
- 115、浙江微能科技有限公司
- 116、上海馨飞电子商务有限公司
- 117、上海宝尊电子商务有限公司
- 118、直客通科技技术有限公司
- 119、科度科技有限公司
- 120、上海数慧系统技术有限公司
- 121、我的医药网
- 122、多粉平台
- 123、铁甲二手机
- 124、上海海新得数据技术有限公司
- 125、深圳市珍爱网信息技术有限公司【珍爱网】
- 126、小蜜蜂
- 127、吉荣数科技
- 128、上海恺域信息科技有限公司
- 129、广州荔支网络有限公司【荔枝FM】
- 130、杭州闪宝科技有限公司
- 131、北京互联新网科技发展有限公司
- 132、誉道科技
- 133、山西兆盛房地产开发有限公司
- 134、北京蓝睿通达科技有限公司
- 135、月亮小屋中国有限公司【蓝月亮】
- 136、青岛国瑞信息技术有限公司
- 137、博雅云计算北京有限公司
- 138、华泰证券香港子公司
- 139、杭州东方通信软件技术有限公司
- 140、武汉博晟安全技术股份有限公司
- 141、深圳市六度人和科技有限公司
- 142、杭州趣维科技有限公司小影
- 143、宁波单车侠之家科技有限公司【单车侠】
- 144、丁丁云康信息科技北京有限公司
- 145、云钱袋
- 146、南京中兴力维
- 147、上海矽昌通信技术有限公司
- 148、深圳萨科科技
- 149、中通服创立科技有限责任公司
- 150、深圳市对庄科技有限公司
- 151、上证所信息网络有限公司
- 152、杭州火烧云科技有限公司【婚礼纪】
- 153、天津青芒果科技有限公司【芒果头条】
- 154、长飞光纤光缆股份有限公司
- 155、世纪凯歌北京医疗科技有限公司
- 156、浙江霖梓控股有限公司
- 157、江西腾飞网络技术有限公司
- 158、安迅物流有限公司
- 159、肉联网
- 160、北京北广梯影广告传媒有限公司
- 161、上海数慧系统技术有限公司
- 162、大志天成
- 163、上海云鹊医
- 164、上海云鹊医
- 165、墨迹天气【墨迹天气】
- 166、上海逸橙信息科技有限公司
- 167、沅朋物联
- 168、杭州恒生云融网络科技有限公司
- 169、绿米联创
- 170、重庆易宠科技有限公司
- 171、安徽引航科技有限公司乐职网
- 172、上海数联医信企业发展有限公司
- 173、良彬建材
- 174、杭州求是同创网络科技有限公司
- 175、荷马国际
- 176、点雇网
- 177、深圳市华星光电技术有限公司
- 178、厦门神州鹰软件科技有限公司
- 179、深圳市招商信诺人寿保险有限公司
- 180、上海好屋网信息技术有限公司
- 181、海信集团【海信】
- 182、信凌可信息科技上海有限公司
- 183、长春天成科技发展有限公司
- 184、用友金融信息技术股份有限公司【用友】
- 185、北京咖啡易融有限公司
- 186、国投瑞银基金管理有限公司
- 187、晋松(上海)网络信息技术有限公司
- 188、深圳市随手科技有限公司【随手记】
- 189、深圳水务科技有限公司
- 190、易企秀【易企秀】
- 191、北京磁云科技
- 192、南京蜂泰互联网科技有限公司
- 193、章鱼直播
- 194、奖多多科技
- 195、天津市神州商龙科技股份有限公司
- 196、岩心科技
- 197、车码科技北京有限公司
- 198、贵阳市投资控股集团
- 199、康旗股份
- 200、龙腾出行
- 201、杭州华量软件
- 202、合肥顶岭医疗科技有限公司
- 203、重庆表达式科技有限公司
- 204、上海米道信息科技有限公司
- 205、北京益友会科技有限公司
- 206、北京融贯电子商务有限公司
- 207、中国外汇交易中心
- 208、中国外运股份有限公司
- 209、中国上海晓圈教育科技有限公司
- 210、普联软件股份有限公司
- 211、北京科蓝软件股份有限公司
- 212、江苏斯诺物联科技有限公司
- 213、北京搜狐-狐友【搜狐】
- 214、新大陆网商金融
- 215、山东神码中税信息科技有限公司
- 216、河南汇顺网络科技有限公司
- 217、北京华夏思源科技发展有限公司
- 218、上海东普信息科技有限公司
- 219、上海鸣勃网络科技有限公司
- 220、广东学苑教育发展有限公司
- 221、深圳强时科技有限公司
- 222、上海云砺信息科技有限公司
- 223、重庆愉客行网络有限公司
- 224、数云
- 225、国家电网运检部
- 226、杭州找趣
- 227、浩鲸云计算科技股份有限公司
- 228、科大讯飞【科大讯飞】
- 229、杭州行装网络科技有限公司
- 230、即有分期金融
- 231、深圳法司德信息科技有限公司
- 232、上海博复信息科技有限公司
- 233、杭州云嘉云计算有限公司
- 234、有家民宿(有家美宿)
- 235、北京赢销通软件技术有限公司
- 236、浙江聚有财金融服务外包有限公司
- 237、易族智汇(北京)科技有限公司
- 238、合肥顶岭医疗科技开发有限公司
- 239、车船宝(深圳)旭珩科技有限公司)
- 240、广州富力地产有限公司
- 241、氢课上海教育科技有限公司
- 242、武汉氪细胞网络技术有限公司
- 243、杭州有云科技有限公司
- 244、上海仙豆智能机器人有限公司
- 245、拉卡拉支付股份有限公司【拉卡拉】
- 246、虎彩印艺股份有限公司
- 247、北京数微科技有限公司
- 248、广东智瑞科技有限公司
- 249、找钢网
- 250、九机网
- 251、杭州跑跑网络科技有限公司
- 252、深圳未来云集
- 253、杭州每日给力科技有限公司
- 254、上海齐犇信息科技有限公司
- 255、滴滴出行【滴滴】
- 256、合肥云诊信息科技有限公司
- 257、云知声智能科技股份有限公司
- 258、南京坦道科技有限公司
- 259、爱乐优二手平台
- 260、猫眼电影私有化部署【猫眼电影】
- 261、美团大象私有化部署【美团大象】
- 262、作业帮教育科技北京有限公司【作业帮】
- 263、北京小年糕互联网技术有限公司
- 264、山东矩阵软件工程股份有限公司
- 265、陕西国驿软件科技有限公司
- 266、君开信息科技
- 267、村鸟网络科技有限责任公司
- 268、云南国际信托有限公司
- 269、金智教育
- 270、珠海市筑巢科技有限公司
- 271、上海百胜软件股份有限公司
- 272、深圳市科盾科技有限公司
- 273、哈啰出行【哈啰】
- 274、途虎养车【途虎】
- 275、卡思优派人力资源集团
- 276、南京观为智慧软件科技有限公司
- 277、杭州城市大脑科技有限公司
- 278、猿辅导【猿辅导】
- 279、洛阳健创网络科技有限公司
- 280、魔力耳朵
- 281、亿阳信通
- 282、上海招鲤科技有限公司
- 283、四川商旅无忧科技服务有限公司
- 284、UU跑腿
- 285、北京老虎证券【老虎证券】
- 286、悠活省吧北京网络科技有限公司
- 287、F5未来商店
- 288、深圳环阳通信息技术有限公司
- 289、遠傳電信
- 290、作业帮北京教育科技有限公司【作业帮】
- 291、成都科鸿智信科技有限公司
- 292、北京木屋时代科技有限公司
- 293、大学通哈尔滨科技有限责任公司
- 294、浙江华坤道威数据科技有限公司
- 295、吉祥航空【吉祥航空】
- 296、南京圆周网络科技有限公司
- 297、广州市洋葱omall电子商务
- 298、天津联物科技有限公司
- 299、跑哪儿科技北京有限公司
- 300、深圳市美西西餐饮有限公司(喜茶)
- 301、平安不动产有限公司【平安】
- 302、江苏中海昇物联科技有限公司
- 303、湖南牙医帮科技有限公司
- 304、重庆民航凯亚信息技术有限公司易通航
- 305、递易上海智能科技有限公司
- 306、亚朵
- 307、浙江新课堂教育股份有限公司
- 308、北京蜂创科技有限公司
- 309、德一智慧城市信息系统有限公司
- 310、北京翼点科技有限公司
- 311、湖南智数新维度信息科技有限公司
- 312、北京玖扬博文文化发展有限公司
- 313、上海宇珩信息科技有限公司
- 314、全景智联武汉科技有限公司
- 315、天津易客满国际物流有限公司
- 316、南京爱福路汽车科技有限公司
- 317、我房旅居集团
- 318、湛江亲邻科技有限公司
- 319、深圳市姜科网络有限公司
- 320、青岛日日顺物流有限公司
- 321、南京太川信息技术有限公司
- 322、美图之家科技有限公司【美图】
- 323、南京太川信息技术有限公司
- 324、众薪科技北京有限公司
- 325、武汉安安物联科技有限公司
- 326、北京智客朗道网络科技有限公司
- 327、深圳市超级猩猩健身管理管理有限公司
- 328、重庆达志科技有限公司
- 329、上海享评信息科技有限公司
- 330、薪得付信息科技
- 331、跟谁学
- 332、中道苏州旅游网络科技有限公司
- 333、广州小卫科技有限公司
- 334、上海非码网络科技有限公司
- 335、途家网网络技术北京有限公司【途家】
- 336、广州辉凡信息科技有限公司
- 337、天维尔信息科技股份有限公司
- 338、上海极豆科技有限公司
- 339、苏州触达信息技术有限公司
- 340、北京热云科技有限公司
- 341、中智企服北京科技有限公司
- 342、易联云计算杭州有限责任公司
- 343、青岛航空股份有限公司【青岛航空】
- 344、山西博睿通科技有限公司
- 345、网易杭州网络有限公司【网易】
- 346、北京果果乐学科技有限公司
- 347、百望股份有限公司
- 348、中保金服深圳科技有限公司
- 349、天津运友物流科技股份有限公司
- 350、广东创能科技股份有限公司
- 351、上海倚博信息科技有限公司
- 352、深圳百果园实业集团股份有限公司
- 353、广州细刻网络科技有限公司
- 354、武汉鸿业众创科技有限公司
- 355、金锡科技广州有限公司
- 356、易瑞国际电子商务有限公司
- 357、奇点云
- 358、中视信息科技有限公司
- 359、开源项目:datax-web
- 360、云知声智能科技股份有限公司
- 361、开源项目:bboss
- 362、成都深驾科技有限公司
- 363、FunPlus【趣加】
- 364、杭州创匠信科技有限公司
- 365、龙匠北京科技发展有限公司
- 366、广州一链通互联网科技有限公司
- 367、上海星艾网络科技有限公司
- 368、虎博网络技术(上海)有限公司
- 369、青岛优米信息技术有限公司
- 370、八维通科技有限公司
- 371、烟台合享智星数据科技有限公司
- 372、东吴证券股份有限公司
- 373、中通云仓股份有限公司【中通】
- 374、北京加菲猫科技有限公司
- 375、北京匠心演绎科技有限公司
- 376、宝贝走天下
- 377、厦门众库科技有限公司
- 378、海通证券数据中心
- 389、湖南快乐通宝小额贷款有限公司
- 380、浙江大华技术股份有限公司
- 381、杭州魔筷科技有限公司
- 382、青岛掌讯通区块链科技有限公司
- 383、新大陆金融科技
- 384、常州玺拓软件科技有限公司
- 385、北京正保网格教育科技有限公司
- 386、统一企业中国投资有限公司【统一】
- 387、微革网络科技有限公司
- 388、杭州融易算科技有限公司
- 399、青岛上啥班网络科技有限公司
- 390、京东酒世界
- 391、杭州爱博仕科技有限公司
- 392、五星金服控股有限公司
- 393、福建乐摩物联科技有限公司
- 394、百炼智能科技有限公司
- 395、山东能源数智云科技有限公司
- 396、招商局能源运输股份有限公司
- 397、三一集团【三一】
- 398、东巴文深圳健康管理有限公司
- 399、索易软件
- 400、深圳市宁远科技有限公司
- 401、熙牛医疗
- 402、南京智鹤电子科技有限公司
- 403、嘀嗒出行【嘀嗒出行】
- 404、广州虎牙信息科技有限公司【虎牙】
- 405、广州欧莱雅百库网络科技有限公司【欧莱雅】
- 406、微微科技有限公司
- 407、我爱我家房地产经纪有限公司【我爱我家】
- 408、九号发现
- 409、薪人薪事
- 410、武汉氪细胞网络技术有限公司
- 411、广州市斯凯奇商业有限公司
- 412、微淼商学院
- 413、杭州车盛科技有限公司
- 414、深兰科技上海有限公司
- 415、安徽中科美络信息技术有限公司
- 416、比亚迪汽车工业有限公司【比亚迪】
- 417、湖南小桔信息技术有限公司
- 418、安徽科大国创软件科技有限公司
- 419、克而瑞
- 420、陕西云基华海信息技术有限公司
- 421、安徽深宁科技有限公司
- 422、广东康爱多数字健康有限公司
- 423、嘉里电子商务
- 424、上海时代光华教育发展有限公司
- 425、CityDo
- 426、上海禹知信息科技有限公司
- 427、广东智瑞科技有限公司
- 428、西安爱铭网络科技有限公司
- 429、心医国际数字医疗系统(大连)有限公司
- 430、乐其电商
- 431、锐达科技
- 432、天津长城滨银汽车金融有限公司
- 433、代码网
- 434、东莞市东城乔伦软件开发工作室
- 435、浙江百应科技有限公司
- 436、上海力爱帝信息技术有限公司(Red E)
- 437、云徙科技有限公司
- 438、北京康智乐思网络科技有限公司【大姨吗APP】
- 439、安徽开元瞬视科技有限公司
- 440、立方
- 441、厦门纵行科技
- 442、乐山-菲尼克斯半导体有限公司
- 443、武汉光谷联合集团有限公司
- 444、上海金仕达软件科技有限公司
- 445、深圳易世通达科技有限公司
- 446、爱动超越人工智能科技北京有限责任公司
- 447、迪普信北京科技有限公司
- 448、掌站科技北京有限公司
- 449、深圳市华云中盛股份有限公司
- 450、上海原圈科技有限公司
- 451、广州赞赏信息科技有限公司
- 452、Amber Group
- 453、德威国际货运代理上海公司
- 454、浙江杰夫兄弟智慧科技有限公司
- 455、信也科技
- 456、开思时代科技深圳有限公司
- 457、大连槐德科技有限公司
- 458、同程生活
- 459、松果出行
- 460、企鹅杏仁集团
- 461、宁波科云信息科技有限公司
- 462、上海格蓝威驰信息科技有限公司
- 463、杭州趣淘鲸科技有限公司
- 464、湖州市数字惠民科技有限公司
- 465、乐普北京医疗器械股份有限公司
- 466、广州市晴川高新技术开发有限公司
- 467、山西缇客科技有限公司
- 468、徐州卡西穆电子商务有限公司
- 469、格创东智科技有限公司
- 470、世纪龙信息网络有限责任公司
- 471、邦道科技有限公司
- 472、河南中盟新云科技股份有限公司
- 473、横琴人寿保险有限公司
- 474、上海海隆华钟信息技术有限公司
- 475、上海久湛
- 476、上海仙豆智能机器人有限公司
- 477、广州汇尚网络科技有限公司
- 478、深圳市阿卡索资讯股份有限公司
- 479、青岛佳家康健康管理有限责任公司
- 480、蓝城兄弟
- 481、成都天府通金融服务股份有限公司
- 482、深圳云镖网络科技有限公司
- 483、上海影创科技
- 484、成都艾拉物联
- 485、北京客邻尚品网络技术有限公司
- 486、IT实战联盟
- 487、杭州尤拉夫科技有限公司
- 488、中大检测(湖南)股份有限公司
- 489、江苏电老虎工业互联网股份有限公司
- 490、上海助通信息科技有限公司
- 491、北京符节科技有限公司
- 492、杭州英祐科技有限公司
- 493、江苏电老虎工业互联网股份有限公司
- 494、深圳市点猫科技有限公司
- 495、杭州天音
- 496、深圳市二十一科技互联网有限公司
- 497、海南海口翎度科技
- 498、北京小趣智品科技有限公司
- 499、广州石竹计算机软件有限公司
- 500、深圳市惟客数据科技有限公司
- 501、中国医疗器械有限公司
- 502、上海云谦科技有限公司
- 503、上海磐农信息科技有限公司
- 504、广州领航食品有限公司
- 505、青岛掌讯通区块链科技有限公司
- 506、北京新网数码信息技术有限公司
- 507、超体信息科技(深圳)有限公司
- 508、长沙店帮手信息科技有限公司
- 509、上海助弓装饰工程有限公司
- 510、杭州寻联网络科技有限公司
- 511、成都大淘客科技有限公司
- 512、松果出行
- 513、深圳市唤梦科技有限公司
- 514、上汽集团商用车技术中心
- 515、北京中航讯科技股份有限公司
- 516、北龙中网(北京)科技有限责任公司
- 517、前海超级前台(深圳)信息技术有限公司
- 518、上海中商网络股份有限公司
- 519、上海助通信息科技有限公司
- 520、宁波聚臻智能科技有限公司
- 521、上海零动数码科技股份有限公司
- 522、浙江学海教育科技有限公司
- 523、聚学云(山东)信息技术有限公司
- 524、多氟多新材料股份有限公司
- 525、智慧眼科技股份有限公司
- 526、广东智通人才连锁股份有限公司
- 527、世纪开元智印互联科技集团股份有限公司
- 528、北京理想汽车【理想汽车】
- 529、巽逸科技(重庆)有限公司
- 530、义乌购电子商务有限公司
- 531、深圳市珂莱蒂尔服饰有限公司
- 532、江西国泰利民信息科技有限公司
- 533、广西广电大数据科技有限公司
- 534、杭州艾麦科技有限公司
- 535、广州小滴科技有限公司
- 536、佳缘科技股份有限公司
- 537、上海深擎信息科技有限公司
- 538、武商网
- 539、福建民本信息科技有限公司
- 540、杭州惠合信息科技有限公司
- 541、厦门爱立得科技有限公司
- 542、成都拟合未来科技有限公司
- 543、宁波聚臻智能科技有限公司
- 544、广东百慧科技有限公司
- 545、笨马网络
- 546、深圳市信安数字科技有限公司
- 547、深圳市思乐数据技术有限公司
- 548、四川绿源集科技有限公司
- 549、湖南云医链生物科技有限公司
- 550、杭州源诚科技有限公司
- 551、北京开课吧科技有限公司
- 552、北京多来点信息技术有限公司
- 553、JEECG BOOT低代码开发平台
- 554、苏州同元软控信息技术有限公司
- 555、江苏大泰信息技术有限公司
- 556、北京大禹汇智
- 557、北京盛哲科技有限公司
- 558、广州钛动科技有限公司
- 559、北京大禹汇智科技有限公司
- 560、湖南鼎翰文化股份有限公司
- 561、苏州安软信息科技有限公司
- 562、芒果tv
- 563、上海艺赛旗软件股份有限公司
- 564、中盈优创资讯科技有限公司
- 565、乐乎公寓
- 566、启明信息
- 567、苏州安软
- 568、南京富金的软件科技有限公司
- 569、深圳市新科聚合网络技术有限公司
- 570、你好现在(北京)科技股份有限公司
- 571、360考试宝典
- 572、北京一零科技有限公司
- 573、厦门星纵信息
- 574、Dalligent Solusi Indonesia
- 575、深圳华普物联科技有限公司
- 576、深圳行健自动化股份有限公司
- 577、深圳市富融信息科技服务有限公司
- 578、蓝鸟云
- 579、上海澎博财经资讯有限公司
- 580、北京小鸦科技有限公司
- 581、杭州盈泉云科技有限公司
- 582、惟客数据
- 583、GOSO香蜜闺秀
- 584、普乐师上海数字科技有限公司
- 585、西安市雁塔区咖北堂网络科技部
- 586、宁波聚臻智能科技有限公司
- 587、普乐师数字科技有限公司
- 588、江苏蟹联网科技有限公司
- 589、杭州未智科技有限公司
- 590、安吉智行物流有限公司
- 591、华生大家居集团有限公司
- 592、美心食品广州有限公司
- 593、货拉拉【货拉拉APP】
- 594、杭州思韬瑞科技有限公司
- 595、杭州玖融科技有限公司
- 596、北京优海网络科技有限公司
- 597、浙江大维高新技术股份有限公司
- 598、粤港澳大湾区数字经济研究院
- 599、普康杭州健康科技有限公司
- 600、华西证券股份有限公司【华西证券】
- 601、杭州海康机器人股份有限公司【海康】
- 602、河南宸邦信息技术有限公司
- 603、成都次元节点网络科技有限公司
- 604、富士康科技集团【富士康】
- 605、青岛东软载波科技股份有限公司
- 606、小菊快跑科技有限公司
- 607、视源股份
- 608、宁波聚臻智能科技有限公司
- 609、阔天科技有限公司
- 610、网宿科技有限公司
- 611、南京梵鼎信息技术有限公司
- 612、房天下【房天下】
- 613、特瓦特能源科技有限公司
- 614、拓迪智能科技有限公司
- 615、东软集团【东软】
- 616、开普云
- 617、领课网络
- 618、南京特维软件有限公司
- 619、福建易联众保睿通信息科技有限公司
- 620、浙江核心同花顺金融科技有限公司【同花顺】
- 621、浙江博观瑞思科技有限公司
- 622、北京新美互通科技有限公司
- 623、北京有生博大软件股份有限公司
- 624、时代中国
- 625、鱼泡网
- 626、一粒方糖安徽科技有限公司
- 627、北京外研在线数字科技有限公司
- 628、德电中国通信技术有限公司
- 629、杭州寻联网络科技有限公司
- 630、橙联中国有限公司
- 631、北京承启通科技有限公司
- 632、银联数据服务有限公司【银联】
- 633、上海晶确科技有限公司
- 634、亚信科技有限公司
- 635、福建新航物联网科技有限公司
- 636、上扬软件
- 637、深蓝汽车科技有限公司
- 638、南昌节点汇智科技有限公司
- 639、锐明技术
- 640、再造再生健康科技有限公司
- 641、华宝证券
- 642、卓正医疗
- 643、深圳湛信科技
- 644、陕西鑫众为软件有限公司
- 645、深圳市润农科技有限公司
- 646、庚商教育智能科技有限公司
- 647、杭州祎声科技
- 648、四川久远银海软件股份有限公司
- 649、GeeFox极狐低代码
- 650、浙江和仁科技股份有限公司
- 651、宁波聚臻智能科技有限公司
- 652、福建福昕软件开发股份有限公司【福昕】
- 653、广州中长康达信息技术有限公司
- 654、武汉趣改信息科技有限公司
- 655、北京华夏思源科技发展有限公司
- 656、宁波关关通科技有限公司
- 657、青岛吕氏餐饮有限公司
- 658、杭州乐刻网络科技有限公司
- 659、上海红瓦信息科技有限公司
- 660、陕西旅小宝信息科技有限公司
- 661、中科卓恒(大连)科技有限公司
- 662、北京华益精点生物技术有限公司
- 663、马士基中国航运有限公司【马士基】
- 664、陕西美咚网络科技有限公司
- 665、山东新北洋信息技术股份有限公司
- 666、福建中瑞文化发展集团有限公司
- 667、黑龙江省建工集团有限责任公司【黑龙江省建工】
- 668、志信能达安全科技(广州)有限公司
- 669、重庆开源共创科技有限公司
- 670、华泰人寿保险股份有限公司【华泰人寿】
- 671、成都盘古纵横集团
- 672、北京果果乐学科技有限公司
- 673、北京凌云空间科技有限公司
- 674、临工重机股份有限公司
- 675、上海热风时尚管理集团【热风】
- 676、HashKey Exchange
- 677、傲基深圳跨境商务股份有限公司
- ……
> 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。
欢迎大家的关注和使用XXL-JOB也将拥抱变化持续发展。
## Contributing
Contributions are welcome! Open a pull request to fix a bug, or open an [Issue](https://github.com/xuxueli/xxl-job/issues/) to discuss a new feature or change.
欢迎参与项目贡献比如提交PR修复一个bug或者新建 [Issue](https://github.com/xuxueli/xxl-job/issues/) 讨论新特性或者变更。
## Copyright and License
This product is open source and free, and will continue to provide free community technical support. Individual or enterprise users are free to access and use.
- Licensed under the GNU General Public License (GPL) v3.
- Copyright (c) 2015-present, xuxueli.
产品开源免费,并且将持续提供免费的社区技术支持。个人或企业内部可自由的接入和使用。如有需要可 [邮件联系](https://www.xuxueli.com/page/community.html) 作者免费获取项目授权。

File diff suppressed because it is too large Load Diff

2454
doc/XXL-JOB官方文档.md Normal file

File diff suppressed because it is too large Load Diff

BIN
doc/XXL-JOB架构图.key Normal file

Binary file not shown.

122
doc/db/tables_xxl_job.sql Normal file
View File

@ -0,0 +1,122 @@
#
# XXL-JOB
# Copyright (c) 2015-present, xuxueli.
CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_unicode_ci;
use `xxl_job`;
SET NAMES utf8mb4;
CREATE TABLE `xxl_job_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_desc` varchar(255) NOT NULL,
`add_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`author` varchar(64) DEFAULT NULL COMMENT '作者',
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
`schedule_type` varchar(50) NOT NULL DEFAULT 'NONE' COMMENT '调度类型',
`schedule_conf` varchar(128) DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型',
`misfire_strategy` varchar(50) NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略',
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
`executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
`glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
`glue_source` mediumtext COMMENT 'GLUE源代码',
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID多个逗号分隔',
`trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态0-停止1-运行',
`trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
`trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
`executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
`trigger_code` int(11) NOT NULL COMMENT '调度-结果',
`trigger_msg` text COMMENT '调度-日志',
`handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
`handle_code` int(11) NOT NULL COMMENT '执行-状态',
`handle_msg` text COMMENT '执行-日志',
`alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态0-默认、1-无需告警、2-告警成功、3-告警失败',
PRIMARY KEY (`id`),
KEY `I_trigger_time` (`trigger_time`),
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_log_report` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
`running_count` int(11) NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
`suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
`fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_logglue` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
`glue_source` mediumtext COMMENT 'GLUE源代码',
`glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
`add_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_registry` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(50) NOT NULL,
`registry_key` varchar(255) NOT NULL,
`registry_value` varchar(255) NOT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_group` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型0=自动注册、1=手动录入',
`address_list` text COMMENT '执行器地址列表,多地址逗号分隔',
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
`role` tinyint(4) NOT NULL COMMENT '角色0-普通用户、1-管理员',
`permission` varchar(255) DEFAULT NULL COMMENT '权限执行器ID列表多个逗号分割',
PRIMARY KEY (`id`),
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `xxl_job_lock` (
`lock_name` varchar(50) NOT NULL COMMENT '锁名称',
PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`, `update_time`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL, '2018-11-03 22:21:31' );
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
INSERT INTO `xxl_job_lock` ( `lock_name`) VALUES ( 'schedule_lock');
commit;

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
doc/images/gitee-gvp.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
doc/images/img_1001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
doc/images/img_1002.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
doc/images/img_6yC0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

BIN
doc/images/img_BPLG.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
doc/images/img_EB65.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
doc/images/img_Fgql.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
doc/images/img_Hr2T.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
doc/images/img_Qohm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

BIN
doc/images/img_UDSo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

BIN
doc/images/img_V3vF.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
doc/images/img_Wb2o.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
doc/images/img_Ypik.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

BIN
doc/images/img_Z9Qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
doc/images/img_ZAhX.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
doc/images/img_ZAsz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
doc/images/img_dNUJ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
doc/images/img_eYrv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

BIN
doc/images/img_hIci.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
doc/images/img_iUw0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
doc/images/img_inc8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

BIN
doc/images/img_jOAU.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
doc/images/img_jrdI.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

BIN
doc/images/img_o8HQ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

BIN
doc/images/img_tJOq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
doc/images/img_tvGI.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

BIN
doc/images/xxl-logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
doc/images/xxl-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

150
pom.xml Normal file
View File

@ -0,0 +1,150 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job</artifactId>
<version>2.4.2</version>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>A distributed task scheduling framework.</description>
<url>https://www.xuxueli.com/</url>
<modules>
<module>xxl-job-core</module>
<module>xxl-job-admin</module>
<module>xxl-job-executor-samples</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.test.skip>true</maven.test.skip>
<netty.version>4.1.115.Final</netty.version>
<gson.version>2.11.0</gson.version>
<spring.version>5.3.39</spring.version>
<spring-boot.version>2.7.18</spring-boot.version>
<mybatis-spring-boot-starter.version>2.3.2</mybatis-spring-boot-starter.version>
<mysql-connector-j.version>9.1.0</mysql-connector-j.version>
<slf4j-api.version>2.0.16</slf4j-api.version>
<junit-jupiter.version>5.11.3</junit-jupiter.version>
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
<groovy.version>4.0.24</groovy.version>
<maven-source-plugin.version>3.3.1</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.11.1</maven-javadoc-plugin.version>
<maven-gpg-plugin.version>3.2.7</maven-gpg-plugin.version>
</properties>
<build>
<plugins>
</plugins>
</build>
<licenses>
<license>
<name>GNU General Public License version 3</name>
<url>https://opensource.org/licenses/GPL-3.0</url>
</license>
</licenses>
<scm>
<tag>master</tag>
<url>https://github.com/xuxueli/xxl-job.git</url>
<connection>scm:git:https://github.com/xuxueli/xxl-job.git</connection>
<developerConnection>scm:git:git@github.com:xuxueli/xxl-job.git</developerConnection>
</scm>
<developers>
<developer>
<id>XXL</id>
<name>xuxueli</name>
<email>931591021@qq.com</email>
<url>https://github.com/xuxueli</url>
</developer>
</developers>
<profiles>
<profile>
<id>release</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>xxl-job-core</module>
</modules>
<build>
<plugins>
<!-- Source -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<doclint>none</doclint>
</configuration>
</execution>
</executions>
</plugin>
<!-- GPG -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven-gpg-plugin.version}</version>
<configuration>
<useAgent>false</useAgent>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>oss</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>oss</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
</profile>
</profiles>
</project>

11
xxl-job-admin/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM openjdk:8-jre-slim
MAINTAINER xuxueli
ENV PARAMS=""
ENV TZ=PRC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ADD target/xxl-job-admin-*.jar /app.jar
ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /app.jar $PARAMS"]

119
xxl-job-admin/pom.xml Normal file
View File

@ -0,0 +1,119 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job</artifactId>
<version>2.4.2</version>
</parent>
<artifactId>xxl-job-admin</artifactId>
<packaging>jar</packaging>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- starter-webspring-webmvc + autoconfigure + logback + yaml + tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- starter-testjunit + spring-test + mockito -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- freemarker-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- mail-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- starter-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- mybatis-startermybatis + mybatis-spring + hikaridefault -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-j.version}</version>
</dependency>
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- docker -->
<!--<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.13</version>
<configuration>
&lt;!&ndash; made of '[a-z0-9-_.]' &ndash;&gt;
<imageName>${project.artifactId}:${project.version}</imageName>
<dockerDirectory>${project.basedir}</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>-->
</plugins>
</build>
</project>

View File

@ -0,0 +1,16 @@
package com.xxl.job.admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author xuxueli 2018-10-28 00:38:13
*/
@SpringBootApplication
public class XxlJobAdminApplication {
public static void main(String[] args) {
SpringApplication.run(XxlJobAdminApplication.class, args);
}
}

View File

@ -0,0 +1,96 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.service.impl.LoginService;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
public class IndexController {
@Resource
private XxlJobService xxlJobService;
@Resource
private LoginService loginService;
@RequestMapping("/")
public String index(Model model) {
Map<String, Object> dashboardMap = xxlJobService.dashboardInfo();
model.addAllAttributes(dashboardMap);
return "index";
}
@RequestMapping("/chartInfo")
@ResponseBody
public ReturnT<Map<String, Object>> chartInfo(Date startDate, Date endDate) {
ReturnT<Map<String, Object>> chartInfo = xxlJobService.chartInfo(startDate, endDate);
return chartInfo;
}
@RequestMapping("/toLogin")
@PermissionLimit(limit=false)
public ModelAndView toLogin(HttpServletRequest request, HttpServletResponse response,ModelAndView modelAndView) {
if (loginService.ifLogin(request, response) != null) {
modelAndView.setView(new RedirectView("/",true,false));
return modelAndView;
}
return new ModelAndView("login");
}
@RequestMapping(value="login", method=RequestMethod.POST)
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){
boolean ifRem = (ifRemember!=null && ifRemember.trim().length()>0 && "on".equals(ifRemember))?true:false;
return loginService.login(request, response, userName, password, ifRem);
}
@RequestMapping(value="logout", method=RequestMethod.POST)
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> logout(HttpServletRequest request, HttpServletResponse response){
return loginService.logout(request, response);
}
@RequestMapping("/help")
public String help() {
/*if (!PermissionInterceptor.ifLogin(request)) {
return "redirect:/toLogin";
}*/
return "help";
}
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
}

View File

@ -0,0 +1,72 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.model.HandleCallbackParam;
import com.xxl.job.core.biz.model.RegistryParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.util.GsonTool;
import com.xxl.job.core.util.XxlJobRemotingUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* Created by xuxueli on 17/5/10.
*/
@Controller
@RequestMapping("/api")
public class JobApiController {
@Resource
private AdminBiz adminBiz;
/**
* api
*
* @param uri
* @param data
* @return
*/
@RequestMapping("/{uri}")
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> api(HttpServletRequest request, @PathVariable("uri") String uri, @RequestBody(required = false) String data) {
// valid
if (!"POST".equalsIgnoreCase(request.getMethod())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support.");
}
if (uri==null || uri.trim().length()==0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
}
if (XxlJobAdminConfig.getAdminConfig().getAccessToken()!=null
&& XxlJobAdminConfig.getAdminConfig().getAccessToken().trim().length()>0
&& !XxlJobAdminConfig.getAdminConfig().getAccessToken().equals(request.getHeader(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN))) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
}
// services mapping
if ("callback".equals(uri)) {
List<HandleCallbackParam> callbackParamList = GsonTool.fromJson(data, List.class, HandleCallbackParam.class);
return adminBiz.callback(callbackParamList);
} else if ("registry".equals(uri)) {
RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class);
return adminBiz.registry(registryParam);
} else if ("registryRemove".equals(uri)) {
RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class);
return adminBiz.registryRemove(registryParam);
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
}
}
}

View File

@ -0,0 +1,100 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLogGlue;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogGlueDao;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.glue.GlueTypeEnum;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
/**
* job code controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/jobcode")
public class JobCodeController {
@Resource
private XxlJobInfoDao xxlJobInfoDao;
@Resource
private XxlJobLogGlueDao xxlJobLogGlueDao;
@RequestMapping
public String index(HttpServletRequest request, Model model, int jobId) {
XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
List<XxlJobLogGlue> jobLogGlues = xxlJobLogGlueDao.findByJobId(jobId);
if (jobInfo == null) {
throw new RuntimeException(I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
}
if (GlueTypeEnum.BEAN == GlueTypeEnum.match(jobInfo.getGlueType())) {
throw new RuntimeException(I18nUtil.getString("jobinfo_glue_gluetype_unvalid"));
}
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup());
// Glue类型-字典
model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());
model.addAttribute("jobInfo", jobInfo);
model.addAttribute("jobLogGlues", jobLogGlues);
return "jobcode/jobcode.index";
}
@RequestMapping("/save")
@ResponseBody
public ReturnT<String> save(HttpServletRequest request, int id, String glueSource, String glueRemark) {
// valid
if (glueRemark==null) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobinfo_glue_remark")) );
}
if (glueRemark.length()<4 || glueRemark.length()>100) {
return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_remark_limit"));
}
XxlJobInfo existsJobInfo = xxlJobInfoDao.loadById(id);
if (existsJobInfo == null) {
return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
}
// valid permission
PermissionInterceptor.validJobGroupPermission(request, existsJobInfo.getJobGroup());
// update new code
existsJobInfo.setGlueSource(glueSource);
existsJobInfo.setGlueRemark(glueRemark);
existsJobInfo.setGlueUpdatetime(new Date());
existsJobInfo.setUpdateTime(new Date());
xxlJobInfoDao.update(existsJobInfo);
// log old code
XxlJobLogGlue xxlJobLogGlue = new XxlJobLogGlue();
xxlJobLogGlue.setJobId(existsJobInfo.getId());
xxlJobLogGlue.setGlueType(existsJobInfo.getGlueType());
xxlJobLogGlue.setGlueSource(glueSource);
xxlJobLogGlue.setGlueRemark(glueRemark);
xxlJobLogGlue.setAddTime(new Date());
xxlJobLogGlue.setUpdateTime(new Date());
xxlJobLogGlueDao.save(xxlJobLogGlue);
// remove code backup more than 30
xxlJobLogGlueDao.removeOld(existsJobInfo.getId(), 30);
return ReturnT.SUCCESS;
}
}

View File

@ -0,0 +1,204 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobRegistryDao;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.RegistryConfig;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* job group controller
* @author xuxueli 2016-10-02 20:52:56
*/
@Controller
@RequestMapping("/jobgroup")
public class JobGroupController {
@Resource
public XxlJobInfoDao xxlJobInfoDao;
@Resource
public XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobRegistryDao xxlJobRegistryDao;
@RequestMapping
@PermissionLimit(adminuser = true)
public String index(Model model) {
return "jobgroup/jobgroup.index";
}
@RequestMapping("/pageList")
@ResponseBody
@PermissionLimit(adminuser = true)
public Map<String, Object> pageList(HttpServletRequest request,
@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
String appname, String title) {
// page query
List<XxlJobGroup> list = xxlJobGroupDao.pageList(start, length, appname, title);
int list_count = xxlJobGroupDao.pageListCount(start, length, appname, title);
// package result
Map<String, Object> maps = new HashMap<String, Object>();
maps.put("recordsTotal", list_count); // 总记录数
maps.put("recordsFiltered", list_count); // 过滤后的总记录数
maps.put("data", list); // 分页列表
return maps;
}
@RequestMapping("/save")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> save(XxlJobGroup xxlJobGroup){
// valid
if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
}
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appname_length") );
}
if (xxlJobGroup.getAppname().contains(">") || xxlJobGroup.getAppname().contains("<")) {
return new ReturnT<String>(500, "AppName"+I18nUtil.getString("system_unvalid") );
}
if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
}
if (xxlJobGroup.getTitle().contains(">") || xxlJobGroup.getTitle().contains("<")) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_title")+I18nUtil.getString("system_unvalid") );
}
if (xxlJobGroup.getAddressType()!=0) {
if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
}
if (xxlJobGroup.getAddressList().contains(">") || xxlJobGroup.getAddressList().contains("<")) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList")+I18nUtil.getString("system_unvalid") );
}
String[] addresss = xxlJobGroup.getAddressList().split(",");
for (String item: addresss) {
if (item==null || item.trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
}
}
}
// process
xxlJobGroup.setUpdateTime(new Date());
int ret = xxlJobGroupDao.save(xxlJobGroup);
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
}
@RequestMapping("/update")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> update(XxlJobGroup xxlJobGroup){
// valid
if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
}
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appname_length") );
}
if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
}
if (xxlJobGroup.getAddressType() == 0) {
// 0=自动注册
List<String> registryList = findRegistryByAppName(xxlJobGroup.getAppname());
String addressListStr = null;
if (registryList!=null && !registryList.isEmpty()) {
Collections.sort(registryList);
addressListStr = "";
for (String item:registryList) {
addressListStr += item + ",";
}
addressListStr = addressListStr.substring(0, addressListStr.length()-1);
}
xxlJobGroup.setAddressList(addressListStr);
} else {
// 1=手动录入
if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
}
String[] addresss = xxlJobGroup.getAddressList().split(",");
for (String item: addresss) {
if (item==null || item.trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
}
}
}
// process
xxlJobGroup.setUpdateTime(new Date());
int ret = xxlJobGroupDao.update(xxlJobGroup);
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
}
private List<String> findRegistryByAppName(String appnameParam){
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
List<XxlJobRegistry> list = xxlJobRegistryDao.findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
if (list != null) {
for (XxlJobRegistry item: list) {
if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.get(appname);
if (registryList == null) {
registryList = new ArrayList<String>();
}
if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue());
}
appAddressMap.put(appname, registryList);
}
}
}
return appAddressMap.get(appnameParam);
}
@RequestMapping("/remove")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> remove(int id){
// valid
int count = xxlJobInfoDao.pageListCount(0, 10, id, -1, null, null, null);
if (count > 0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_0") );
}
List<XxlJobGroup> allList = xxlJobGroupDao.findAll();
if (allList.size() == 1) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_1") );
}
int ret = xxlJobGroupDao.remove(id);
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
}
@RequestMapping("/loadById")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<XxlJobGroup> loadById(int id){
XxlJobGroup jobGroup = xxlJobGroupDao.load(id);
return jobGroup!=null?new ReturnT<XxlJobGroup>(jobGroup):new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null);
}
}

View File

@ -0,0 +1,155 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobUser;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum;
import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum;
import com.xxl.job.admin.core.thread.JobScheduleHelper;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.service.XxlJobService;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.job.core.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/jobinfo")
public class JobInfoController {
private static Logger logger = LoggerFactory.getLogger(JobInfoController.class);
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobService xxlJobService;
@RequestMapping
public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "-1") int jobGroup) {
// 枚举-字典
model.addAttribute("ExecutorRouteStrategyEnum", ExecutorRouteStrategyEnum.values()); // 路由策略-列表
model.addAttribute("GlueTypeEnum", GlueTypeEnum.values()); // Glue类型-字典
model.addAttribute("ExecutorBlockStrategyEnum", ExecutorBlockStrategyEnum.values()); // 阻塞处理策略-字典
model.addAttribute("ScheduleTypeEnum", ScheduleTypeEnum.values()); // 调度类型
model.addAttribute("MisfireStrategyEnum", MisfireStrategyEnum.values()); // 调度过期策略
// 执行器列表
List<XxlJobGroup> jobGroupList_all = xxlJobGroupDao.findAll();
// filter group
List<XxlJobGroup> jobGroupList = PermissionInterceptor.filterJobGroupByRole(request, jobGroupList_all);
if (jobGroupList==null || jobGroupList.size()==0) {
throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
}
model.addAttribute("JobGroupList", jobGroupList);
model.addAttribute("jobGroup", jobGroup);
return "jobinfo/jobinfo.index";
}
@RequestMapping("/pageList")
@ResponseBody
public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
return xxlJobService.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
}
@RequestMapping("/add")
@ResponseBody
public ReturnT<String> add(HttpServletRequest request, XxlJobInfo jobInfo) {
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup());
// opt
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
return xxlJobService.add(jobInfo, loginUser);
}
@RequestMapping("/update")
@ResponseBody
public ReturnT<String> update(HttpServletRequest request, XxlJobInfo jobInfo) {
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup());
// opt
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
return xxlJobService.update(jobInfo, loginUser);
}
@RequestMapping("/remove")
@ResponseBody
public ReturnT<String> remove(int id) {
return xxlJobService.remove(id);
}
@RequestMapping("/stop")
@ResponseBody
public ReturnT<String> pause(int id) {
return xxlJobService.stop(id);
}
@RequestMapping("/start")
@ResponseBody
public ReturnT<String> start(int id) {
return xxlJobService.start(id);
}
@RequestMapping("/trigger")
@ResponseBody
public ReturnT<String> triggerJob(HttpServletRequest request, int id, String executorParam, String addressList) {
// login user
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
// trigger
return xxlJobService.trigger(loginUser, id, executorParam, addressList);
}
@RequestMapping("/nextTriggerTime")
@ResponseBody
public ReturnT<List<String>> nextTriggerTime(String scheduleType, String scheduleConf) {
XxlJobInfo paramXxlJobInfo = new XxlJobInfo();
paramXxlJobInfo.setScheduleType(scheduleType);
paramXxlJobInfo.setScheduleConf(scheduleConf);
List<String> result = new ArrayList<>();
try {
Date lastTime = new Date();
for (int i = 0; i < 5; i++) {
lastTime = JobScheduleHelper.generateNextValidTime(paramXxlJobInfo, lastTime);
if (lastTime != null) {
result.add(DateUtil.formatDateTime(lastTime));
} else {
break;
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<List<String>>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) + e.getMessage());
}
return new ReturnT<List<String>>(result);
}
}

View File

@ -0,0 +1,250 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.complete.XxlJobCompleter;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogDao;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.KillParam;
import com.xxl.job.core.biz.model.LogParam;
import com.xxl.job.core.biz.model.LogResult;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.HtmlUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* index controller
* @author xuxueli 2015-12-19 16:13:16
*/
@Controller
@RequestMapping("/joblog")
public class JobLogController {
private static Logger logger = LoggerFactory.getLogger(JobLogController.class);
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@Resource
public XxlJobInfoDao xxlJobInfoDao;
@Resource
public XxlJobLogDao xxlJobLogDao;
@RequestMapping
public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "0") Integer jobId) {
// 执行器列表
List<XxlJobGroup> jobGroupList_all = xxlJobGroupDao.findAll();
// filter group
List<XxlJobGroup> jobGroupList = PermissionInterceptor.filterJobGroupByRole(request, jobGroupList_all);
if (jobGroupList==null || jobGroupList.size()==0) {
throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
}
model.addAttribute("JobGroupList", jobGroupList);
// 任务
if (jobId > 0) {
XxlJobInfo jobInfo = xxlJobInfoDao.loadById(jobId);
if (jobInfo == null) {
throw new RuntimeException(I18nUtil.getString("jobinfo_field_id") + I18nUtil.getString("system_unvalid"));
}
model.addAttribute("jobInfo", jobInfo);
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobInfo.getJobGroup());
}
return "joblog/joblog.index";
}
@RequestMapping("/getJobsByGroup")
@ResponseBody
public ReturnT<List<XxlJobInfo>> getJobsByGroup(int jobGroup){
List<XxlJobInfo> list = xxlJobInfoDao.getJobsByGroup(jobGroup);
return new ReturnT<List<XxlJobInfo>>(list);
}
@RequestMapping("/pageList")
@ResponseBody
public Map<String, Object> pageList(HttpServletRequest request,
@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
int jobGroup, int jobId, int logStatus, String filterTime) {
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobGroup); // 仅管理员支持查询全部普通用户仅支持查询有权限的 jobGroup
// parse param
Date triggerTimeStart = null;
Date triggerTimeEnd = null;
if (filterTime!=null && filterTime.trim().length()>0) {
String[] temp = filterTime.split(" - ");
if (temp.length == 2) {
triggerTimeStart = DateUtil.parseDateTime(temp[0]);
triggerTimeEnd = DateUtil.parseDateTime(temp[1]);
}
}
// page query
List<XxlJobLog> list = xxlJobLogDao.pageList(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
int list_count = xxlJobLogDao.pageListCount(start, length, jobGroup, jobId, triggerTimeStart, triggerTimeEnd, logStatus);
// package result
Map<String, Object> maps = new HashMap<String, Object>();
maps.put("recordsTotal", list_count); // 总记录数
maps.put("recordsFiltered", list_count); // 过滤后的总记录数
maps.put("data", list); // 分页列表
return maps;
}
@RequestMapping("/logDetailPage")
public String logDetailPage(int id, Model model){
// base check
ReturnT<String> logStatue = ReturnT.SUCCESS;
XxlJobLog jobLog = xxlJobLogDao.load(id);
if (jobLog == null) {
throw new RuntimeException(I18nUtil.getString("joblog_logid_unvalid"));
}
model.addAttribute("triggerCode", jobLog.getTriggerCode());
model.addAttribute("handleCode", jobLog.getHandleCode());
model.addAttribute("logId", jobLog.getId());
return "joblog/joblog.detail";
}
@RequestMapping("/logDetailCat")
@ResponseBody
public ReturnT<LogResult> logDetailCat(long logId, int fromLineNum){
try {
// valid
XxlJobLog jobLog = xxlJobLogDao.load(logId); // todo, need to improve performance
if (jobLog == null) {
return new ReturnT<LogResult>(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_logid_unvalid"));
}
// log cat
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(jobLog.getExecutorAddress());
ReturnT<LogResult> logResult = executorBiz.log(new LogParam(jobLog.getTriggerTime().getTime(), logId, fromLineNum));
// is end
if (logResult.getContent()!=null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) {
if (jobLog.getHandleCode() > 0) {
logResult.getContent().setEnd(true);
}
}
// fix xss
if (logResult.getContent()!=null && StringUtils.hasText(logResult.getContent().getLogContent())) {
String newLogContent = logResult.getContent().getLogContent();
newLogContent = HtmlUtils.htmlEscape(newLogContent, "UTF-8");
logResult.getContent().setLogContent(newLogContent);
}
return logResult;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<LogResult>(ReturnT.FAIL_CODE, e.getMessage());
}
}
@RequestMapping("/logKill")
@ResponseBody
public ReturnT<String> logKill(int id){
// base check
XxlJobLog log = xxlJobLogDao.load(id);
XxlJobInfo jobInfo = xxlJobInfoDao.loadById(log.getJobId());
if (jobInfo==null) {
return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
}
if (ReturnT.SUCCESS_CODE != log.getTriggerCode()) {
return new ReturnT<String>(500, I18nUtil.getString("joblog_kill_log_limit"));
}
// request of kill
ReturnT<String> runResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(new KillParam(jobInfo.getId()));
} catch (Exception e) {
logger.error(e.getMessage(), e);
runResult = new ReturnT<String>(500, e.getMessage());
}
if (ReturnT.SUCCESS_CODE == runResult.getCode()) {
log.setHandleCode(ReturnT.FAIL_CODE);
log.setHandleMsg( I18nUtil.getString("joblog_kill_log_byman")+":" + (runResult.getMsg()!=null?runResult.getMsg():""));
log.setHandleTime(new Date());
XxlJobCompleter.updateHandleInfoAndFinish(log);
return new ReturnT<String>(runResult.getMsg());
} else {
return new ReturnT<String>(500, runResult.getMsg());
}
}
@RequestMapping("/clearLog")
@ResponseBody
public ReturnT<String> clearLog(HttpServletRequest request, int jobGroup, int jobId, int type){
// valid permission
PermissionInterceptor.validJobGroupPermission(request, jobGroup);
// opt
Date clearBeforeTime = null;
int clearBeforeNum = 0;
if (type == 1) {
clearBeforeTime = DateUtil.addMonths(new Date(), -1); // 清理一个月之前日志数据
} else if (type == 2) {
clearBeforeTime = DateUtil.addMonths(new Date(), -3); // 清理三个月之前日志数据
} else if (type == 3) {
clearBeforeTime = DateUtil.addMonths(new Date(), -6); // 清理六个月之前日志数据
} else if (type == 4) {
clearBeforeTime = DateUtil.addYears(new Date(), -1); // 清理一年之前日志数据
} else if (type == 5) {
clearBeforeNum = 1000; // 清理一千条以前日志数据
} else if (type == 6) {
clearBeforeNum = 10000; // 清理一万条以前日志数据
} else if (type == 7) {
clearBeforeNum = 30000; // 清理三万条以前日志数据
} else if (type == 8) {
clearBeforeNum = 100000; // 清理十万条以前日志数据
} else if (type == 9) {
clearBeforeNum = 0; // 清理所有日志数据
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("joblog_clean_type_unvalid"));
}
List<Long> logIds = null;
do {
logIds = xxlJobLogDao.findClearLogIds(jobGroup, jobId, clearBeforeTime, clearBeforeNum, 1000);
if (logIds!=null && logIds.size()>0) {
xxlJobLogDao.clearLog(logIds);
}
} while (logIds!=null && logIds.size()>0);
return ReturnT.SUCCESS;
}
}

View File

@ -0,0 +1,186 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.controller.interceptor.PermissionInterceptor;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobUser;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobUserDao;
import com.xxl.job.core.biz.model.ReturnT;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author xuxueli 2019-05-04 16:39:50
*/
@Controller
@RequestMapping("/user")
public class JobUserController {
@Resource
private XxlJobUserDao xxlJobUserDao;
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@RequestMapping
@PermissionLimit(adminuser = true)
public String index(Model model) {
// 执行器列表
List<XxlJobGroup> groupList = xxlJobGroupDao.findAll();
model.addAttribute("groupList", groupList);
return "user/user.index";
}
@RequestMapping("/pageList")
@ResponseBody
@PermissionLimit(adminuser = true)
public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
String username, int role) {
// page list
List<XxlJobUser> list = xxlJobUserDao.pageList(start, length, username, role);
int list_count = xxlJobUserDao.pageListCount(start, length, username, role);
// filter
if (list!=null && list.size()>0) {
for (XxlJobUser item: list) {
item.setPassword(null);
}
}
// package result
Map<String, Object> maps = new HashMap<String, Object>();
maps.put("recordsTotal", list_count); // 总记录数
maps.put("recordsFiltered", list_count); // 过滤后的总记录数
maps.put("data", list); // 分页列表
return maps;
}
@RequestMapping("/add")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> add(XxlJobUser xxlJobUser) {
// valid username
if (!StringUtils.hasText(xxlJobUser.getUsername())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_username") );
}
xxlJobUser.setUsername(xxlJobUser.getUsername().trim());
if (!(xxlJobUser.getUsername().length()>=4 && xxlJobUser.getUsername().length()<=20)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
}
// valid password
if (!StringUtils.hasText(xxlJobUser.getPassword())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_please_input")+I18nUtil.getString("user_password") );
}
xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
}
// md5 password
xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
// check repeat
XxlJobUser existUser = xxlJobUserDao.loadByUserName(xxlJobUser.getUsername());
if (existUser != null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("user_username_repeat") );
}
// write
xxlJobUserDao.save(xxlJobUser);
return ReturnT.SUCCESS;
}
@RequestMapping("/update")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> update(HttpServletRequest request, XxlJobUser xxlJobUser) {
// avoid opt login seft
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
if (loginUser.getUsername().equals(xxlJobUser.getUsername())) {
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit"));
}
// valid password
if (StringUtils.hasText(xxlJobUser.getPassword())) {
xxlJobUser.setPassword(xxlJobUser.getPassword().trim());
if (!(xxlJobUser.getPassword().length()>=4 && xxlJobUser.getPassword().length()<=20)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
}
// md5 password
xxlJobUser.setPassword(DigestUtils.md5DigestAsHex(xxlJobUser.getPassword().getBytes()));
} else {
xxlJobUser.setPassword(null);
}
// write
xxlJobUserDao.update(xxlJobUser);
return ReturnT.SUCCESS;
}
@RequestMapping("/remove")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> remove(HttpServletRequest request, int id) {
// avoid opt login seft
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
if (loginUser.getId() == id) {
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("user_update_loginuser_limit"));
}
xxlJobUserDao.delete(id);
return ReturnT.SUCCESS;
}
@RequestMapping("/updatePwd")
@ResponseBody
public ReturnT<String> updatePwd(HttpServletRequest request, String password, String oldPassword){
// valid
if (oldPassword==null || oldPassword.trim().length()==0){
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd"));
}
if (password==null || password.trim().length()==0){
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("system_please_input") + I18nUtil.getString("change_pwd_field_oldpwd"));
}
password = password.trim();
if (!(password.length()>=4 && password.length()<=20)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("system_lengh_limit")+"[4-20]" );
}
// md5 password
String md5OldPassword = DigestUtils.md5DigestAsHex(oldPassword.getBytes());
String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
// valid old pwd
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
XxlJobUser existUser = xxlJobUserDao.loadByUserName(loginUser.getUsername());
if (!md5OldPassword.equals(existUser.getPassword())) {
return new ReturnT<String>(ReturnT.FAIL.getCode(), I18nUtil.getString("change_pwd_field_oldpwd") + I18nUtil.getString("system_unvalid"));
}
// write new
existUser.setPassword(md5Password);
xxlJobUserDao.update(existUser);
return ReturnT.SUCCESS;
}
}

View File

@ -0,0 +1,29 @@
package com.xxl.job.admin.controller.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限限制
* @author xuxueli 2015-12-12 18:29:02
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionLimit {
/**
* 登录拦截 (默认拦截)
*/
boolean limit() default true;
/**
* 要求管理员权限
*
* @return
*/
boolean adminuser() default false;
}

View File

@ -0,0 +1,42 @@
package com.xxl.job.admin.controller.interceptor;
import com.xxl.job.admin.core.util.FtlUtil;
import com.xxl.job.admin.core.util.I18nUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
* push cookies to model as cookieMap
*
* @author xuxueli 2015-12-12 18:09:04
*/
@Component
public class CookieInterceptor implements AsyncHandlerInterceptor {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// cookie
if (modelAndView!=null && request.getCookies()!=null && request.getCookies().length>0) {
HashMap<String, Cookie> cookieMap = new HashMap<String, Cookie>();
for (Cookie ck : request.getCookies()) {
cookieMap.put(ck.getName(), ck);
}
modelAndView.addObject("cookieMap", cookieMap);
}
// static method
if (modelAndView != null) {
modelAndView.addObject("I18nUtil", FtlUtil.generateStaticModel(I18nUtil.class.getName()));
}
}
}

View File

@ -0,0 +1,119 @@
package com.xxl.job.admin.controller.interceptor;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobUser;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.service.impl.LoginService;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 权限拦截
*
* @author xuxueli 2015-12-12 18:09:04
*/
@Component
public class PermissionInterceptor implements AsyncHandlerInterceptor {
@Resource
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true; // proceed with the next interceptor
}
// if need login
boolean needLogin = true;
boolean needAdminuser = false;
HandlerMethod method = (HandlerMethod)handler;
PermissionLimit permission = method.getMethodAnnotation(PermissionLimit.class);
if (permission!=null) {
needLogin = permission.limit();
needAdminuser = permission.adminuser();
}
if (needLogin) {
XxlJobUser loginUser = loginService.ifLogin(request, response);
if (loginUser == null) {
response.setStatus(302);
response.setHeader("location", request.getContextPath()+"/toLogin");
return false;
}
if (needAdminuser && loginUser.getRole()!=1) {
throw new RuntimeException(I18nUtil.getString("system_permission_limit"));
}
request.setAttribute(LoginService.LOGIN_IDENTITY_KEY, loginUser); // set loginUser, with request
}
return true; // proceed with the next interceptor
}
// -------------------- permission tool --------------------
/**
* get loginUser
*
* @param request
* @return
*/
public static XxlJobUser getLoginUser(HttpServletRequest request){
XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY); // get loginUser, with request
return loginUser;
}
/**
* valid permission by JobGroup
*
* @param request
* @param jobGroup
*/
public static void validJobGroupPermission(HttpServletRequest request, int jobGroup) {
XxlJobUser loginUser = getLoginUser(request);
if (!loginUser.validPermission(jobGroup)) {
throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]");
}
}
/**
* filter XxlJobGroup by role
*
* @param request
* @param jobGroupList_all
* @return
*/
public static List<XxlJobGroup> filterJobGroupByRole(HttpServletRequest request, List<XxlJobGroup> jobGroupList_all){
List<XxlJobGroup> jobGroupList = new ArrayList<>();
if (jobGroupList_all!=null && jobGroupList_all.size()>0) {
XxlJobUser loginUser = PermissionInterceptor.getLoginUser(request);
if (loginUser.getRole() == 1) {
jobGroupList = jobGroupList_all;
} else {
List<String> groupIdStrs = new ArrayList<>();
if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) {
groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(","));
}
for (XxlJobGroup groupItem:jobGroupList_all) {
if (groupIdStrs.contains(String.valueOf(groupItem.getId()))) {
jobGroupList.add(groupItem);
}
}
}
}
return jobGroupList;
}
}

View File

@ -0,0 +1,28 @@
package com.xxl.job.admin.controller.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* web mvc config
*
* @author xuxueli 2018-04-02 20:48:20
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private PermissionInterceptor permissionInterceptor;
@Resource
private CookieInterceptor cookieInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
registry.addInterceptor(cookieInterceptor).addPathPatterns("/**");
}
}

View File

@ -0,0 +1,66 @@
package com.xxl.job.admin.controller.resolver;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.admin.core.util.JacksonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* common exception resolver
*
* @author xuxueli 2016-1-6 19:22:18
*/
@Component
public class WebExceptionResolver implements HandlerExceptionResolver {
private static transient Logger logger = LoggerFactory.getLogger(WebExceptionResolver.class);
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
if (!(ex instanceof XxlJobException)) {
logger.error("WebExceptionResolver:{}", ex);
}
// if json
boolean isJson = false;
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod)handler;
ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class);
if (responseBody != null) {
isJson = true;
}
}
// error result
ReturnT<String> errorResult = new ReturnT<String>(ReturnT.FAIL_CODE, ex.toString().replaceAll("\n", "<br/>"));
// response
ModelAndView mv = new ModelAndView();
if (isJson) {
try {
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JacksonUtil.writeValueAsString(errorResult));
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return mv;
} else {
mv.addObject("exceptionMsg", errorResult.getMsg());
mv.setViewName("/common/common.exception");
return mv;
}
}
}

View File

@ -0,0 +1,20 @@
package com.xxl.job.admin.core.alarm;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
/**
* @author xuxueli 2020-01-19
*/
public interface JobAlarm {
/**
* job alarm
*
* @param info
* @param jobLog
* @return
*/
public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog);
}

View File

@ -0,0 +1,65 @@
package com.xxl.job.admin.core.alarm;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Component
public class JobAlarmer implements ApplicationContextAware, InitializingBean {
private static Logger logger = LoggerFactory.getLogger(JobAlarmer.class);
private ApplicationContext applicationContext;
private List<JobAlarm> jobAlarmList;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
Map<String, JobAlarm> serviceBeanMap = applicationContext.getBeansOfType(JobAlarm.class);
if (serviceBeanMap != null && serviceBeanMap.size() > 0) {
jobAlarmList = new ArrayList<JobAlarm>(serviceBeanMap.values());
}
}
/**
* job alarm
*
* @param info
* @param jobLog
* @return
*/
public boolean alarm(XxlJobInfo info, XxlJobLog jobLog) {
boolean result = false;
if (jobAlarmList!=null && jobAlarmList.size()>0) {
result = true; // success means all-success
for (JobAlarm alarm: jobAlarmList) {
boolean resultItem = false;
try {
resultItem = alarm.doAlarm(info, jobLog);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
if (!resultItem) {
result = false;
}
}
}
return result;
}
}

View File

@ -0,0 +1,118 @@
package com.xxl.job.admin.core.alarm.impl;
import com.xxl.job.admin.core.alarm.JobAlarm;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.model.ReturnT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import javax.mail.internet.MimeMessage;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* job alarm by email
*
* @author xuxueli 2020-01-19
*/
@Component
public class EmailJobAlarm implements JobAlarm {
private static Logger logger = LoggerFactory.getLogger(EmailJobAlarm.class);
/**
* fail alarm
*
* @param jobLog
*/
@Override
public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog){
boolean alarmResult = true;
// send monitor email
if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
// alarmContent
String alarmContent = "Alarm Job LogId=" + jobLog.getId();
if (jobLog.getTriggerCode() != ReturnT.SUCCESS_CODE) {
alarmContent += "<br>TriggerMsg=<br>" + jobLog.getTriggerMsg();
}
if (jobLog.getHandleCode()>0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) {
alarmContent += "<br>HandleCode=" + jobLog.getHandleMsg();
}
// email info
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(Integer.valueOf(info.getJobGroup()));
String personal = I18nUtil.getString("admin_name_full");
String title = I18nUtil.getString("jobconf_monitor");
String content = MessageFormat.format(loadEmailJobAlarmTemplate(),
group!=null?group.getTitle():"null",
info.getId(),
info.getJobDesc(),
alarmContent);
Set<String> emailSet = new HashSet<String>(Arrays.asList(info.getAlarmEmail().split(",")));
for (String email: emailSet) {
// make mail
try {
MimeMessage mimeMessage = XxlJobAdminConfig.getAdminConfig().getMailSender().createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailFrom(), personal);
helper.setTo(email);
helper.setSubject(title);
helper.setText(content, true);
XxlJobAdminConfig.getAdminConfig().getMailSender().send(mimeMessage);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job, job fail alarm email send error, JobLogId:{}", jobLog.getId(), e);
alarmResult = false;
}
}
}
return alarmResult;
}
/**
* load email job alarm template
*
* @return
*/
private static final String loadEmailJobAlarmTemplate(){
String mailBodyTemplate = "<h5>" + I18nUtil.getString("jobconf_monitor_detail") + "</span>" +
"<table border=\"1\" cellpadding=\"3\" style=\"border-collapse:collapse; width:80%;\" >\n" +
" <thead style=\"font-weight: bold;color: #ffffff;background-color: #ff8c00;\" >" +
" <tr>\n" +
" <td width=\"20%\" >"+ I18nUtil.getString("jobinfo_field_jobgroup") +"</td>\n" +
" <td width=\"10%\" >"+ I18nUtil.getString("jobinfo_field_id") +"</td>\n" +
" <td width=\"20%\" >"+ I18nUtil.getString("jobinfo_field_jobdesc") +"</td>\n" +
" <td width=\"10%\" >"+ I18nUtil.getString("jobconf_monitor_alarm_title") +"</td>\n" +
" <td width=\"40%\" >"+ I18nUtil.getString("jobconf_monitor_alarm_content") +"</td>\n" +
" </tr>\n" +
" </thead>\n" +
" <tbody>\n" +
" <tr>\n" +
" <td>{0}</td>\n" +
" <td>{1}</td>\n" +
" <td>{2}</td>\n" +
" <td>"+ I18nUtil.getString("jobconf_monitor_alarm_type") +"</td>\n" +
" <td>{3}</td>\n" +
" </tr>\n" +
" </tbody>\n" +
"</table>";
return mailBodyTemplate;
}
}

View File

@ -0,0 +1,99 @@
package com.xxl.job.admin.core.complete;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.context.XxlJobContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.MessageFormat;
/**
* @author xuxueli 2020-10-30 20:43:10
*/
public class XxlJobCompleter {
private static Logger logger = LoggerFactory.getLogger(XxlJobCompleter.class);
/**
* common fresh handle entrance (limit only once)
*
* @param xxlJobLog
* @return
*/
public static int updateHandleInfoAndFinish(XxlJobLog xxlJobLog) {
// finish
finishJob(xxlJobLog);
// text最大64kb 避免长度过长
if (xxlJobLog.getHandleMsg().length() > 15000) {
xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg().substring(0, 15000) );
}
// fresh handle
return XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateHandleInfo(xxlJobLog);
}
/**
* do somethind to finish job
*/
private static void finishJob(XxlJobLog xxlJobLog){
// 1handle success, to trigger child job
String triggerChildMsg = null;
if (XxlJobContext.HANDLE_CODE_SUCCESS == xxlJobLog.getHandleCode()) {
XxlJobInfo xxlJobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(xxlJobLog.getJobId());
if (xxlJobInfo!=null && xxlJobInfo.getChildJobId()!=null && xxlJobInfo.getChildJobId().trim().length()>0) {
triggerChildMsg = "<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_child_run") +"<<<<<<<<<<< </span><br>";
String[] childJobIds = xxlJobInfo.getChildJobId().split(",");
for (int i = 0; i < childJobIds.length; i++) {
int childJobId = (childJobIds[i]!=null && childJobIds[i].trim().length()>0 && isNumeric(childJobIds[i]))?Integer.valueOf(childJobIds[i]):-1;
if (childJobId > 0) {
JobTriggerPoolHelper.trigger(childJobId, TriggerTypeEnum.PARENT, -1, null, null, null);
ReturnT<String> triggerChildResult = ReturnT.SUCCESS;
// add msg
triggerChildMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg1"),
(i+1),
childJobIds.length,
childJobIds[i],
(triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?I18nUtil.getString("system_success"):I18nUtil.getString("system_fail")),
triggerChildResult.getMsg());
} else {
triggerChildMsg += MessageFormat.format(I18nUtil.getString("jobconf_callback_child_msg2"),
(i+1),
childJobIds.length,
childJobIds[i]);
}
}
}
}
if (triggerChildMsg != null) {
xxlJobLog.setHandleMsg( xxlJobLog.getHandleMsg() + triggerChildMsg );
}
// 2fix_delay trigger next
// on the way
}
private static boolean isNumeric(String str){
try {
int result = Integer.valueOf(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}

View File

@ -0,0 +1,158 @@
package com.xxl.job.admin.core.conf;
import com.xxl.job.admin.core.alarm.JobAlarmer;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.dao.*;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Arrays;
/**
* xxl-job config
*
* @author xuxueli 2017-04-28
*/
@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
private static XxlJobAdminConfig adminConfig = null;
public static XxlJobAdminConfig getAdminConfig() {
return adminConfig;
}
// ---------------------- XxlJobScheduler ----------------------
private XxlJobScheduler xxlJobScheduler;
@Override
public void afterPropertiesSet() throws Exception {
adminConfig = this;
xxlJobScheduler = new XxlJobScheduler();
xxlJobScheduler.init();
}
@Override
public void destroy() throws Exception {
xxlJobScheduler.destroy();
}
// ---------------------- XxlJobScheduler ----------------------
// conf
@Value("${xxl.job.i18n}")
private String i18n;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${spring.mail.from}")
private String emailFrom;
@Value("${xxl.job.triggerpool.fast.max}")
private int triggerPoolFastMax;
@Value("${xxl.job.triggerpool.slow.max}")
private int triggerPoolSlowMax;
@Value("${xxl.job.logretentiondays}")
private int logretentiondays;
// dao, service
@Resource
private XxlJobLogDao xxlJobLogDao;
@Resource
private XxlJobInfoDao xxlJobInfoDao;
@Resource
private XxlJobRegistryDao xxlJobRegistryDao;
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobLogReportDao xxlJobLogReportDao;
@Resource
private JavaMailSender mailSender;
@Resource
private DataSource dataSource;
@Resource
private JobAlarmer jobAlarmer;
public String getI18n() {
if (!Arrays.asList("zh_CN", "zh_TC", "en").contains(i18n)) {
return "zh_CN";
}
return i18n;
}
public String getAccessToken() {
return accessToken;
}
public String getEmailFrom() {
return emailFrom;
}
public int getTriggerPoolFastMax() {
if (triggerPoolFastMax < 200) {
return 200;
}
return triggerPoolFastMax;
}
public int getTriggerPoolSlowMax() {
if (triggerPoolSlowMax < 100) {
return 100;
}
return triggerPoolSlowMax;
}
public int getLogretentiondays() {
if (logretentiondays < 7) {
return -1; // Limit greater than or equal to 7, otherwise close
}
return logretentiondays;
}
public XxlJobLogDao getXxlJobLogDao() {
return xxlJobLogDao;
}
public XxlJobInfoDao getXxlJobInfoDao() {
return xxlJobInfoDao;
}
public XxlJobRegistryDao getXxlJobRegistryDao() {
return xxlJobRegistryDao;
}
public XxlJobGroupDao getXxlJobGroupDao() {
return xxlJobGroupDao;
}
public XxlJobLogReportDao getXxlJobLogReportDao() {
return xxlJobLogReportDao;
}
public JavaMailSender getMailSender() {
return mailSender;
}
public DataSource getDataSource() {
return dataSource;
}
public JobAlarmer getJobAlarmer() {
return jobAlarmer;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
package com.xxl.job.admin.core.exception;
/**
* @author xuxueli 2019-05-04 23:19:29
*/
public class XxlJobException extends RuntimeException {
public XxlJobException() {
}
public XxlJobException(String message) {
super(message);
}
}

View File

@ -0,0 +1,77 @@
package com.xxl.job.admin.core.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* Created by xuxueli on 16/9/30.
*/
public class XxlJobGroup {
private int id;
private String appname;
private String title;
private int addressType; // 执行器地址类型0=自动注册1=手动录入
private String addressList; // 执行器地址列表多地址逗号分隔(手动录入)
private Date updateTime;
// registry list
private List<String> registryList; // 执行器地址列表(系统注册)
public List<String> getRegistryList() {
if (addressList!=null && addressList.trim().length()>0) {
registryList = new ArrayList<String>(Arrays.asList(addressList.split(",")));
}
return registryList;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAppname() {
return appname;
}
public void setAppname(String appname) {
this.appname = appname;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getAddressType() {
return addressType;
}
public void setAddressType(int addressType) {
this.addressType = addressType;
}
public String getAddressList() {
return addressList;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public void setAddressList(String addressList) {
this.addressList = addressList;
}
}

View File

@ -0,0 +1,237 @@
package com.xxl.job.admin.core.model;
import java.util.Date;
/**
* xxl-job info
*
* @author xuxueli 2016-1-12 18:25:49
*/
public class XxlJobInfo {
private int id; // 主键ID
private int jobGroup; // 执行器主键ID
private String jobDesc;
private Date addTime;
private Date updateTime;
private String author; // 负责人
private String alarmEmail; // 报警邮件
private String scheduleType; // 调度类型
private String scheduleConf; // 调度配置值含义取决于调度类型
private String misfireStrategy; // 调度过期策略
private String executorRouteStrategy; // 执行器路由策略
private String executorHandler; // 执行器任务Handler名称
private String executorParam; // 执行器任务参数
private String executorBlockStrategy; // 阻塞处理策略
private int executorTimeout; // 任务执行超时时间单位秒
private int executorFailRetryCount; // 失败重试次数
private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
private String glueSource; // GLUE源代码
private String glueRemark; // GLUE备注
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID多个逗号分隔
private int triggerStatus; // 调度状态0-停止1-运行
private long triggerLastTime; // 上次调度时间
private long triggerNextTime; // 下次调度时间
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getJobGroup() {
return jobGroup;
}
public void setJobGroup(int jobGroup) {
this.jobGroup = jobGroup;
}
public String getJobDesc() {
return jobDesc;
}
public void setJobDesc(String jobDesc) {
this.jobDesc = jobDesc;
}
public Date getAddTime() {
return addTime;
}
public void setAddTime(Date addTime) {
this.addTime = addTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getAlarmEmail() {
return alarmEmail;
}
public void setAlarmEmail(String alarmEmail) {
this.alarmEmail = alarmEmail;
}
public String getScheduleType() {
return scheduleType;
}
public void setScheduleType(String scheduleType) {
this.scheduleType = scheduleType;
}
public String getScheduleConf() {
return scheduleConf;
}
public void setScheduleConf(String scheduleConf) {
this.scheduleConf = scheduleConf;
}
public String getMisfireStrategy() {
return misfireStrategy;
}
public void setMisfireStrategy(String misfireStrategy) {
this.misfireStrategy = misfireStrategy;
}
public String getExecutorRouteStrategy() {
return executorRouteStrategy;
}
public void setExecutorRouteStrategy(String executorRouteStrategy) {
this.executorRouteStrategy = executorRouteStrategy;
}
public String getExecutorHandler() {
return executorHandler;
}
public void setExecutorHandler(String executorHandler) {
this.executorHandler = executorHandler;
}
public String getExecutorParam() {
return executorParam;
}
public void setExecutorParam(String executorParam) {
this.executorParam = executorParam;
}
public String getExecutorBlockStrategy() {
return executorBlockStrategy;
}
public void setExecutorBlockStrategy(String executorBlockStrategy) {
this.executorBlockStrategy = executorBlockStrategy;
}
public int getExecutorTimeout() {
return executorTimeout;
}
public void setExecutorTimeout(int executorTimeout) {
this.executorTimeout = executorTimeout;
}
public int getExecutorFailRetryCount() {
return executorFailRetryCount;
}
public void setExecutorFailRetryCount(int executorFailRetryCount) {
this.executorFailRetryCount = executorFailRetryCount;
}
public String getGlueType() {
return glueType;
}
public void setGlueType(String glueType) {
this.glueType = glueType;
}
public String getGlueSource() {
return glueSource;
}
public void setGlueSource(String glueSource) {
this.glueSource = glueSource;
}
public String getGlueRemark() {
return glueRemark;
}
public void setGlueRemark(String glueRemark) {
this.glueRemark = glueRemark;
}
public Date getGlueUpdatetime() {
return glueUpdatetime;
}
public void setGlueUpdatetime(Date glueUpdatetime) {
this.glueUpdatetime = glueUpdatetime;
}
public String getChildJobId() {
return childJobId;
}
public void setChildJobId(String childJobId) {
this.childJobId = childJobId;
}
public int getTriggerStatus() {
return triggerStatus;
}
public void setTriggerStatus(int triggerStatus) {
this.triggerStatus = triggerStatus;
}
public long getTriggerLastTime() {
return triggerLastTime;
}
public void setTriggerLastTime(long triggerLastTime) {
this.triggerLastTime = triggerLastTime;
}
public long getTriggerNextTime() {
return triggerNextTime;
}
public void setTriggerNextTime(long triggerNextTime) {
this.triggerNextTime = triggerNextTime;
}
}

View File

@ -0,0 +1,157 @@
package com.xxl.job.admin.core.model;
import java.util.Date;
/**
* xxl-job log, used to track trigger process
* @author xuxueli 2015-12-19 23:19:09
*/
public class XxlJobLog {
private long id;
// job info
private int jobGroup;
private int jobId;
// execute info
private String executorAddress;
private String executorHandler;
private String executorParam;
private String executorShardingParam;
private int executorFailRetryCount;
// trigger info
private Date triggerTime;
private int triggerCode;
private String triggerMsg;
// handle info
private Date handleTime;
private int handleCode;
private String handleMsg;
// alarm info
private int alarmStatus;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public int getJobGroup() {
return jobGroup;
}
public void setJobGroup(int jobGroup) {
this.jobGroup = jobGroup;
}
public int getJobId() {
return jobId;
}
public void setJobId(int jobId) {
this.jobId = jobId;
}
public String getExecutorAddress() {
return executorAddress;
}
public void setExecutorAddress(String executorAddress) {
this.executorAddress = executorAddress;
}
public String getExecutorHandler() {
return executorHandler;
}
public void setExecutorHandler(String executorHandler) {
this.executorHandler = executorHandler;
}
public String getExecutorParam() {
return executorParam;
}
public void setExecutorParam(String executorParam) {
this.executorParam = executorParam;
}
public String getExecutorShardingParam() {
return executorShardingParam;
}
public void setExecutorShardingParam(String executorShardingParam) {
this.executorShardingParam = executorShardingParam;
}
public int getExecutorFailRetryCount() {
return executorFailRetryCount;
}
public void setExecutorFailRetryCount(int executorFailRetryCount) {
this.executorFailRetryCount = executorFailRetryCount;
}
public Date getTriggerTime() {
return triggerTime;
}
public void setTriggerTime(Date triggerTime) {
this.triggerTime = triggerTime;
}
public int getTriggerCode() {
return triggerCode;
}
public void setTriggerCode(int triggerCode) {
this.triggerCode = triggerCode;
}
public String getTriggerMsg() {
return triggerMsg;
}
public void setTriggerMsg(String triggerMsg) {
this.triggerMsg = triggerMsg;
}
public Date getHandleTime() {
return handleTime;
}
public void setHandleTime(Date handleTime) {
this.handleTime = handleTime;
}
public int getHandleCode() {
return handleCode;
}
public void setHandleCode(int handleCode) {
this.handleCode = handleCode;
}
public String getHandleMsg() {
return handleMsg;
}
public void setHandleMsg(String handleMsg) {
this.handleMsg = handleMsg;
}
public int getAlarmStatus() {
return alarmStatus;
}
public void setAlarmStatus(int alarmStatus) {
this.alarmStatus = alarmStatus;
}
}

View File

@ -0,0 +1,75 @@
package com.xxl.job.admin.core.model;
import java.util.Date;
/**
* xxl-job log for glue, used to track job code process
* @author xuxueli 2016-5-19 17:57:46
*/
public class XxlJobLogGlue {
private int id;
private int jobId; // 任务主键ID
private String glueType; // GLUE类型 #com.xxl.job.core.glue.GlueTypeEnum
private String glueSource;
private String glueRemark;
private Date addTime;
private Date updateTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getJobId() {
return jobId;
}
public void setJobId(int jobId) {
this.jobId = jobId;
}
public String getGlueType() {
return glueType;
}
public void setGlueType(String glueType) {
this.glueType = glueType;
}
public String getGlueSource() {
return glueSource;
}
public void setGlueSource(String glueSource) {
this.glueSource = glueSource;
}
public String getGlueRemark() {
return glueRemark;
}
public void setGlueRemark(String glueRemark) {
this.glueRemark = glueRemark;
}
public Date getAddTime() {
return addTime;
}
public void setAddTime(Date addTime) {
this.addTime = addTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}

View File

@ -0,0 +1,54 @@
package com.xxl.job.admin.core.model;
import java.util.Date;
public class XxlJobLogReport {
private int id;
private Date triggerDay;
private int runningCount;
private int sucCount;
private int failCount;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Date getTriggerDay() {
return triggerDay;
}
public void setTriggerDay(Date triggerDay) {
this.triggerDay = triggerDay;
}
public int getRunningCount() {
return runningCount;
}
public void setRunningCount(int runningCount) {
this.runningCount = runningCount;
}
public int getSucCount() {
return sucCount;
}
public void setSucCount(int sucCount) {
this.sucCount = sucCount;
}
public int getFailCount() {
return failCount;
}
public void setFailCount(int failCount) {
this.failCount = failCount;
}
}

View File

@ -0,0 +1,55 @@
package com.xxl.job.admin.core.model;
import java.util.Date;
/**
* Created by xuxueli on 16/9/30.
*/
public class XxlJobRegistry {
private int id;
private String registryGroup;
private String registryKey;
private String registryValue;
private Date updateTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRegistryGroup() {
return registryGroup;
}
public void setRegistryGroup(String registryGroup) {
this.registryGroup = registryGroup;
}
public String getRegistryKey() {
return registryKey;
}
public void setRegistryKey(String registryKey) {
this.registryKey = registryKey;
}
public String getRegistryValue() {
return registryValue;
}
public void setRegistryValue(String registryValue) {
this.registryValue = registryValue;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}

View File

@ -0,0 +1,73 @@
package com.xxl.job.admin.core.model;
import org.springframework.util.StringUtils;
/**
* @author xuxueli 2019-05-04 16:43:12
*/
public class XxlJobUser {
private int id;
private String username; // 账号
private String password; // 密码
private int role; // 角色0-普通用户1-管理员
private String permission; // 权限执行器ID列表多个逗号分割
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getRole() {
return role;
}
public void setRole(int role) {
this.role = role;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
// plugin
public boolean validPermission(int jobGroup){
if (this.role == 1) {
return true;
} else {
if (StringUtils.hasText(this.permission)) {
for (String permissionItem : this.permission.split(",")) {
if (String.valueOf(jobGroup).equals(permissionItem)) {
return true;
}
}
}
return false;
}
}
}

View File

@ -0,0 +1,32 @@
//package com.xxl.job.admin.core.jobbean;
//
//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
//import org.quartz.JobExecutionContext;
//import org.quartz.JobExecutionException;
//import org.quartz.JobKey;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import org.springframework.scheduling.quartz.QuartzJobBean;
//
///**
// * http job bean
// * @DisallowConcurrentExecution disable concurrent, thread size can not be only one, better given more
// * @author xuxueli 2015-12-17 18:20:34
// */
////@DisallowConcurrentExecution
//public class RemoteHttpJobBean extends QuartzJobBean {
// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
//
// @Override
// protected void executeInternal(JobExecutionContext context)
// throws JobExecutionException {
//
// // load jobId
// JobKey jobKey = context.getTrigger().getJobKey();
// Integer jobId = Integer.valueOf(jobKey.getName());
//
//
// }
//
//}

View File

@ -0,0 +1,413 @@
//package com.xxl.job.admin.core.schedule;
//
//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
//import com.xxl.job.admin.core.model.XxlJobInfo;
//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
//import com.xxl.job.admin.core.util.I18nUtil;
//import com.xxl.job.core.biz.AdminBiz;
//import com.xxl.job.core.biz.ExecutorBiz;
//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
//import com.xxl.rpc.remoting.invoker.call.CallType;
//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
//import com.xxl.rpc.remoting.net.NetEnum;
//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
//import com.xxl.rpc.serialize.Serializer;
//import org.quartz.*;
//import org.quartz.Trigger.TriggerState;
//import org.quartz.impl.triggers.CronTriggerImpl;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import org.springframework.util.Assert;
//
//import javax.servlet.ServletException;
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;
//import java.io.IOException;
//import java.util.Date;
//import java.util.concurrent.ConcurrentHashMap;
//
///**
// * base quartz scheduler util
// * @author xuxueli 2015-12-19 16:13:53
// */
//public final class XxlJobDynamicScheduler {
// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
//
// // ---------------------- param ----------------------
//
// // scheduler
// private static Scheduler scheduler;
// public void setScheduler(Scheduler scheduler) {
// XxlJobDynamicScheduler_old.scheduler = scheduler;
// }
//
//
// // ---------------------- init + destroy ----------------------
// public void start() throws Exception {
// // valid
// Assert.notNull(scheduler, "quartz scheduler is null");
//
// // init i18n
// initI18n();
//
// // admin registry monitor run
// JobRegistryMonitorHelper.getInstance().start();
//
// // admin monitor run
// JobFailMonitorHelper.getInstance().start();
//
// // admin-server
// initRpcProvider();
//
// logger.info(">>>>>>>>> init xxl-job admin success.");
// }
//
//
// public void destroy() throws Exception {
// // admin trigger pool stop
// JobTriggerPoolHelper.toStop();
//
// // admin registry stop
// JobRegistryMonitorHelper.getInstance().toStop();
//
// // admin monitor stop
// JobFailMonitorHelper.getInstance().toStop();
//
// // admin-server
// stopRpcProvider();
// }
//
//
// // ---------------------- I18n ----------------------
//
// private void initI18n(){
// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
// }
// }
//
//
// // ---------------------- admin rpc provider (no server version) ----------------------
// private static ServletServerHandler servletServerHandler;
// private void initRpcProvider(){
// // init
// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
// xxlRpcProviderFactory.initConfig(
// NetEnum.NETTY_HTTP,
// Serializer.SerializeEnum.HESSIAN.getSerializer(),
// null,
// 0,
// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
// null,
// null);
//
// // add services
// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
//
// // servlet handler
// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
// }
// private void stopRpcProvider() throws Exception {
// XxlRpcInvokerFactory.getInstance().stop();
// }
// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// servletServerHandler.handle(null, request, response);
// }
//
//
// // ---------------------- executor-client ----------------------
// private static ConcurrentHashMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
// // valid
// if (address==null || address.trim().length()==0) {
// return null;
// }
//
// // load-cache
// address = address.trim();
// ExecutorBiz executorBiz = executorBizRepository.get(address);
// if (executorBiz != null) {
// return executorBiz;
// }
//
// // set-cache
// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
// NetEnum.NETTY_HTTP,
// Serializer.SerializeEnum.HESSIAN.getSerializer(),
// CallType.SYNC,
// LoadBalance.ROUND,
// ExecutorBiz.class,
// null,
// 5000,
// address,
// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
// null,
// null).getObject();
//
// executorBizRepository.put(address, executorBiz);
// return executorBiz;
// }
//
//
// // ---------------------- schedule util ----------------------
//
// /**
// * fill job info
// *
// * @param jobInfo
// */
// public static void fillJobInfo(XxlJobInfo jobInfo) {
//
// String name = String.valueOf(jobInfo.getId());
//
// // trigger key
// TriggerKey triggerKey = TriggerKey.triggerKey(name);
// try {
//
// // trigger cron
// Trigger trigger = scheduler.getTrigger(triggerKey);
// if (trigger!=null && trigger instanceof CronTriggerImpl) {
// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
// jobInfo.setJobCron(cronExpression);
// }
//
// // trigger state
// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
// if (triggerState!=null) {
// jobInfo.setJobStatus(triggerState.name());
// }
//
// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
// //String jobClass = jobDetail.getJobClass().getName();
//
// } catch (SchedulerException e) {
// logger.error(e.getMessage(), e);
// }
// }
//
//
// /**
// * add trigger + job
// *
// * @param jobName
// * @param cronExpression
// * @return
// * @throws SchedulerException
// */
// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
// // 1job key
// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
// JobKey jobKey = new JobKey(jobName);
//
// // 2valid
// if (scheduler.checkExists(triggerKey)) {
// return true; // PASS
// }
//
// // 3corn trigger
// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
//
// // 4job detail
// Class<? extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
//
// /*if (jobInfo.getJobData()!=null) {
// JobDataMap jobDataMap = jobDetail.getJobDataMap();
// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
// }*/
//
// // 5schedule job
// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
//
// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
// return true;
// }
//
//
// /**
// * remove trigger + job
// *
// * @param jobName
// * @return
// * @throws SchedulerException
// */
// public static boolean removeJob(String jobName) throws SchedulerException {
//
// JobKey jobKey = new JobKey(jobName);
// scheduler.deleteJob(jobKey);
//
// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
// if (scheduler.checkExists(triggerKey)) {
// scheduler.unscheduleJob(triggerKey); // trigger + job
// }*/
//
// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
// return true;
// }
//
//
// /**
// * updateJobCron
// *
// * @param jobName
// * @param cronExpression
// * @return
// * @throws SchedulerException
// */
// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
//
// // 1job key
// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//
// // 2valid
// if (!scheduler.checkExists(triggerKey)) {
// return true; // PASS
// }
//
// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
//
// // 3avoid repeat cron
// String oldCron = oldTrigger.getCronExpression();
// if (oldCron.equals(cronExpression)){
// return true; // PASS
// }
//
// // 4new cron trigger
// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
//
// // 5rescheduleJob
// scheduler.rescheduleJob(triggerKey, oldTrigger);
//
// /*
// JobKey jobKey = new JobKey(jobName);
//
// // old job detail
// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
//
// // new trigger
// HashSet<Trigger> triggerSet = new HashSet<Trigger>();
// triggerSet.add(cronTrigger);
// // cover trigger of job detail
// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
//
// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
// return true;
// }
//
//
// /**
// * pause
// *
// * @param jobName
// * @return
// * @throws SchedulerException
// */
// /*public static boolean pauseJob(String jobName) throws SchedulerException {
//
// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//
// boolean result = false;
// if (scheduler.checkExists(triggerKey)) {
// scheduler.pauseTrigger(triggerKey);
// result = true;
// }
//
// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
// return result;
// }*/
//
//
// /**
// * resume
// *
// * @param jobName
// * @return
// * @throws SchedulerException
// */
// /*public static boolean resumeJob(String jobName) throws SchedulerException {
//
// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//
// boolean result = false;
// if (scheduler.checkExists(triggerKey)) {
// scheduler.resumeTrigger(triggerKey);
// result = true;
// }
//
// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
// return result;
// }*/
//
//
// /**
// * run
// *
// * @param jobName
// * @return
// * @throws SchedulerException
// */
// /*public static boolean triggerJob(String jobName) throws SchedulerException {
// // TriggerKey : name + group
// JobKey jobKey = new JobKey(jobName);
// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
//
// boolean result = false;
// if (scheduler.checkExists(triggerKey)) {
// scheduler.triggerJob(jobKey);
// result = true;
// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
// } else {
// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
// }
// return result;
// }*/
//
//
// /**
// * finaAllJobList
// *
// * @return
// *//*
// @Deprecated
// public static List<Map<String, Object>> finaAllJobList(){
// List<Map<String, Object>> jobList = new ArrayList<Map<String,Object>>();
//
// try {
// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
// return null;
// }
// String groupName = scheduler.getJobGroupNames().get(0);
// Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
// if (jobKeys!=null && jobKeys.size()>0) {
// for (JobKey jobKey : jobKeys) {
// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
// Trigger trigger = scheduler.getTrigger(triggerKey);
// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
// Map<String, Object> jobMap = new HashMap<String, Object>();
// jobMap.put("TriggerKey", triggerKey);
// jobMap.put("Trigger", trigger);
// jobMap.put("JobDetail", jobDetail);
// jobMap.put("TriggerState", triggerState);
// jobList.add(jobMap);
// }
// }
//
// } catch (SchedulerException e) {
// logger.error(e.getMessage(), e);
// return null;
// }
// return jobList;
// }*/
//
//}

View File

@ -0,0 +1,58 @@
//package com.xxl.job.admin.core.quartz;
//
//import org.quartz.SchedulerConfigException;
//import org.quartz.spi.ThreadPool;
//
///**
// * single thread pool, for async trigger
// *
// * @author xuxueli 2019-03-06
// */
//public class XxlJobThreadPool implements ThreadPool {
//
// @Override
// public boolean runInThread(Runnable runnable) {
//
// // async run
// runnable.run();
// return true;
//
// //return false;
// }
//
// @Override
// public int blockForAvailableThreads() {
// return 1;
// }
//
// @Override
// public void initialize() throws SchedulerConfigException {
//
// }
//
// @Override
// public void shutdown(boolean waitForJobsToComplete) {
//
// }
//
// @Override
// public int getPoolSize() {
// return 1;
// }
//
// @Override
// public void setInstanceId(String schedInstId) {
//
// }
//
// @Override
// public void setInstanceName(String schedName) {
//
// }
//
// // support
// public void setThreadCount(int count) {
// //
// }
//
//}

View File

@ -0,0 +1,48 @@
package com.xxl.job.admin.core.route;
import com.xxl.job.admin.core.route.strategy.*;
import com.xxl.job.admin.core.util.I18nUtil;
/**
* Created by xuxueli on 17/3/10.
*/
public enum ExecutorRouteStrategyEnum {
FIRST(I18nUtil.getString("jobconf_route_first"), new ExecutorRouteFirst()),
LAST(I18nUtil.getString("jobconf_route_last"), new ExecutorRouteLast()),
ROUND(I18nUtil.getString("jobconf_route_round"), new ExecutorRouteRound()),
RANDOM(I18nUtil.getString("jobconf_route_random"), new ExecutorRouteRandom()),
CONSISTENT_HASH(I18nUtil.getString("jobconf_route_consistenthash"), new ExecutorRouteConsistentHash()),
LEAST_FREQUENTLY_USED(I18nUtil.getString("jobconf_route_lfu"), new ExecutorRouteLFU()),
LEAST_RECENTLY_USED(I18nUtil.getString("jobconf_route_lru"), new ExecutorRouteLRU()),
FAILOVER(I18nUtil.getString("jobconf_route_failover"), new ExecutorRouteFailover()),
BUSYOVER(I18nUtil.getString("jobconf_route_busyover"), new ExecutorRouteBusyover()),
SHARDING_BROADCAST(I18nUtil.getString("jobconf_route_shard"), null);
ExecutorRouteStrategyEnum(String title, ExecutorRouter router) {
this.title = title;
this.router = router;
}
private String title;
private ExecutorRouter router;
public String getTitle() {
return title;
}
public ExecutorRouter getRouter() {
return router;
}
public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem){
if (name != null) {
for (ExecutorRouteStrategyEnum item: ExecutorRouteStrategyEnum.values()) {
if (item.name().equals(name)) {
return item;
}
}
}
return defaultItem;
}
}

View File

@ -0,0 +1,24 @@
package com.xxl.job.admin.core.route;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Created by xuxueli on 17/3/10.
*/
public abstract class ExecutorRouter {
protected static Logger logger = LoggerFactory.getLogger(ExecutorRouter.class);
/**
* route address
*
* @param addressList
* @return ReturnT.content=address
*/
public abstract ReturnT<String> route(TriggerParam triggerParam, List<String> addressList);
}

View File

@ -0,0 +1,48 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.IdleBeatParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteBusyover extends ExecutorRouter {
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
StringBuffer idleBeatResultSB = new StringBuffer();
for (String address : addressList) {
// beat
ReturnT<String> idleBeatResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(new IdleBeatParam(triggerParam.getJobId()));
} catch (Exception e) {
logger.error(e.getMessage(), e);
idleBeatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
}
idleBeatResultSB.append( (idleBeatResultSB.length()>0)?"<br><br>":"")
.append(I18nUtil.getString("jobconf_idleBeat") + "")
.append("<br>address").append(address)
.append("<br>code").append(idleBeatResult.getCode())
.append("<br>msg").append(idleBeatResult.getMsg());
// beat success
if (idleBeatResult.getCode() == ReturnT.SUCCESS_CODE) {
idleBeatResult.setMsg(idleBeatResultSB.toString());
idleBeatResult.setContent(address);
return idleBeatResult;
}
}
return new ReturnT<String>(ReturnT.FAIL_CODE, idleBeatResultSB.toString());
}
}

View File

@ -0,0 +1,85 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* 分组下机器地址相同不同JOB均匀散列在不同机器上保证分组下机器分配JOB平均且每个JOB固定调度其中一台机器
* avirtual node解决不均衡问题
* bhash method replace hashCodeString的hashCode可能重复需要进一步扩大hashCode的取值范围
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteConsistentHash extends ExecutorRouter {
private static int VIRTUAL_NODE_NUM = 100;
/**
* get hash code on 2^32 ring (md5散列的方式计算hash值)
* @param key
* @return
*/
private static long hash(String key) {
// md5 byte
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 not supported", e);
}
md5.reset();
byte[] keyBytes = null;
try {
keyBytes = key.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unknown string :" + key, e);
}
md5.update(keyBytes);
byte[] digest = md5.digest();
// hash code, Truncate to 32-bits
long hashCode = ((long) (digest[3] & 0xFF) << 24)
| ((long) (digest[2] & 0xFF) << 16)
| ((long) (digest[1] & 0xFF) << 8)
| (digest[0] & 0xFF);
long truncateHashCode = hashCode & 0xffffffffL;
return truncateHashCode;
}
public String hashJob(int jobId, List<String> addressList) {
// ------A1------A2-------A3------
// -----------J1------------------
TreeMap<Long, String> addressRing = new TreeMap<Long, String>();
for (String address: addressList) {
for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
long addressHash = hash("SHARD-" + address + "-NODE-" + i);
addressRing.put(addressHash, address);
}
}
long jobHash = hash(String.valueOf(jobId));
SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
if (!lastRing.isEmpty()) {
return lastRing.get(lastRing.firstKey());
}
return addressRing.firstEntry().getValue();
}
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
String address = hashJob(triggerParam.getJobId(), addressList);
return new ReturnT<String>(address);
}
}

View File

@ -0,0 +1,48 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteFailover extends ExecutorRouter {
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
StringBuffer beatResultSB = new StringBuffer();
for (String address : addressList) {
// beat
ReturnT<String> beatResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
beatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
}
beatResultSB.append( (beatResultSB.length()>0)?"<br><br>":"")
.append(I18nUtil.getString("jobconf_beat") + "")
.append("<br>address").append(address)
.append("<br>code").append(beatResult.getCode())
.append("<br>msg").append(beatResult.getMsg());
// beat success
if (beatResult.getCode() == ReturnT.SUCCESS_CODE) {
beatResult.setMsg(beatResultSB.toString());
beatResult.setContent(address);
return beatResult;
}
}
return new ReturnT<String>(ReturnT.FAIL_CODE, beatResultSB.toString());
}
}

View File

@ -0,0 +1,19 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteFirst extends ExecutorRouter {
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList){
return new ReturnT<String>(addressList.get(0));
}
}

View File

@ -0,0 +1,79 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 单个JOB对应的每个执行器使用频率最低的优先被选举
* a(*)LFU(Least Frequently Used)最不经常使用频率/次数
* bLRU(Least Recently Used)最近最久未使用时间
*
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteLFU extends ExecutorRouter {
private static ConcurrentMap<Integer, HashMap<String, Integer>> jobLfuMap = new ConcurrentHashMap<Integer, HashMap<String, Integer>>();
private static long CACHE_VALID_TIME = 0;
public String route(int jobId, List<String> addressList) {
// cache clear
if (System.currentTimeMillis() > CACHE_VALID_TIME) {
jobLfuMap.clear();
CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
}
// lfu item init
HashMap<String, Integer> lfuItemMap = jobLfuMap.get(jobId); // Key排序可以用TreeMap+构造入参CompareValue排序暂时只能通过ArrayList
if (lfuItemMap == null) {
lfuItemMap = new HashMap<String, Integer>();
jobLfuMap.putIfAbsent(jobId, lfuItemMap); // 避免重复覆盖
}
// put new
for (String address: addressList) {
if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) >1000000 ) {
lfuItemMap.put(address, new Random().nextInt(addressList.size())); // 初始化时主动Random一次缓解首次压力
}
}
// remove old
List<String> delKeys = new ArrayList<>();
for (String existKey: lfuItemMap.keySet()) {
if (!addressList.contains(existKey)) {
delKeys.add(existKey);
}
}
if (delKeys.size() > 0) {
for (String delKey: delKeys) {
lfuItemMap.remove(delKey);
}
}
// load least userd count address
List<Map.Entry<String, Integer>> lfuItemList = new ArrayList<Map.Entry<String, Integer>>(lfuItemMap.entrySet());
Collections.sort(lfuItemList, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
Map.Entry<String, Integer> addressItem = lfuItemList.get(0);
String minAddress = addressItem.getKey();
addressItem.setValue(addressItem.getValue() + 1);
return addressItem.getKey();
}
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
String address = route(triggerParam.getJobId(), addressList);
return new ReturnT<String>(address);
}
}

View File

@ -0,0 +1,76 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 单个JOB对应的每个执行器最久为使用的优先被选举
* aLFU(Least Frequently Used)最不经常使用频率/次数
* b(*)LRU(Least Recently Used)最近最久未使用时间
*
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteLRU extends ExecutorRouter {
private static ConcurrentMap<Integer, LinkedHashMap<String, String>> jobLRUMap = new ConcurrentHashMap<Integer, LinkedHashMap<String, String>>();
private static long CACHE_VALID_TIME = 0;
public String route(int jobId, List<String> addressList) {
// cache clear
if (System.currentTimeMillis() > CACHE_VALID_TIME) {
jobLRUMap.clear();
CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
}
// init lru
LinkedHashMap<String, String> lruItem = jobLRUMap.get(jobId);
if (lruItem == null) {
/**
* LinkedHashMap
* aaccessOrdertrue=访问顺序排序get/put时排序false=插入顺序排期
* bremoveEldestEntry新增元素时将会调用返回true时会删除最老元素可封装LinkedHashMap并重写该方法比如定义最大容量超出是返回true即可实现固定长度的LRU算法
*/
lruItem = new LinkedHashMap<String, String>(16, 0.75f, true);
jobLRUMap.putIfAbsent(jobId, lruItem);
}
// put new
for (String address: addressList) {
if (!lruItem.containsKey(address)) {
lruItem.put(address, address);
}
}
// remove old
List<String> delKeys = new ArrayList<>();
for (String existKey: lruItem.keySet()) {
if (!addressList.contains(existKey)) {
delKeys.add(existKey);
}
}
if (delKeys.size() > 0) {
for (String delKey: delKeys) {
lruItem.remove(delKey);
}
}
// load
String eldestKey = lruItem.entrySet().iterator().next().getKey();
String eldestValue = lruItem.get(eldestKey);
return eldestValue;
}
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
String address = route(triggerParam.getJobId(), addressList);
return new ReturnT<String>(address);
}
}

View File

@ -0,0 +1,19 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteLast extends ExecutorRouter {
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
return new ReturnT<String>(addressList.get(addressList.size()-1));
}
}

View File

@ -0,0 +1,23 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
import java.util.Random;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteRandom extends ExecutorRouter {
private static Random localRandom = new Random();
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
String address = addressList.get(localRandom.nextInt(addressList.size()));
return new ReturnT<String>(address);
}
}

View File

@ -0,0 +1,46 @@
package com.xxl.job.admin.core.route.strategy;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by xuxueli on 17/3/10.
*/
public class ExecutorRouteRound extends ExecutorRouter {
private static ConcurrentMap<Integer, AtomicInteger> routeCountEachJob = new ConcurrentHashMap<>();
private static long CACHE_VALID_TIME = 0;
private static int count(int jobId) {
// cache clear
if (System.currentTimeMillis() > CACHE_VALID_TIME) {
routeCountEachJob.clear();
CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
}
AtomicInteger count = routeCountEachJob.get(jobId);
if (count == null || count.get() > 1000000) {
// 初始化时主动Random一次缓解首次压力
count = new AtomicInteger(new Random().nextInt(100));
} else {
// count++
count.addAndGet(1);
}
routeCountEachJob.put(jobId, count);
return count.get();
}
@Override
public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
String address = addressList.get(count(triggerParam.getJobId())%addressList.size());
return new ReturnT<String>(address);
}
}

View File

@ -0,0 +1,39 @@
package com.xxl.job.admin.core.scheduler;
import com.xxl.job.admin.core.util.I18nUtil;
/**
* @author xuxueli 2020-10-29 21:11:23
*/
public enum MisfireStrategyEnum {
/**
* do nothing
*/
DO_NOTHING(I18nUtil.getString("misfire_strategy_do_nothing")),
/**
* fire once now
*/
FIRE_ONCE_NOW(I18nUtil.getString("misfire_strategy_fire_once_now"));
private String title;
MisfireStrategyEnum(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public static MisfireStrategyEnum match(String name, MisfireStrategyEnum defaultItem){
for (MisfireStrategyEnum item: MisfireStrategyEnum.values()) {
if (item.name().equals(name)) {
return item;
}
}
return defaultItem;
}
}

View File

@ -0,0 +1,46 @@
package com.xxl.job.admin.core.scheduler;
import com.xxl.job.admin.core.util.I18nUtil;
/**
* @author xuxueli 2020-10-29 21:11:23
*/
public enum ScheduleTypeEnum {
NONE(I18nUtil.getString("schedule_type_none")),
/**
* schedule by cron
*/
CRON(I18nUtil.getString("schedule_type_cron")),
/**
* schedule by fixed rate (in seconds)
*/
FIX_RATE(I18nUtil.getString("schedule_type_fix_rate")),
/**
* schedule by fix delay (in seconds) after the last time
*/
/*FIX_DELAY(I18nUtil.getString("schedule_type_fix_delay"))*/;
private String title;
ScheduleTypeEnum(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public static ScheduleTypeEnum match(String name, ScheduleTypeEnum defaultItem){
for (ScheduleTypeEnum item: ScheduleTypeEnum.values()) {
if (item.name().equals(name)) {
return item;
}
}
return defaultItem;
}
}

View File

@ -0,0 +1,101 @@
package com.xxl.job.admin.core.scheduler;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.thread.*;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.client.ExecutorBizClient;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author xuxueli 2018-10-28 00:18:17
*/
public class XxlJobScheduler {
private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
public void init() throws Exception {
// init i18n
initI18n();
// admin trigger pool start
JobTriggerPoolHelper.toStart();
// admin registry monitor run
JobRegistryHelper.getInstance().start();
// admin fail-monitor run
JobFailMonitorHelper.getInstance().start();
// admin lose-monitor run ( depend on JobTriggerPoolHelper )
JobCompleteHelper.getInstance().start();
// admin log report start
JobLogReportHelper.getInstance().start();
// start-schedule ( depend on JobTriggerPoolHelper )
JobScheduleHelper.getInstance().start();
logger.info(">>>>>>>>> init xxl-job admin success.");
}
public void destroy() throws Exception {
// stop-schedule
JobScheduleHelper.getInstance().toStop();
// admin log report stop
JobLogReportHelper.getInstance().toStop();
// admin lose-monitor stop
JobCompleteHelper.getInstance().toStop();
// admin fail-monitor stop
JobFailMonitorHelper.getInstance().toStop();
// admin registry stop
JobRegistryHelper.getInstance().toStop();
// admin trigger pool stop
JobTriggerPoolHelper.toStop();
}
// ---------------------- I18n ----------------------
private void initI18n(){
for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
}
}
// ---------------------- executor-client ----------------------
private static ConcurrentMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
public static ExecutorBiz getExecutorBiz(String address) throws Exception {
// valid
if (address==null || address.trim().length()==0) {
return null;
}
// load-cache
address = address.trim();
ExecutorBiz executorBiz = executorBizRepository.get(address);
if (executorBiz != null) {
return executorBiz;
}
// set-cache
executorBiz = new ExecutorBizClient(address, XxlJobAdminConfig.getAdminConfig().getAccessToken());
executorBizRepository.put(address, executorBiz);
return executorBiz;
}
}

View File

@ -0,0 +1,184 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.complete.XxlJobCompleter;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.model.HandleCallbackParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
/**
* job lose-monitor instance
*
* @author xuxueli 2015-9-1 18:05:56
*/
public class JobCompleteHelper {
private static Logger logger = LoggerFactory.getLogger(JobCompleteHelper.class);
private static JobCompleteHelper instance = new JobCompleteHelper();
public static JobCompleteHelper getInstance(){
return instance;
}
// ---------------------- monitor ----------------------
private ThreadPoolExecutor callbackThreadPool = null;
private Thread monitorThread;
private volatile boolean toStop = false;
public void start(){
// for callback
callbackThreadPool = new ThreadPoolExecutor(
2,
20,
30L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobLosedMonitorHelper-callbackThreadPool-" + r.hashCode());
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
r.run();
logger.warn(">>>>>>>>>>> xxl-job, callback too fast, match threadpool rejected handler(run now).");
}
});
// for monitor
monitorThread = new Thread(new Runnable() {
@Override
public void run() {
// wait for JobTriggerPoolHelper-init
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
// monitor
while (!toStop) {
try {
// 任务结果丢失处理调度记录停留在 "运行中" 状态超过10min且对应执行器心跳注册失败不在线则将本地调度主动标记失败
Date losedTime = DateUtil.addMinutes(new Date(), -10);
List<Long> losedJobIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLostJobIds(losedTime);
if (losedJobIds!=null && losedJobIds.size()>0) {
for (Long logId: losedJobIds) {
XxlJobLog jobLog = new XxlJobLog();
jobLog.setId(logId);
jobLog.setHandleTime(new Date());
jobLog.setHandleCode(ReturnT.FAIL_CODE);
jobLog.setHandleMsg( I18nUtil.getString("joblog_lost_fail") );
XxlJobCompleter.updateHandleInfoAndFinish(jobLog);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
}
}
try {
TimeUnit.SECONDS.sleep(60);
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, JobLosedMonitorHelper stop");
}
});
monitorThread.setDaemon(true);
monitorThread.setName("xxl-job, admin JobLosedMonitorHelper");
monitorThread.start();
}
public void toStop(){
toStop = true;
// stop registryOrRemoveThreadPool
callbackThreadPool.shutdownNow();
// stop monitorThread (interrupt and wait)
monitorThread.interrupt();
try {
monitorThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
// ---------------------- helper ----------------------
public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
callbackThreadPool.execute(new Runnable() {
@Override
public void run() {
for (HandleCallbackParam handleCallbackParam: callbackParamList) {
ReturnT<String> callbackResult = callback(handleCallbackParam);
logger.debug(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}",
(callbackResult.getCode()== ReturnT.SUCCESS_CODE?"success":"fail"), handleCallbackParam, callbackResult);
}
}
});
return ReturnT.SUCCESS;
}
private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
// valid log item
XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(handleCallbackParam.getLogId());
if (log == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found.");
}
if (log.getHandleCode() > 0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "log repeate callback."); // avoid repeat callback, trigger child job etc
}
// handle msg
StringBuffer handleMsg = new StringBuffer();
if (log.getHandleMsg()!=null) {
handleMsg.append(log.getHandleMsg()).append("<br>");
}
if (handleCallbackParam.getHandleMsg() != null) {
handleMsg.append(handleCallbackParam.getHandleMsg());
}
// success, save log
log.setHandleTime(new Date());
log.setHandleCode(handleCallbackParam.getHandleCode());
log.setHandleMsg(handleMsg.toString());
XxlJobCompleter.updateHandleInfoAndFinish(log);
return ReturnT.SUCCESS;
}
}

View File

@ -0,0 +1,110 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.util.I18nUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* job monitor instance
*
* @author xuxueli 2015-9-1 18:05:56
*/
public class JobFailMonitorHelper {
private static Logger logger = LoggerFactory.getLogger(JobFailMonitorHelper.class);
private static JobFailMonitorHelper instance = new JobFailMonitorHelper();
public static JobFailMonitorHelper getInstance(){
return instance;
}
// ---------------------- monitor ----------------------
private Thread monitorThread;
private volatile boolean toStop = false;
public void start(){
monitorThread = new Thread(new Runnable() {
@Override
public void run() {
// monitor
while (!toStop) {
try {
List<Long> failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findFailJobLogIds(1000);
if (failLogIds!=null && !failLogIds.isEmpty()) {
for (long failLogId: failLogIds) {
// lock log
int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, 0, -1);
if (lockRet < 1) {
continue;
}
XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId);
XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId());
// 1fail retry monitor
if (log.getExecutorFailRetryCount() > 0) {
JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam(), null);
String retryMsg = "<br><br><span style=\"color:#F39C12;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<<<<<<<<< </span><br>";
log.setTriggerMsg(log.getTriggerMsg() + retryMsg);
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log);
}
// 2fail alarm monitor
int newAlarmStatus = 0; // 告警状态0-默认-1=锁定状态1-无需告警2-告警成功3-告警失败
if (info != null) {
boolean alarmResult = XxlJobAdminConfig.getAdminConfig().getJobAlarmer().alarm(info, log);
newAlarmStatus = alarmResult?2:3;
} else {
newAlarmStatus = 1;
}
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, -1, newAlarmStatus);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, job fail monitor thread error:{}", e);
}
}
try {
TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, job fail monitor thread stop");
}
});
monitorThread.setDaemon(true);
monitorThread.setName("xxl-job, admin JobFailMonitorHelper");
monitorThread.start();
}
public void toStop(){
toStop = true;
// interrupt and wait
monitorThread.interrupt();
try {
monitorThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,152 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobLogReport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* job log report helper
*
* @author xuxueli 2019-11-22
*/
public class JobLogReportHelper {
private static Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class);
private static JobLogReportHelper instance = new JobLogReportHelper();
public static JobLogReportHelper getInstance(){
return instance;
}
private Thread logrThread;
private volatile boolean toStop = false;
public void start(){
logrThread = new Thread(new Runnable() {
@Override
public void run() {
// last clean log time
long lastCleanLogTime = 0;
while (!toStop) {
// 1log-report refresh: refresh log report in 3 days
try {
for (int i = 0; i < 3; i++) {
// today
Calendar itemDay = Calendar.getInstance();
itemDay.add(Calendar.DAY_OF_MONTH, -i);
itemDay.set(Calendar.HOUR_OF_DAY, 0);
itemDay.set(Calendar.MINUTE, 0);
itemDay.set(Calendar.SECOND, 0);
itemDay.set(Calendar.MILLISECOND, 0);
Date todayFrom = itemDay.getTime();
itemDay.set(Calendar.HOUR_OF_DAY, 23);
itemDay.set(Calendar.MINUTE, 59);
itemDay.set(Calendar.SECOND, 59);
itemDay.set(Calendar.MILLISECOND, 999);
Date todayTo = itemDay.getTime();
// refresh log-report every minute
XxlJobLogReport xxlJobLogReport = new XxlJobLogReport();
xxlJobLogReport.setTriggerDay(todayFrom);
xxlJobLogReport.setRunningCount(0);
xxlJobLogReport.setSucCount(0);
xxlJobLogReport.setFailCount(0);
Map<String, Object> triggerCountMap = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLogReport(todayFrom, todayTo);
if (triggerCountMap!=null && triggerCountMap.size()>0) {
int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCount"))):0;
int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))):0;
int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))):0;
int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc;
xxlJobLogReport.setRunningCount(triggerDayCountRunning);
xxlJobLogReport.setSucCount(triggerDayCountSuc);
xxlJobLogReport.setFailCount(triggerDayCountFail);
}
// do refresh
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().update(xxlJobLogReport);
if (ret < 1) {
XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().save(xxlJobLogReport);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, job log report thread error:{}", e);
}
}
// 2log-clean: switch open & once each day
if (XxlJobAdminConfig.getAdminConfig().getLogretentiondays()>0
&& System.currentTimeMillis() - lastCleanLogTime > 24*60*60*1000) {
// expire-time
Calendar expiredDay = Calendar.getInstance();
expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminConfig.getAdminConfig().getLogretentiondays());
expiredDay.set(Calendar.HOUR_OF_DAY, 0);
expiredDay.set(Calendar.MINUTE, 0);
expiredDay.set(Calendar.SECOND, 0);
expiredDay.set(Calendar.MILLISECOND, 0);
Date clearBeforeTime = expiredDay.getTime();
// clean expired log
List<Long> logIds = null;
do {
logIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findClearLogIds(0, 0, clearBeforeTime, 0, 1000);
if (logIds!=null && logIds.size()>0) {
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().clearLog(logIds);
}
} while (logIds!=null && logIds.size()>0);
// update clean time
lastCleanLogTime = System.currentTimeMillis();
}
try {
TimeUnit.MINUTES.sleep(1);
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, job log report thread stop");
}
});
logrThread.setDaemon(true);
logrThread.setName("xxl-job, admin JobLogReportHelper");
logrThread.start();
}
public void toStop(){
toStop = true;
// interrupt and wait
logrThread.interrupt();
try {
logrThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,204 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobRegistry;
import com.xxl.job.core.biz.model.RegistryParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.RegistryConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.concurrent.*;
/**
* job registry instance
* @author xuxueli 2016-10-02 19:10:24
*/
public class JobRegistryHelper {
private static Logger logger = LoggerFactory.getLogger(JobRegistryHelper.class);
private static JobRegistryHelper instance = new JobRegistryHelper();
public static JobRegistryHelper getInstance(){
return instance;
}
private ThreadPoolExecutor registryOrRemoveThreadPool = null;
private Thread registryMonitorThread;
private volatile boolean toStop = false;
public void start(){
// for registry or remove
registryOrRemoveThreadPool = new ThreadPoolExecutor(
2,
10,
30L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobRegistryMonitorHelper-registryOrRemoveThreadPool-" + r.hashCode());
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
r.run();
logger.warn(">>>>>>>>>>> xxl-job, registry or remove too fast, match threadpool rejected handler(run now).");
}
});
// for monitor
registryMonitorThread = new Thread(new Runnable() {
@Override
public void run() {
while (!toStop) {
try {
// auto registry group
List<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
if (groupList!=null && !groupList.isEmpty()) {
// remove dead address (admin/executor)
List<Integer> ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findDead(RegistryConfig.DEAD_TIMEOUT, new Date());
if (ids!=null && ids.size()>0) {
XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(ids);
}
// fresh online address (admin/executor)
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
List<XxlJobRegistry> list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
if (list != null) {
for (XxlJobRegistry item: list) {
if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.get(appname);
if (registryList == null) {
registryList = new ArrayList<String>();
}
if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue());
}
appAddressMap.put(appname, registryList);
}
}
}
// fresh group address
for (XxlJobGroup group: groupList) {
List<String> registryList = appAddressMap.get(group.getAppname());
String addressListStr = null;
if (registryList!=null && !registryList.isEmpty()) {
Collections.sort(registryList);
StringBuilder addressListSB = new StringBuilder();
for (String item:registryList) {
addressListSB.append(item).append(",");
}
addressListStr = addressListSB.toString();
addressListStr = addressListStr.substring(0, addressListStr.length()-1);
}
group.setAddressList(addressListStr);
group.setUpdateTime(new Date());
XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
}
}
try {
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
} catch (InterruptedException e) {
if (!toStop) {
logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, job registry monitor thread stop");
}
});
registryMonitorThread.setDaemon(true);
registryMonitorThread.setName("xxl-job, admin JobRegistryMonitorHelper-registryMonitorThread");
registryMonitorThread.start();
}
public void toStop(){
toStop = true;
// stop registryOrRemoveThreadPool
registryOrRemoveThreadPool.shutdownNow();
// stop monitir (interrupt and wait)
registryMonitorThread.interrupt();
try {
registryMonitorThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
// ---------------------- helper ----------------------
public ReturnT<String> registry(RegistryParam registryParam) {
// valid
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
}
// async execute
registryOrRemoveThreadPool.execute(new Runnable() {
@Override
public void run() {
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
if (ret < 1) {
XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
// fresh
freshGroupRegistryInfo(registryParam);
}
}
});
return ReturnT.SUCCESS;
}
public ReturnT<String> registryRemove(RegistryParam registryParam) {
// valid
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
}
// async execute
registryOrRemoveThreadPool.execute(new Runnable() {
@Override
public void run() {
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
if (ret > 0) {
// fresh
freshGroupRegistryInfo(registryParam);
}
}
});
return ReturnT.SUCCESS;
}
private void freshGroupRegistryInfo(RegistryParam registryParam){
// Under consideration, prevent affecting core tables
}
}

View File

@ -0,0 +1,380 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.cron.CronExpression;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.scheduler.MisfireStrategyEnum;
import com.xxl.job.admin.core.scheduler.ScheduleTypeEnum;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* @author xuxueli 2019-05-21
*/
public class JobScheduleHelper {
private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
private static JobScheduleHelper instance = new JobScheduleHelper();
public static JobScheduleHelper getInstance(){
return instance;
}
public static final long PRE_READ_MS = 5000; // pre read
private Thread scheduleThread;
private Thread ringThread;
private volatile boolean scheduleThreadToStop = false;
private volatile boolean ringThreadToStop = false;
private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
public void start(){
// schedule thread
scheduleThread = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
} catch (InterruptedException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
logger.info(">>>>>>>>> init xxl-job admin scheduler success.");
// pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;
while (!scheduleThreadToStop) {
// Scan Job
long start = System.currentTimeMillis();
Connection conn = null;
Boolean connAutoCommit = null;
PreparedStatement preparedStatement = null;
boolean preReadSuc = true;
try {
conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
connAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
preparedStatement.execute();
// tx start
// 1pre read
long nowTime = System.currentTimeMillis();
List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
if (scheduleList!=null && scheduleList.size()>0) {
// 2push time-ring
for (XxlJobInfo jobInfo: scheduleList) {
// time-ring jump
if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
// 2.1trigger-expire > 5spass && make next-trigger-time
logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
// 1misfire match
MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);
if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) {
// FIRE_ONCE_NOW trigger
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);
logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
}
// 2fresh next
refreshNextValidTime(jobInfo, new Date());
} else if (nowTime > jobInfo.getTriggerNextTime()) {
// 2.2trigger-expire < 5sdirect-trigger && make next-trigger-time
// 1trigger
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
// 2fresh next
refreshNextValidTime(jobInfo, new Date());
// next-trigger-time in 5s, pre-read again
if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
// 1make ring second
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2push time ring
pushTimeRing(ringSecond, jobInfo.getId());
// 3fresh next
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
} else {
// 2.3trigger-pre-readtime-ring trigger && make next-trigger-time
// 1make ring second
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
// 2push time ring
pushTimeRing(ringSecond, jobInfo.getId());
// 3fresh next
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
}
// 3update trigger info
for (XxlJobInfo jobInfo: scheduleList) {
XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
}
} else {
preReadSuc = false;
}
// tx stop
} catch (Exception e) {
if (!scheduleThreadToStop) {
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
}
} finally {
// commit
if (conn != null) {
try {
conn.commit();
} catch (SQLException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
conn.setAutoCommit(connAutoCommit);
} catch (SQLException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
conn.close();
} catch (SQLException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
}
// close PreparedStatement
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
}
}
long cost = System.currentTimeMillis()-start;
// Wait seconds, align second
if (cost < 1000) { // scan-overtime, not wait
try {
// pre-read period: success > scan each second; fail > skip this period;
TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);
} catch (InterruptedException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
}
}
logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
}
});
scheduleThread.setDaemon(true);
scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
scheduleThread.start();
// ring thread
ringThread = new Thread(new Runnable() {
@Override
public void run() {
while (!ringThreadToStop) {
// align second
try {
TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
} catch (InterruptedException e) {
if (!ringThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
// second data
List<Integer> ringItemData = new ArrayList<>();
int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长跨过刻度向前校验一个刻度
for (int i = 0; i < 2; i++) {
List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
if (tmpData != null) {
ringItemData.addAll(tmpData);
}
}
// ring trigger
logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
if (ringItemData.size() > 0) {
// do trigger
for (int jobId: ringItemData) {
// do trigger
JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
}
// clear
ringItemData.clear();
}
} catch (Exception e) {
if (!ringThreadToStop) {
logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
}
});
ringThread.setDaemon(true);
ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
ringThread.start();
}
private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
try {
Date nextValidTime = generateNextValidTime(jobInfo, fromTime);
if (nextValidTime != null) {
jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
jobInfo.setTriggerNextTime(nextValidTime.getTime());
} else {
// generateNextValidTime fail, stop job
jobInfo.setTriggerStatus(0);
jobInfo.setTriggerLastTime(0);
jobInfo.setTriggerNextTime(0);
logger.error(">>>>>>>>>>> xxl-job, refreshNextValidTime fail for job: jobId={}, scheduleType={}, scheduleConf={}",
jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf());
}
} catch (Exception e) {
// generateNextValidTime error, stop job
jobInfo.setTriggerStatus(0);
jobInfo.setTriggerLastTime(0);
jobInfo.setTriggerNextTime(0);
logger.error(">>>>>>>>>>> xxl-job, refreshNextValidTime error for job: jobId={}, scheduleType={}, scheduleConf={}",
jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf(), e);
}
}
private void pushTimeRing(int ringSecond, int jobId){
// push async ring
List<Integer> ringItemData = ringData.get(ringSecond);
if (ringItemData == null) {
ringItemData = new ArrayList<Integer>();
ringData.put(ringSecond, ringItemData);
}
ringItemData.add(jobId);
logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData) );
}
public void toStop(){
// 1stop schedule
scheduleThreadToStop = true;
try {
TimeUnit.SECONDS.sleep(1); // wait
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
if (scheduleThread.getState() != Thread.State.TERMINATED){
// interrupt and wait
scheduleThread.interrupt();
try {
scheduleThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
// if has ring data
boolean hasRingData = false;
if (!ringData.isEmpty()) {
for (int second : ringData.keySet()) {
List<Integer> tmpData = ringData.get(second);
if (tmpData!=null && tmpData.size()>0) {
hasRingData = true;
break;
}
}
}
if (hasRingData) {
try {
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
// stop ring (wait job-in-memory stop)
ringThreadToStop = true;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
if (ringThread.getState() != Thread.State.TERMINATED){
// interrupt and wait
ringThread.interrupt();
try {
ringThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper stop");
}
// ---------------------- tools ----------------------
public static Date generateNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
if (ScheduleTypeEnum.CRON == scheduleTypeEnum) {
Date nextValidTime = new CronExpression(jobInfo.getScheduleConf()).getNextValidTimeAfter(fromTime);
return nextValidTime;
} else if (ScheduleTypeEnum.FIX_RATE == scheduleTypeEnum /*|| ScheduleTypeEnum.FIX_DELAY == scheduleTypeEnum*/) {
return new Date(fromTime.getTime() + Integer.valueOf(jobInfo.getScheduleConf())*1000 );
}
return null;
}
}

View File

@ -0,0 +1,150 @@
package com.xxl.job.admin.core.thread;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
import com.xxl.job.admin.core.trigger.XxlJobTrigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* job trigger thread pool helper
*
* @author xuxueli 2018-07-03 21:08:07
*/
public class JobTriggerPoolHelper {
private static Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class);
// ---------------------- trigger pool ----------------------
// fast/slow thread pool
private ThreadPoolExecutor fastTriggerPool = null;
private ThreadPoolExecutor slowTriggerPool = null;
public void start(){
fastTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());
}
});
slowTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());
}
});
}
public void stop() {
//triggerPool.shutdown();
fastTriggerPool.shutdownNow();
slowTriggerPool.shutdownNow();
logger.info(">>>>>>>>> xxl-job trigger thread pool shutdown success.");
}
// job timeout count
private volatile long minTim = System.currentTimeMillis()/60000; // ms > min
private volatile ConcurrentMap<Integer, AtomicInteger> jobTimeoutCountMap = new ConcurrentHashMap<>();
/**
* add trigger
*/
public void addTrigger(final int jobId,
final TriggerTypeEnum triggerType,
final int failRetryCount,
final String executorShardingParam,
final String executorParam,
final String addressList) {
// choose thread pool
ThreadPoolExecutor triggerPool_ = fastTriggerPool;
AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId);
if (jobTimeoutCount!=null && jobTimeoutCount.get() > 10) { // job-timeout 10 times in 1 min
triggerPool_ = slowTriggerPool;
}
// trigger
triggerPool_.execute(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
try {
// do trigger
XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
// check timeout-count-map
long minTim_now = System.currentTimeMillis()/60000;
if (minTim != minTim_now) {
minTim = minTim_now;
jobTimeoutCountMap.clear();
}
// incr timeout-count-map
long cost = System.currentTimeMillis()-start;
if (cost > 500) { // ob-timeout threshold 500ms
AtomicInteger timeoutCount = jobTimeoutCountMap.putIfAbsent(jobId, new AtomicInteger(1));
if (timeoutCount != null) {
timeoutCount.incrementAndGet();
}
}
}
}
});
}
// ---------------------- helper ----------------------
private static JobTriggerPoolHelper helper = new JobTriggerPoolHelper();
public static void toStart() {
helper.start();
}
public static void toStop() {
helper.stop();
}
/**
* @param jobId
* @param triggerType
* @param failRetryCount
* >=0: use this param
* <0: use param from job info config
* @param executorShardingParam
* @param executorParam
* null: use job param
* not null: cover job param
*/
public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam, String addressList) {
helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
}
}

View File

@ -0,0 +1,27 @@
package com.xxl.job.admin.core.trigger;
import com.xxl.job.admin.core.util.I18nUtil;
/**
* trigger type enum
*
* @author xuxueli 2018-09-16 04:56:41
*/
public enum TriggerTypeEnum {
MANUAL(I18nUtil.getString("jobconf_trigger_type_manual")),
CRON(I18nUtil.getString("jobconf_trigger_type_cron")),
RETRY(I18nUtil.getString("jobconf_trigger_type_retry")),
PARENT(I18nUtil.getString("jobconf_trigger_type_parent")),
API(I18nUtil.getString("jobconf_trigger_type_api")),
MISFIRE(I18nUtil.getString("jobconf_trigger_type_misfire"));
private TriggerTypeEnum(String title){
this.title = title;
}
private String title;
public String getTitle() {
return title;
}
}

View File

@ -0,0 +1,226 @@
package com.xxl.job.admin.core.trigger;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.util.IpUtil;
import com.xxl.job.core.util.ThrowableUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
/**
* xxl-job trigger
* Created by xuxueli on 17/7/13.
*/
public class XxlJobTrigger {
private static Logger logger = LoggerFactory.getLogger(XxlJobTrigger.class);
/**
* trigger job
*
* @param jobId
* @param triggerType
* @param failRetryCount
* >=0: use this param
* <0: use param from job info config
* @param executorShardingParam
* @param executorParam
* null: use job param
* not null: cover job param
* @param addressList
* null: use executor addressList
* not null: cover
*/
public static void trigger(int jobId,
TriggerTypeEnum triggerType,
int failRetryCount,
String executorShardingParam,
String executorParam,
String addressList) {
// load data
XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
if (jobInfo == null) {
logger.warn(">>>>>>>>>>>> trigger fail, jobId invalidjobId={}", jobId);
return;
}
if (executorParam != null) {
jobInfo.setExecutorParam(executorParam);
}
int finalFailRetryCount = failRetryCount>=0?failRetryCount:jobInfo.getExecutorFailRetryCount();
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());
// cover addressList
if (addressList!=null && addressList.trim().length()>0) {
group.setAddressType(1);
group.setAddressList(addressList.trim());
}
// sharding param
int[] shardingParam = null;
if (executorShardingParam!=null){
String[] shardingArr = executorShardingParam.split("/");
if (shardingArr.length==2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
shardingParam = new int[2];
shardingParam[0] = Integer.valueOf(shardingArr[0]);
shardingParam[1] = Integer.valueOf(shardingArr[1]);
}
}
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
&& group.getRegistryList()!=null && !group.getRegistryList().isEmpty()
&& shardingParam==null) {
for (int i = 0; i < group.getRegistryList().size(); i++) {
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());
}
} else {
if (shardingParam == null) {
shardingParam = new int[]{0, 1};
}
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
}
}
private static boolean isNumeric(String str){
try {
int result = Integer.valueOf(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
* @param group job group, registry list may be empty
* @param jobInfo
* @param finalFailRetryCount
* @param triggerType
* @param index sharding index
* @param total sharding index
*/
private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){
// param
ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION); // block strategy
ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null); // route strategy
String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==executorRouteStrategyEnum)?String.valueOf(index).concat("/").concat(String.valueOf(total)):null;
// 1save log-id
XxlJobLog jobLog = new XxlJobLog();
jobLog.setJobGroup(jobInfo.getJobGroup());
jobLog.setJobId(jobInfo.getId());
jobLog.setTriggerTime(new Date());
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog);
logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());
// 2init trigger-param
TriggerParam triggerParam = new TriggerParam();
triggerParam.setJobId(jobInfo.getId());
triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
triggerParam.setExecutorParams(jobInfo.getExecutorParam());
triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
triggerParam.setLogId(jobLog.getId());
triggerParam.setLogDateTime(jobLog.getTriggerTime().getTime());
triggerParam.setGlueType(jobInfo.getGlueType());
triggerParam.setGlueSource(jobInfo.getGlueSource());
triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
triggerParam.setBroadcastIndex(index);
triggerParam.setBroadcastTotal(total);
// 3init address
String address = null;
ReturnT<String> routeAddressResult = null;
if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) {
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
if (index < group.getRegistryList().size()) {
address = group.getRegistryList().get(index);
} else {
address = group.getRegistryList().get(0);
}
} else {
routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
address = routeAddressResult.getContent();
}
}
} else {
routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
}
// 4trigger remote executor
ReturnT<String> triggerResult = null;
if (address != null) {
triggerResult = runExecutor(triggerParam, address);
} else {
triggerResult = new ReturnT<String>(ReturnT.FAIL_CODE, null);
}
// 5collection trigger info
StringBuffer triggerMsgSb = new StringBuffer();
triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append("").append(triggerType.getTitle());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append("").append(IpUtil.getIp());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append("")
.append( (group.getAddressType() == 0)?I18nUtil.getString("jobgroup_field_addressType_0"):I18nUtil.getString("jobgroup_field_addressType_1") );
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append("").append(group.getRegistryList());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append("").append(executorRouteStrategyEnum.getTitle());
if (shardingParam != null) {
triggerMsgSb.append("("+shardingParam+")");
}
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append("").append(blockStrategy.getTitle());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_timeout")).append("").append(jobInfo.getExecutorTimeout());
triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append("").append(finalFailRetryCount);
triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_run") +"<<<<<<<<<<< </span><br>")
.append((routeAddressResult!=null&&routeAddressResult.getMsg()!=null)?routeAddressResult.getMsg()+"<br><br>":"").append(triggerResult.getMsg()!=null?triggerResult.getMsg():"");
// 6save log trigger-info
jobLog.setExecutorAddress(address);
jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
jobLog.setExecutorParam(jobInfo.getExecutorParam());
jobLog.setExecutorShardingParam(shardingParam);
jobLog.setExecutorFailRetryCount(finalFailRetryCount);
//jobLog.setTriggerTime();
jobLog.setTriggerCode(triggerResult.getCode());
jobLog.setTriggerMsg(triggerMsgSb.toString());
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog);
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
}
/**
* run executor
* @param triggerParam
* @param address
* @return
*/
public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
ReturnT<String> runResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
}
StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + "");
runResultSB.append("<br>address").append(address);
runResultSB.append("<br>code").append(runResult.getCode());
runResultSB.append("<br>msg").append(runResult.getMsg());
runResult.setMsg(runResultSB.toString());
return runResult;
}
}

View File

@ -0,0 +1,98 @@
package com.xxl.job.admin.core.util;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Cookie.Util
*
* @author xuxueli 2015-12-12 18:01:06
*/
public class CookieUtil {
// 默认缓存时间,单位/, 2H
private static final int COOKIE_MAX_AGE = Integer.MAX_VALUE;
// 保存路径,根路径
private static final String COOKIE_PATH = "/";
/**
* 保存
*
* @param response
* @param key
* @param value
* @param ifRemember
*/
public static void set(HttpServletResponse response, String key, String value, boolean ifRemember) {
int age = ifRemember?COOKIE_MAX_AGE:-1;
set(response, key, value, null, COOKIE_PATH, age, true);
}
/**
* 保存
*
* @param response
* @param key
* @param value
* @param maxAge
*/
private static void set(HttpServletResponse response, String key, String value, String domain, String path, int maxAge, boolean isHttpOnly) {
Cookie cookie = new Cookie(key, value);
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setPath(path);
cookie.setMaxAge(maxAge);
cookie.setHttpOnly(isHttpOnly);
response.addCookie(cookie);
}
/**
* 查询value
*
* @param request
* @param key
* @return
*/
public static String getValue(HttpServletRequest request, String key) {
Cookie cookie = get(request, key);
if (cookie != null) {
return cookie.getValue();
}
return null;
}
/**
* 查询Cookie
*
* @param request
* @param key
*/
private static Cookie get(HttpServletRequest request, String key) {
Cookie[] arr_cookie = request.getCookies();
if (arr_cookie != null && arr_cookie.length > 0) {
for (Cookie cookie : arr_cookie) {
if (cookie.getName().equals(key)) {
return cookie;
}
}
}
return null;
}
/**
* 删除Cookie
*
* @param request
* @param response
* @param key
*/
public static void remove(HttpServletRequest request, HttpServletResponse response, String key) {
Cookie cookie = get(request, key);
if (cookie != null) {
set(response, key, "", null, COOKIE_PATH, 0, true);
}
}
}

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