xieshengjie пре 2 година
комит
fe374c76db
100 измењених фајлова са 12405 додато и 0 уклоњено
  1. 7 0
      .gitignore
  2. 674 0
      LICENSE
  3. 32 0
      NOTICE
  4. 596 0
      README.md
  5. 1247 0
      doc/XXL-JOB-English-Documentation.md
  6. 2203 0
      doc/XXL-JOB官方文档.md
  7. BIN
      doc/XXL-JOB架构图.pptx
  8. 281 0
      doc/db/tables_xxl_job_postgres.sql
  9. BIN
      doc/images/cnblog-首页-每日一博-第一.png
  10. BIN
      doc/images/cnblog-首页-热门动弹-第一.png
  11. BIN
      doc/images/donate-alipay.jpg
  12. BIN
      doc/images/donate-paypal.png
  13. BIN
      doc/images/donate-wechat.png
  14. BIN
      doc/images/gitee-gvp.jpg
  15. BIN
      doc/images/img_1001.png
  16. BIN
      doc/images/img_1002.png
  17. BIN
      doc/images/img_6yC0.png
  18. BIN
      doc/images/img_BPLG.png
  19. BIN
      doc/images/img_EB65.png
  20. BIN
      doc/images/img_Fgql.png
  21. BIN
      doc/images/img_Hr2T.png
  22. BIN
      doc/images/img_Qohm.png
  23. BIN
      doc/images/img_UDSo.png
  24. BIN
      doc/images/img_V3vF.png
  25. BIN
      doc/images/img_Wb2o.png
  26. BIN
      doc/images/img_Ypik.png
  27. BIN
      doc/images/img_Z9Qr.png
  28. BIN
      doc/images/img_ZAhX.png
  29. BIN
      doc/images/img_ZAsz.png
  30. BIN
      doc/images/img_dNUJ.png
  31. BIN
      doc/images/img_eYrv.png
  32. BIN
      doc/images/img_hIci.png
  33. BIN
      doc/images/img_iUw0.png
  34. BIN
      doc/images/img_inc8.png
  35. BIN
      doc/images/img_jOAU.png
  36. BIN
      doc/images/img_jrdI.png
  37. BIN
      doc/images/img_o8HQ.png
  38. BIN
      doc/images/img_tJOq.png
  39. BIN
      doc/images/img_tvGI.png
  40. BIN
      doc/images/qq群-一个xxl同学进了58.png
  41. BIN
      doc/images/xxl-logo.jpg
  42. BIN
      doc/images/xxl-logo.png
  43. 73 0
      docker-compose/docker-compose.yml
  44. 9 0
      docker-compose/postgres/init.d/init-xxl-job-db.sh
  45. 445 0
      docker-compose/postgres/sql/xxl_job_postgres.sql
  46. 145 0
      pom.xml
  47. 11 0
      xxl-job-admin/Dockerfile
  48. 116 0
      xxl-job-admin/pom.xml
  49. 16 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/XxlJobAdminApplication.java
  50. 96 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java
  51. 72 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
  52. 96 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobCodeController.java
  53. 197 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java
  54. 183 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobInfoController.java
  55. 233 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
  56. 179 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/UserController.java
  57. 29 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/annotation/PermissionLimit.java
  58. 43 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java
  59. 59 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java
  60. 28 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/WebMvcConfig.java
  61. 66 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/controller/resolver/WebExceptionResolver.java
  62. 20 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarm.java
  63. 65 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarmer.java
  64. 117 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/impl/EmailJobAlarm.java
  65. 99 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/complete/XxlJobCompleter.java
  66. 158 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
  67. 1666 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
  68. 14 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java
  69. 77 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java
  70. 237 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
  71. 157 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java
  72. 77 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java
  73. 54 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java
  74. 55 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java
  75. 73 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java
  76. 32 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
  77. 413 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
  78. 58 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
  79. 48 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java
  80. 24 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java
  81. 48 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
  82. 85 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java
  83. 48 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
  84. 19 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java
  85. 79 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java
  86. 76 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java
  87. 19 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java
  88. 23 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java
  89. 46 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java
  90. 39 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java
  91. 46 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java
  92. 101 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java
  93. 184 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java
  94. 110 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java
  95. 152 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java
  96. 204 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java
  97. 369 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
  98. 150 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java
  99. 27 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java
  100. 0 0
      xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+.idea
+.classpath
+.project
+*.iml
+target/
+.DS_Store
+.gitattributes

+ 674 - 0
LICENSE

@@ -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 - 0
NOTICE

@@ -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
+
+Hessian:
+
+  * LICENSE:
+    * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0)
+  * HOMEPAGE:
+    * http://hessian.caucho.com
+
+SLF4J:
+
+  * LICENSE:
+    * http://www.apache.org/licenses/LICENSE-2.0 (Apache License 2.0)
+  * HOMEPAGE:
+    * http://www.slf4j.org

+ 596 - 0
README.md

@@ -0,0 +1,596 @@
+<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是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。
+
+
+## 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、命令行任务:原生提供通用命令行任务Handler(Bean任务,"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, 随后我将之发布到OSCHINA,XXL-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-11,XXL-JOB有幸参会《[InfoQ ArchSummit全球架构师峰会](http://bj2017.archsummit.com/)》,并被拍拍贷架构总监"杨波老师"在专题 "[微服务原理、基础架构和开源实践](http://bj2017.archsummit.com/training/2)" 中现场介绍。
+
+于2017-12-18,XXL-JOB参与"[2017年度最受欢迎中国开源软件](http://www.oschina.net/project/top_cn_2017?sort=1)"评比,在当时已录入的约九千个国产开源项目中角逐,最终进入了前30强。
+
+于2018-01-15,XXL-JOB参与"[2017码云最火开源项目](https://www.oschina.net/news/92438/2017-mayun-top-50)"评比,在当时已录入的约六千五百个码云项目中角逐,最终进去了前20强。
+
+于2018-04-14,iTechPlus在上海举办的 "[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-05,XXL-JOB参与"[2018年度最受欢迎中国开源软件](https://www.oschina.net/project/top_cn_2018?sort=1)"评比,在当时已录入的一万多个开源项目中角逐,最终排名第19名。
+
+于2019-12-10,XXL-JOB参与"[2019年度最受欢迎中国开源软件](https://www.oschina.net/project/top_cn_2019)"评比,在当时已录入的一万多个开源项目中角逐,最终排名"开发框架和基础组件类"第9名。
+
+于2020-11-16,XXL-JOB参与"[2020年度最受欢迎中国开源软件](https://www.oschina.net/project/top_cn_2020)"评比,在当时已录入的一万多个开源项目中角逐,最终排名"开发框架和基础组件类"第8名。
+
+> 我司大众点评目前已接入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、爱动超越人工智能科技(北京)有限责任公司
+    - ……
+
+> 更多接入的公司,欢迎在 [登记地址](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.
+
+产品开源免费,并且将持续提供免费的社区技术支持。个人或企业内部可自由的接入和使用。
+
+
+## Donate
+No matter how much the donation amount is enough to express your thought, thank you very much :)     [To donate](https://www.xuxueli.com/page/donate.html )
+
+无论捐赠金额多少都足够表达您这份心意,非常感谢 :)      [前往捐赠](https://www.xuxueli.com/page/donate.html )

Разлика између датотеке није приказан због своје велике величине
+ 1247 - 0
doc/XXL-JOB-English-Documentation.md


Разлика између датотеке није приказан због своје велике величине
+ 2203 - 0
doc/XXL-JOB官方文档.md


BIN
doc/XXL-JOB架构图.pptx


+ 281 - 0
doc/db/tables_xxl_job_postgres.sql

@@ -0,0 +1,281 @@
+CREATE SEQUENCE "xxl_job_group_id_seq"
+INCREMENT 1
+MINVALUE  1
+MAXVALUE 2147483647
+START 1
+CACHE 1;
+
+SELECT setval('"xxl_job_group_id_seq"', 2, true);
+
+CREATE SEQUENCE "xxl_job_info_id_seq"
+INCREMENT 1
+MINVALUE  1
+MAXVALUE 2147483647
+START 1
+CACHE 1;
+
+
+
+
+CREATE SEQUENCE "xxl_job_log_id_seq"
+INCREMENT 1
+MINVALUE  1
+MAXVALUE 2147483647
+START 1
+CACHE 1;
+
+
+
+
+CREATE SEQUENCE "xxl_job_logglue_id_seq"
+INCREMENT 1
+MINVALUE  1
+MAXVALUE 2147483647
+START 1
+CACHE 1;
+
+
+
+
+CREATE SEQUENCE "xxl_job_log_report_id_seq"
+INCREMENT 1
+MINVALUE  1
+MAXVALUE 2147483647
+START 1
+CACHE 1;
+
+
+
+
+CREATE SEQUENCE "xxl_job_registry_id_seq"
+INCREMENT 1
+MINVALUE  1
+MAXVALUE 2147483647
+START 1
+CACHE 1;
+
+
+
+
+CREATE SEQUENCE "xxl_job_user_id_seq"
+INCREMENT 1
+MINVALUE  1
+MAXVALUE 2147483647
+START 1
+CACHE 1;
+
+SELECT setval('"xxl_job_user_id_seq"', 2, true);
+
+CREATE TABLE "xxl_job_group" (
+  "id" int4 NOT NULL DEFAULT nextval('xxl_job_group_id_seq'::regclass),
+  "app_name" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
+  "title" varchar(12) COLLATE "pg_catalog"."default" NOT NULL,
+  "address_type" int2 NOT NULL,
+  "address_list" text COLLATE "pg_catalog"."default",
+  "update_time" timestamp(6)
+)
+;
+COMMENT ON COLUMN "xxl_job_group"."app_name" IS '执行器AppName';
+COMMENT ON COLUMN "xxl_job_group"."title" IS '执行器名称';
+COMMENT ON COLUMN "xxl_job_group"."address_type" IS '执行器地址类型:0=自动注册、1=手动录入';
+COMMENT ON COLUMN "xxl_job_group"."address_list" IS '执行器地址列表,多地址逗号分隔';
+
+
+CREATE TABLE "xxl_job_info" (
+  "id" int4 NOT NULL DEFAULT nextval('xxl_job_info_id_seq'::regclass),
+  "job_group" int4 NOT NULL,
+  "job_desc" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
+  "add_time" timestamp(6),
+  "update_time" timestamp(6),
+  "author" varchar(64) COLLATE "pg_catalog"."default",
+  "alarm_email" varchar(255) COLLATE "pg_catalog"."default",
+  "schedule_type" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+  "schedule_conf" varchar(128) COLLATE "pg_catalog"."default",
+  "misfire_strategy" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+  "executor_route_strategy" varchar(50) COLLATE "pg_catalog"."default",
+  "executor_handler" varchar(255) COLLATE "pg_catalog"."default",
+  "executor_param" varchar(512) COLLATE "pg_catalog"."default",
+  "executor_block_strategy" varchar(50) COLLATE "pg_catalog"."default",
+  "executor_timeout" int4 NOT NULL,
+  "executor_fail_retry_count" int4 NOT NULL,
+  "glue_type" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+  "glue_source" text COLLATE "pg_catalog"."default",
+  "glue_remark" varchar(128) COLLATE "pg_catalog"."default",
+  "glue_updatetime" timestamp(6),
+  "child_jobid" varchar(255) COLLATE "pg_catalog"."default",
+  "trigger_status" int2 NOT NULL,
+  "trigger_last_time" int8 NOT NULL,
+  "trigger_next_time" int8 NOT NULL
+)
+;
+COMMENT ON COLUMN "xxl_job_info"."job_group" IS '执行器主键ID';
+COMMENT ON COLUMN "xxl_job_info"."author" IS '作者';
+COMMENT ON COLUMN "xxl_job_info"."alarm_email" IS '报警邮件';
+COMMENT ON COLUMN "xxl_job_info"."schedule_type" IS '调度类型';
+COMMENT ON COLUMN "xxl_job_info"."schedule_conf" IS '调度配置,值含义取决于调度类型';
+COMMENT ON COLUMN "xxl_job_info"."misfire_strategy" IS '调度过期策略';
+COMMENT ON COLUMN "xxl_job_info"."executor_route_strategy" IS '执行器路由策略';
+COMMENT ON COLUMN "xxl_job_info"."executor_handler" IS '执行器任务handler';
+COMMENT ON COLUMN "xxl_job_info"."executor_param" IS '执行器任务参数';
+COMMENT ON COLUMN "xxl_job_info"."executor_block_strategy" IS '阻塞处理策略';
+COMMENT ON COLUMN "xxl_job_info"."executor_timeout" IS '任务执行超时时间,单位秒';
+COMMENT ON COLUMN "xxl_job_info"."executor_fail_retry_count" IS '失败重试次数';
+COMMENT ON COLUMN "xxl_job_info"."glue_type" IS 'GLUE类型';
+COMMENT ON COLUMN "xxl_job_info"."glue_source" IS 'GLUE源代码';
+COMMENT ON COLUMN "xxl_job_info"."glue_remark" IS 'GLUE备注';
+COMMENT ON COLUMN "xxl_job_info"."glue_updatetime" IS 'GLUE更新时间';
+COMMENT ON COLUMN "xxl_job_info"."child_jobid" IS '子任务ID,多个逗号分隔';
+COMMENT ON COLUMN "xxl_job_info"."trigger_status" IS '调度状态:0-停止,1-运行';
+COMMENT ON COLUMN "xxl_job_info"."trigger_last_time" IS '上次调度时间';
+COMMENT ON COLUMN "xxl_job_info"."trigger_next_time" IS '下次调度时间';
+
+
+CREATE TABLE "xxl_job_lock" (
+  "lock_name" varchar(50) COLLATE "pg_catalog"."default" NOT NULL
+)
+;
+COMMENT ON COLUMN "xxl_job_lock"."lock_name" IS '锁名称';
+
+
+CREATE TABLE "xxl_job_log" (
+  "id" int4 NOT NULL DEFAULT nextval('xxl_job_log_id_seq'::regclass),
+  "job_group" int4 NOT NULL,
+  "job_id" int4 NOT NULL,
+  "executor_address" varchar(255) COLLATE "pg_catalog"."default",
+  "executor_handler" varchar(255) COLLATE "pg_catalog"."default",
+  "executor_param" varchar(512) COLLATE "pg_catalog"."default",
+  "executor_sharding_param" varchar(20) COLLATE "pg_catalog"."default",
+  "executor_fail_retry_count" int4 NOT NULL DEFAULT 0,
+  "trigger_time" timestamp(6),
+  "trigger_code" int4 NOT NULL,
+  "trigger_msg" text COLLATE "pg_catalog"."default",
+  "handle_time" timestamp(6),
+  "handle_code" int4 NOT NULL,
+  "handle_msg" text COLLATE "pg_catalog"."default",
+  "alarm_status" int2 NOT NULL DEFAULT 0
+)
+;
+COMMENT ON COLUMN "xxl_job_log"."job_group" IS '执行器主键ID';
+COMMENT ON COLUMN "xxl_job_log"."job_id" IS '任务,主键ID';
+COMMENT ON COLUMN "xxl_job_log"."executor_address" IS '执行器地址,本次执行的地址';
+COMMENT ON COLUMN "xxl_job_log"."executor_handler" IS '执行器任务handler';
+COMMENT ON COLUMN "xxl_job_log"."executor_param" IS '执行器任务参数';
+COMMENT ON COLUMN "xxl_job_log"."executor_sharding_param" IS '执行器任务分片参数,格式如 1/2';
+COMMENT ON COLUMN "xxl_job_log"."executor_fail_retry_count" IS '失败重试次数';
+COMMENT ON COLUMN "xxl_job_log"."trigger_time" IS '调度-时间';
+COMMENT ON COLUMN "xxl_job_log"."trigger_code" IS '调度-结果';
+COMMENT ON COLUMN "xxl_job_log"."trigger_msg" IS '调度-日志';
+COMMENT ON COLUMN "xxl_job_log"."handle_time" IS '执行-时间';
+COMMENT ON COLUMN "xxl_job_log"."handle_code" IS '执行-状态';
+COMMENT ON COLUMN "xxl_job_log"."handle_msg" IS '执行-日志';
+COMMENT ON COLUMN "xxl_job_log"."alarm_status" IS '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败';
+
+
+CREATE TABLE "xxl_job_log_report" (
+  "id" int4 NOT NULL DEFAULT nextval('xxl_job_log_report_id_seq'::regclass),
+  "trigger_day" timestamp(6),
+  "running_count" int4 NOT NULL,
+  "suc_count" int4 NOT NULL,
+  "fail_count" int4 NOT NULL,
+  "update_time" timestamp(6)
+)
+;
+COMMENT ON COLUMN "xxl_job_log_report"."trigger_day" IS '调度-时间';
+COMMENT ON COLUMN "xxl_job_log_report"."running_count" IS '运行中-日志数量';
+COMMENT ON COLUMN "xxl_job_log_report"."suc_count" IS '执行成功-日志数量';
+COMMENT ON COLUMN "xxl_job_log_report"."fail_count" IS '执行失败-日志数量';
+
+
+CREATE TABLE "xxl_job_logglue" (
+  "id" int4 NOT NULL DEFAULT nextval('xxl_job_logglue_id_seq'::regclass),
+  "job_id" int4 NOT NULL,
+  "glue_type" varchar(50) COLLATE "pg_catalog"."default",
+  "glue_source" text COLLATE "pg_catalog"."default",
+  "glue_remark" varchar(128) COLLATE "pg_catalog"."default" NOT NULL,
+  "add_time" timestamp(6),
+  "update_time" timestamp(6)
+)
+;
+COMMENT ON COLUMN "xxl_job_logglue"."job_id" IS '任务,主键ID';
+COMMENT ON COLUMN "xxl_job_logglue"."glue_type" IS 'GLUE类型';
+COMMENT ON COLUMN "xxl_job_logglue"."glue_source" IS 'GLUE源代码';
+COMMENT ON COLUMN "xxl_job_logglue"."glue_remark" IS 'GLUE备注';
+
+
+CREATE TABLE "xxl_job_registry" (
+  "id" int4 NOT NULL DEFAULT nextval('xxl_job_registry_id_seq'::regclass),
+  "registry_group" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+  "registry_key" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
+  "registry_value" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
+  "update_time" timestamp(6)
+)
+;
+
+
+CREATE TABLE "xxl_job_user" (
+  "id" int4 NOT NULL DEFAULT nextval('xxl_job_user_id_seq'::regclass),
+  "username" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+  "password" varchar(50) COLLATE "pg_catalog"."default" NOT NULL,
+  "role" int2 NOT NULL,
+  "permission" varchar(255) COLLATE "pg_catalog"."default"
+)
+;
+COMMENT ON COLUMN "xxl_job_user"."username" IS '账号';
+COMMENT ON COLUMN "xxl_job_user"."password" IS '密码';
+COMMENT ON COLUMN "xxl_job_user"."role" IS '角色:0-普通用户、1-管理员';
+COMMENT ON COLUMN "xxl_job_user"."permission" IS '权限:执行器ID列表,多个逗号分割';
+
+
+ALTER TABLE "xxl_job_group" ADD CONSTRAINT "xxl_job_group_pkey" PRIMARY KEY ("id");
+
+
+ALTER TABLE "xxl_job_info" ADD CONSTRAINT "xxl_job_info_pkey" PRIMARY KEY ("id");
+
+
+ALTER TABLE "xxl_job_lock" ADD CONSTRAINT "xxl_job_lock_pkey" PRIMARY KEY ("lock_name");
+
+
+CREATE INDEX "I_handle_code" ON "xxl_job_log" USING btree (
+  "handle_code" "pg_catalog"."int4_ops" ASC NULLS LAST
+);
+CREATE INDEX "I_trigger_time" ON "xxl_job_log" USING btree (
+  "trigger_time" "pg_catalog"."timestamp_ops" ASC NULLS LAST
+);
+
+
+ALTER TABLE "xxl_job_log" ADD CONSTRAINT "xxl_job_log_pkey" PRIMARY KEY ("id");
+
+
+CREATE INDEX "i_trigger_day" ON "xxl_job_log_report" USING btree (
+  "trigger_day" "pg_catalog"."timestamp_ops" ASC NULLS LAST
+);
+
+
+ALTER TABLE "xxl_job_log_report" ADD CONSTRAINT "xxl_job_log_report_pkey" PRIMARY KEY ("id");
+
+
+ALTER TABLE "xxl_job_logglue" ADD CONSTRAINT "xxl_job_logglue_pkey" PRIMARY KEY ("id");
+
+
+CREATE INDEX "i_g_k_v" ON "xxl_job_registry" USING btree (
+  "registry_group" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST,
+  "registry_key" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST,
+  "registry_value" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
+);
+
+
+ALTER TABLE "xxl_job_registry" ADD CONSTRAINT "xxl_job_registry_pkey" PRIMARY KEY ("id");
+
+
+CREATE INDEX "i_username" ON "xxl_job_user" USING btree (
+  "username" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
+);
+
+ALTER TABLE "xxl_job_user" ADD CONSTRAINT "xxl_job_user_pkey" PRIMARY KEY ("id");
+
+
+
+
+INSERT INTO "xxl_job_user" ("id", "username", "password", "role", "permission") VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO "xxl_job_group" ("id", "app_name", "title", "address_type", "address_list", "update_time") VALUES (1, 'xxl-job-executor', '执行器-测试组', 0, NULL, '2022-06-05 22:21:31');
+INSERT INTO "xxl_job_lock" ("lock_name") VALUES ('schedule_lock');

BIN
doc/images/cnblog-首页-每日一博-第一.png


BIN
doc/images/cnblog-首页-热门动弹-第一.png


BIN
doc/images/donate-alipay.jpg


BIN
doc/images/donate-paypal.png


BIN
doc/images/donate-wechat.png


BIN
doc/images/gitee-gvp.jpg


BIN
doc/images/img_1001.png


BIN
doc/images/img_1002.png


BIN
doc/images/img_6yC0.png


BIN
doc/images/img_BPLG.png


BIN
doc/images/img_EB65.png


BIN
doc/images/img_Fgql.png


BIN
doc/images/img_Hr2T.png


BIN
doc/images/img_Qohm.png


BIN
doc/images/img_UDSo.png


BIN
doc/images/img_V3vF.png


BIN
doc/images/img_Wb2o.png


BIN
doc/images/img_Ypik.png


BIN
doc/images/img_Z9Qr.png


BIN
doc/images/img_ZAhX.png


BIN
doc/images/img_ZAsz.png


BIN
doc/images/img_dNUJ.png


BIN
doc/images/img_eYrv.png


BIN
doc/images/img_hIci.png


BIN
doc/images/img_iUw0.png


BIN
doc/images/img_inc8.png


BIN
doc/images/img_jOAU.png


BIN
doc/images/img_jrdI.png


BIN
doc/images/img_o8HQ.png


BIN
doc/images/img_tJOq.png


BIN
doc/images/img_tvGI.png


BIN
doc/images/qq群-一个xxl同学进了58.png


BIN
doc/images/xxl-logo.jpg


BIN
doc/images/xxl-logo.png


+ 73 - 0
docker-compose/docker-compose.yml

@@ -0,0 +1,73 @@
+version: '2.4'
+
+services:
+  postgres: # 数据库连接:jdbc:postgresql://{ip}:5432/{dbname} 用户名:postgres 密码:heliang
+    image: postgres:12.10
+    restart: always
+    environment:
+      POSTGRES_USER: postgres
+      POSTGRES_PASSWORD: heliang
+      POSTGRES_DB: postgres
+      PGDATA: /var/lib/postgresql/data/pgdata
+      LANG: C.UTF-8
+      TZ: Asia/Shanghai
+    volumes:
+      - ./postgres/data/:/var/lib/postgresql/data/pgdata/:rw
+      - ./postgres/init.d/:/docker-entrypoint-initdb.d/:rw
+      - ./postgres/sql/:/sql/:rw
+      - ./postgres/upgrade/:/upgrade/:rw
+    networks:
+      - mobe-network
+    privileged: true
+  xxl-job: #分布式任务调度 http://{ip}:8866/xxl-job-admin 后台登录用户名:admin 密码:123456
+    image: heliang230/xxl-job-admin:2.3.0
+    restart: always
+    environment:
+      APPLICATION_PORT: 8866 #调度中心web界面访问端口
+      SERVER_SERVLET_CONTEXTPATH: /xxl-job-admin #web上下文
+      POSTGRES_SERVICE_HOST: postgres #数据库
+      POSTGRES_SERVICE_PORT: 5432 #数据库端口
+      POSTGRES_SERVICE_DB_NAME: xxl_job #数据库名称
+      POSTGRES_SERVICE_USER: postgres #数据库用户名
+      POSTGRES_SERVICE_PASSWORD: heliang #数据库密码
+      SERVICE_ACCESSTOKEN: d1bacd94024ed228 #调度中心与执行器服务认证凭证
+      JAVA_OPTS: -Xmx512m
+    volumes:
+      - ./xxl-job/data/:/data/:rw
+    ports:
+      - 8866:8866
+    networks:
+      - mobe-network
+    privileged: true
+    depends_on:
+      - postgres
+  xxl-job-executor: #执行器
+    image: heliang230/xxl-job-executor:2.3.2
+    restart: always
+    environment:
+      SERVER_PORT: 8080 #执行器API端口
+      SPRING_MAIN_WEB_ENVIRONMENT: "true" #是否开启web模式
+      XXL_JOB_ADMIN_ADDRESSES: http://xxl-job:8866/xxl-job-admin #调度中心地址
+      XXL_JOB_ACCESSTOKEN: d1bacd94024ed228 #执行器与调度中心token凭证
+      XXL_JOB_EXECUTOR_APPNAME: xxl-job-executor #执行器应用名称
+      XXL_JOB_EXECUTOR_ADDRESS: http://xxl-job-executor:9999/ #注册到调度中心的执行器地址信息
+      XXL_JOB_EXECUTOR_IP: xxl-job-executor #执行器ip地址
+      XXL_JOB_EXECUTOR_PORT: 9999 #执行器端口
+      XXL_JOB_EXECUTOR_LOGPATH: /data/applogs/xxl-job/jobhandler #日志存储路径
+      XXL_JOB_EXECUTOR_LOGRETENTIONDAYS: 30 #存储日志时间
+      JAVA_OPTS: -Xmx512m
+      SLEEP: 5 #启动容器后,休眠5秒中,为了防止报错
+    volumes:
+      - ./xxl-job-executor/data/:/data/:rw
+    ports:
+      - 8080:8080
+    networks:
+      - mobe-network
+    privileged: true
+    depends_on:
+      - postgres
+      - xxl-job
+networks:
+  mobe-network:
+
+

+ 9 - 0
docker-compose/postgres/init.d/init-xxl-job-db.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+
+
+psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER"  <<-EOSQL
+	CREATE DATABASE xxl_job;
+	\c xxl_job;
+	\i /sql/xxl_job_postgres.sql;
+EOSQL

Разлика између датотеке није приказан због своје велике величине
+ 445 - 0
docker-compose/postgres/sql/xxl_job_postgres.sql


+ 145 - 0
pom.xml

@@ -0,0 +1,145 @@
+<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.3.0</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-all.version>4.1.63.Final</netty-all.version>
+		<gson.version>2.9.0</gson.version>
+
+		<spring.version>5.3.20</spring.version>
+		<spring-boot.version>2.6.7</spring-boot.version>
+
+		<mybatis-spring-boot-starter.version>2.2.2</mybatis-spring-boot-starter.version>
+		<mysql-connector-java.version>8.0.29</mysql-connector-java.version>
+
+		<slf4j-api.version>1.7.36</slf4j-api.version>
+		<junit.version>5.8.2</junit.version>
+		<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
+
+		<groovy.version>3.0.10</groovy.version>
+
+		<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
+		<maven-javadoc-plugin.version>3.2.0</maven-javadoc-plugin.version>
+		<maven-gpg-plugin.version>1.6</maven-gpg-plugin.version>
+		<maven-war-plugin.version>3.3.1</maven-war-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>
+			<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 - 0
xxl-job-admin/Dockerfile

@@ -0,0 +1,11 @@
+FROM heliang230/centos:7.5.1804
+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  --spring.profiles.active=pro"]

+ 116 - 0
xxl-job-admin/pom.xml

@@ -0,0 +1,116 @@
+<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.3.0</version>
+	</parent>
+	<artifactId>xxl-job-admin</artifactId>
+	<packaging>jar</packaging>
+
+	<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-web:spring-webmvc + autoconfigure + logback + yaml + tomcat -->
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+		<!-- starter-test:junit + 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-starter:mybatis + mybatis-spring + hikari(default) -->
+		<dependency>
+			<groupId>org.mybatis.spring.boot</groupId>
+			<artifactId>mybatis-spring-boot-starter</artifactId>
+			<version>${mybatis-spring-boot-starter.version}</version>
+		</dependency>
+		<!-- mysql -->
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-java</artifactId>
+			<version>${mysql-connector-java.version}</version>
+		</dependency>
+
+		<!-- xxl-job-core -->
+		<dependency>
+			<groupId>com.xuxueli</groupId>
+			<artifactId>xxl-job-core</artifactId>
+			<version>${project.parent.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.postgresql</groupId>
+			<artifactId>postgresql</artifactId>
+		</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>
+					<!-- made of '[a-z0-9-_.]' -->
+					<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>

+ 16 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/XxlJobAdminApplication.java

@@ -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);
+	}
+
+}

+ 96 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java

@@ -0,0 +1,96 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.controller.annotation.PermissionLimit;
+import com.xxl.job.admin.service.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));
+	}
+	
+}

+ 72 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java

@@ -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.");
+        }
+
+    }
+
+}

+ 96 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobCodeController.java

@@ -0,0 +1,96 @@
+package com.xxl.job.admin.controller;
+
+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
+		JobInfoController.validPermission(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(Model model, 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 exists_jobInfo = xxlJobInfoDao.loadById(id);
+		if (exists_jobInfo == null) {
+			return new ReturnT<String>(500, I18nUtil.getString("jobinfo_glue_jobid_unvalid"));
+		}
+		
+		// update new code
+		exists_jobInfo.setGlueSource(glueSource);
+		exists_jobInfo.setGlueRemark(glueRemark);
+		exists_jobInfo.setGlueUpdatetime(new Date());
+
+		exists_jobInfo.setUpdateTime(new Date());
+		xxlJobInfoDao.update(exists_jobInfo);
+
+		// log old code
+		XxlJobLogGlue xxlJobLogGlue = new XxlJobLogGlue();
+		xxlJobLogGlue.setJobId(exists_jobInfo.getId());
+		xxlJobLogGlue.setGlueType(exists_jobInfo.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(exists_jobInfo.getId(), 30);
+
+		return ReturnT.SUCCESS;
+	}
+	
+}

+ 197 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java

@@ -0,0 +1,197 @@
+package com.xxl.job.admin.controller;
+
+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
+	public String index(Model model) {
+		return "jobgroup/jobgroup.index";
+	}
+
+	@RequestMapping("/pageList")
+	@ResponseBody
+	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
+	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
+	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
+	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
+	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);
+	}
+
+}

+ 183 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobInfoController.java

@@ -0,0 +1,183 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.core.cron.CronExpression;
+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.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.admin.dao.XxlJobGroupDao;
+import com.xxl.job.admin.service.LoginService;
+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.text.ParseException;
+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 = 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";
+	}
+
+	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 = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
+			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;
+	}
+	public static void validPermission(HttpServletRequest request, int jobGroup) {
+		XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
+		if (!loginUser.validPermission(jobGroup)) {
+			throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]");
+		}
+	}
+	
+	@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(XxlJobInfo jobInfo) {
+		return xxlJobService.add(jobInfo);
+	}
+	
+	@RequestMapping("/update")
+	@ResponseBody
+	public ReturnT<String> update(XxlJobInfo jobInfo) {
+		return xxlJobService.update(jobInfo);
+	}
+	
+	@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
+	//@PermissionLimit(limit = false)
+	public ReturnT<String> triggerJob(int id, String executorParam, String addressList) {
+		// force cover job param
+		if (executorParam == null) {
+			executorParam = "";
+		}
+
+		JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
+		return ReturnT.SUCCESS;
+	}
+
+	@RequestMapping("/nextTriggerTime")
+	@ResponseBody
+	public ReturnT<List<String>> nextTriggerTime(String scheduleType, String scheduleConf) {
+
+		if(scheduleConf==null||"".equals(scheduleConf)){
+			 	return new ReturnT<List<String>>(new ArrayList<>());
+		}
+		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);
+
+	}
+	
+}

+ 233 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java

@@ -0,0 +1,233 @@
+package com.xxl.job.admin.controller;
+
+import com.xxl.job.admin.core.exception.XxlJobException;
+import com.xxl.job.admin.core.complete.XxlJobCompleter;
+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.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.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 = JobInfoController.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
+			JobInfoController.validPermission(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
+		JobInfoController.validPermission(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("executorAddress", jobLog.getExecutorAddress());
+        model.addAttribute("triggerTime", jobLog.getTriggerTime().getTime());
+        model.addAttribute("logId", jobLog.getId());
+		return "joblog/joblog.detail";
+	}
+
+	@RequestMapping("/logDetailCat")
+	@ResponseBody
+	public ReturnT<LogResult> logDetailCat(String executorAddress, long triggerTime, long logId, int fromLineNum){
+		try {
+			ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
+			ReturnT<LogResult> logResult = executorBiz.log(new LogParam(triggerTime, logId, fromLineNum));
+
+			// is end
+            if (logResult.getContent()!=null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) {
+                XxlJobLog jobLog = xxlJobLogDao.load(logId);
+                if (jobLog.getHandleCode() > 0) {
+                    logResult.getContent().setEnd(true);
+                }
+            }
+
+			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(int jobGroup, int jobId, int type){
+
+		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;
+	}
+
+}

+ 179 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/UserController.java

@@ -0,0 +1,179 @@
+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.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.admin.service.LoginService;
+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 UserController {
+
+    @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 = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
+        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 = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
+        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){
+
+        // valid password
+        if (password==null || password.trim().length()==0){
+            return new ReturnT<String>(ReturnT.FAIL.getCode(), "密码不可为空");
+        }
+        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 md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
+
+        // update pwd
+        XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
+
+        // do write
+        XxlJobUser existUser = xxlJobUserDao.loadByUserName(loginUser.getUsername());
+        existUser.setPassword(md5Password);
+        xxlJobUserDao.update(existUser);
+
+        return ReturnT.SUCCESS;
+    }
+
+}

+ 29 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/annotation/PermissionLimit.java

@@ -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;
+
+}

+ 43 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java

@@ -0,0 +1,43 @@
+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.ModelAndView;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+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 extends HandlerInterceptorAdapter {
+
+	@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()));
+		}
+		
+		super.postHandle(request, response, handler, modelAndView);
+	}
+	
+}

+ 59 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java

@@ -0,0 +1,59 @@
+package com.xxl.job.admin.controller.interceptor;
+
+import com.xxl.job.admin.controller.annotation.PermissionLimit;
+import com.xxl.job.admin.core.model.XxlJobUser;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.admin.service.LoginService;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 权限拦截
+ *
+ * @author xuxueli 2015-12-12 18:09:04
+ */
+@Component
+public class PermissionInterceptor extends HandlerInterceptorAdapter {
+
+	@Resource
+	private LoginService loginService;
+
+	@Override
+	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+		
+		if (!(handler instanceof HandlerMethod)) {
+			return super.preHandle(request, response, handler);
+		}
+
+		// 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);
+		}
+
+		return super.preHandle(request, response, handler);
+	}
+	
+}

+ 28 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/WebMvcConfig.java

@@ -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("/**");
+    }
+
+}

+ 66 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/controller/resolver/WebExceptionResolver.java

@@ -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;
+		}
+	}
+	
+}

+ 20 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarm.java

@@ -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);
+
+}

+ 65 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/JobAlarmer.java

@@ -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;
+    }
+
+}

+ 117 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/alarm/impl/EmailJobAlarm.java

@@ -0,0 +1,117 @@
+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
+     */
+    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;
+    }
+
+}

+ 99 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/complete/XxlJobCompleter.java

@@ -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){
+
+        // 1、handle success, to trigger child job
+        String triggerChildMsg = null;
+        if (XxlJobContext.HANDLE_COCE_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 );
+        }
+
+        // 2、fix_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;
+        }
+    }
+
+}

+ 158 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java

@@ -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;
+    }
+
+}

Разлика између датотеке није приказан због своје велике величине
+ 1666 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java


+ 14 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/exception/XxlJobException.java

@@ -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);
+    }
+
+}

+ 77 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java

@@ -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 Integer 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 Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer 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;
+    }
+
+}

+ 237 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java

@@ -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 Integer 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 Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer 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;
+	}
+}

+ 157 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLog.java

@@ -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;
+	}
+
+}

+ 77 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogGlue.java

@@ -0,0 +1,77 @@
+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 Integer 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 Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer 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;
+	}
+
+}

+ 54 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobLogReport.java

@@ -0,0 +1,54 @@
+package com.xxl.job.admin.core.model;
+
+import java.util.Date;
+
+public class XxlJobLogReport {
+
+    private Integer id;
+
+    private Date triggerDay;
+
+    private int runningCount;
+    private int sucCount;
+    private int failCount;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer 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;
+    }
+}

+ 55 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobRegistry.java

@@ -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 Integer id;
+    private String registryGroup;
+    private String registryKey;
+    private String registryValue;
+    private Date updateTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer 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;
+    }
+}

+ 73 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobUser.java

@@ -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 Integer id;
+	private String username;		// 账号
+	private String password;		// 密码
+	private int role;				// 角色:0-普通用户、1-管理员
+	private String permission;	// 权限:执行器ID列表,多个逗号分割
+
+	public Integer getId() {
+		return id;
+	}
+
+	public void setId(Integer 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;
+		}
+
+	}
+
+}

+ 32 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java

@@ -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());
+//
+//
+//	}
+//
+//}

+ 413 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java

@@ -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 {
+//    	// 1、job key
+//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//        JobKey jobKey = new JobKey(jobName);
+//
+//        // 2、valid
+//        if (scheduler.checkExists(triggerKey)) {
+//            return true;    // PASS
+//        }
+//
+//        // 3、corn trigger
+//        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();   // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+//        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+//        // 4、job 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");
+//		}*/
+//
+//        // 5、schedule 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 {
+//
+//        // 1、job key
+//        TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+//        // 2、valid
+//        if (!scheduler.checkExists(triggerKey)) {
+//            return true;    // PASS
+//        }
+//
+//        CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+//        // 3、avoid repeat cron
+//        String oldCron = oldTrigger.getCronExpression();
+//        if (oldCron.equals(cronExpression)){
+//            return true;    // PASS
+//        }
+//
+//        // 4、new cron trigger
+//        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+//        oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+//        // 5、rescheduleJob
+//        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;
+//    }*/
+//
+//}

+ 58 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java

@@ -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) {
+//        //
+//    }
+//
+//}

+ 48 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouteStrategyEnum.java

@@ -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;
+    }
+
+}

+ 24 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/ExecutorRouter.java

@@ -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);
+
+}

+ 48 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java

@@ -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());
+    }
+
+}

+ 85 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteConsistentHash.java

@@ -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固定调度其中一台机器;
+ *      a、virtual node:解决不均衡问题
+ *      b、hash method replace hashCode:String的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);
+    }
+
+}

+ 48 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java

@@ -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());
+
+    }
+}

+ 19 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFirst.java

@@ -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));
+    }
+
+}

+ 79 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLFU.java

@@ -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):最不经常使用,频率/次数
+ *      b、LRU(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+构造入参Compare;Value排序暂时只能通过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);
+    }
+
+}

+ 76 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLRU.java

@@ -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对应的每个执行器,最久为使用的优先被选举
+ *      a、LFU(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
+             *      a、accessOrder:true=访问顺序排序(get/put时排序);false=插入顺序排期;
+             *      b、removeEldestEntry:新增元素时将会调用,返回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);
+    }
+
+}

+ 19 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteLast.java

@@ -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));
+    }
+
+}

+ 23 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRandom.java

@@ -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);
+    }
+
+}

+ 46 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteRound.java

@@ -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);
+    }
+
+}

+ 39 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/MisfireStrategyEnum.java

@@ -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;
+    }
+
+}

+ 46 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/ScheduleTypeEnum.java

@@ -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;
+    }
+
+}

+ 101 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/scheduler/XxlJobScheduler.java

@@ -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;
+    }
+
+}

+ 184 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobCompleteHelper.java

@@ -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;
+	}
+
+
+
+}

+ 110 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java

@@ -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());
+
+								// 1、fail 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);
+								}
+
+								// 2、fail alarm monitor
+								int newAlarmStatus = 0;		// 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
+								if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {
+									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);
+		}
+	}
+
+}

+ 152 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobLogReportHelper.java

@@ -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) {
+
+                    // 1、log-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);
+                        }
+                    }
+
+                    // 2、log-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);
+        }
+    }
+
+}

+ 204 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryHelper.java

@@ -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
+	}
+
+
+}

+ 369 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java

@@ -0,0 +1,369 @@
+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
+
+                        // 1、pre read
+                        long nowTime = System.currentTimeMillis();
+                        List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
+                        if (scheduleList!=null && scheduleList.size()>0) {
+                            // 2、push time-ring
+                            for (XxlJobInfo jobInfo: scheduleList) {
+
+                                // time-ring jump
+                                if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
+                                    // 2.1、trigger-expire > 5s:pass && make next-trigger-time
+                                    logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());
+
+                                    // 1、misfire 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() );
+                                    }
+
+                                    // 2、fresh next
+                                    refreshNextValidTime(jobInfo, new Date());
+
+                                } else if (nowTime > jobInfo.getTriggerNextTime()) {
+                                    // 2.2、trigger-expire < 5s:direct-trigger && make next-trigger-time
+
+                                    // 1、trigger
+                                    JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
+                                    logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );
+
+                                    // 2、fresh next
+                                    refreshNextValidTime(jobInfo, new Date());
+
+                                    // next-trigger-time in 5s, pre-read again
+                                    if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
+
+                                        // 1、make ring second
+                                        int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+
+                                        // 2、push time ring
+                                        pushTimeRing(ringSecond, jobInfo.getId());
+
+                                        // 3、fresh next
+                                        refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
+
+                                    }
+
+                                } else {
+                                    // 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time
+
+                                    // 1、make ring second
+                                    int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+
+                                    // 2、push time ring
+                                    pushTimeRing(ringSecond, jobInfo.getId());
+
+                                    // 3、fresh next
+                                    refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
+
+                                }
+
+                            }
+
+                            // 3、update 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 {
+        Date nextValidTime = generateNextValidTime(jobInfo, fromTime);
+        if (nextValidTime != null) {
+            jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+            jobInfo.setTriggerNextTime(nextValidTime.getTime());
+        } else {
+            jobInfo.setTriggerStatus(0);
+            jobInfo.setTriggerLastTime(0);
+            jobInfo.setTriggerNextTime(0);
+            logger.warn(">>>>>>>>>>> xxl-job, refreshNextValidTime fail for job: jobId={}, scheduleType={}, scheduleConf={}",
+                    jobInfo.getId(), jobInfo.getScheduleType(), jobInfo.getScheduleConf());
+        }
+    }
+
+    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(){
+
+        // 1、stop 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;
+    }
+
+}

+ 150 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobTriggerPoolHelper.java

@@ -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);
+    }
+
+}

+ 27 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/TriggerTypeEnum.java

@@ -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;
+    }
+
+}

+ 0 - 0
xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java


Неке датотеке нису приказане због велике количине промена