zhaoxian 2 tahun lalu
melakukan
2c9c25a941
90 mengubah file dengan 11238 tambahan dan 0 penghapusan
  1. 7 0
      .gitignore
  2. 674 0
      LICENSE
  3. 32 0
      NOTICE
  4. 638 0
      README.md
  5. 201 0
      job-live/pom.xml
  6. 22 0
      job-live/src/main/java/cn/com/ctop/job/live/LiveJobApplication.java
  7. 13 0
      job-live/src/main/java/cn/com/ctop/job/live/config/MybatisPlusConfig.java
  8. 60 0
      job-live/src/main/java/cn/com/ctop/job/live/config/XxlJobConfig.java
  9. 73 0
      job-live/src/main/java/cn/com/ctop/job/live/controller/LiveController.java
  10. 524 0
      job-live/src/main/java/cn/com/ctop/job/live/entity/KuaishouLiveAccountReportDaily.java
  11. 540 0
      job-live/src/main/java/cn/com/ctop/job/live/entity/KuaishouLiveCampaignReportDaily.java
  12. 587 0
      job-live/src/main/java/cn/com/ctop/job/live/entity/KuaishouLiveCreativeReportDaily.java
  13. 571 0
      job-live/src/main/java/cn/com/ctop/job/live/entity/KuaishouLiveUnitReportDaily.java
  14. 18 0
      job-live/src/main/java/cn/com/ctop/job/live/mapper/KuaishouLiveAccountReportDailyMapper.java
  15. 17 0
      job-live/src/main/java/cn/com/ctop/job/live/mapper/KuaishouLiveCampaignReportDailyMapper.java
  16. 18 0
      job-live/src/main/java/cn/com/ctop/job/live/mapper/KuaishouLiveCreativeReportDailyMapper.java
  17. 18 0
      job-live/src/main/java/cn/com/ctop/job/live/mapper/KuaishouLiveUnitReportDailyMapper.java
  18. 186 0
      job-live/src/main/java/cn/com/ctop/job/live/mapper/xml/KuaishouLiveAccountReportDailyMapper.xml
  19. 194 0
      job-live/src/main/java/cn/com/ctop/job/live/mapper/xml/KuaishouLiveCampaignReportDailyMapper.xml
  20. 209 0
      job-live/src/main/java/cn/com/ctop/job/live/mapper/xml/KuaishouLiveCreativeReportDailyMapper.xml
  21. 204 0
      job-live/src/main/java/cn/com/ctop/job/live/mapper/xml/KuaishouLiveUnitReportDailyMapper.xml
  22. 14 0
      job-live/src/main/java/cn/com/ctop/job/live/service/IKuaishouLiveAccountReportDailyService.java
  23. 14 0
      job-live/src/main/java/cn/com/ctop/job/live/service/IKuaishouLiveCampaignReportDailyService.java
  24. 14 0
      job-live/src/main/java/cn/com/ctop/job/live/service/IKuaishouLiveCreativeReportDailyService.java
  25. 14 0
      job-live/src/main/java/cn/com/ctop/job/live/service/IKuaishouLiveUnitReportDailyService.java
  26. 75 0
      job-live/src/main/java/cn/com/ctop/job/live/service/impl/KuaishouLiveAccountReportDailyServiceImpl.java
  27. 74 0
      job-live/src/main/java/cn/com/ctop/job/live/service/impl/KuaishouLiveCampaignReportDailyServiceImpl.java
  28. 74 0
      job-live/src/main/java/cn/com/ctop/job/live/service/impl/KuaishouLiveCreativeReportDailyServiceImpl.java
  29. 74 0
      job-live/src/main/java/cn/com/ctop/job/live/service/impl/KuaishouLiveUnitReportDailyServiceImpl.java
  30. 62 0
      job-live/src/main/java/cn/com/ctop/job/live/utils/APIUtil.java
  31. 66 0
      job-live/src/main/java/cn/com/ctop/job/live/utils/Check.java
  32. 303 0
      job-live/src/main/java/cn/com/ctop/job/live/utils/DateUtils.java
  33. 28 0
      job-live/src/main/java/cn/com/ctop/job/live/utils/HttpGetWithBodyEntity.java
  34. 137 0
      job-live/src/main/java/cn/com/ctop/job/live/utils/HttpUtils.java
  35. 14 0
      job-live/src/main/java/cn/com/ctop/job/live/utils/KuaishouAPIConstant.java
  36. 173 0
      job-live/src/main/java/cn/com/ctop/job/live/utils/Result.java
  37. 124 0
      job-live/src/main/resources/application.yml
  38. 49 0
      job-live/src/main/resources/generatorConfig.xml
  39. 4 0
      job-live/src/main/resources/log4j.properties
  40. 57 0
      job-live/src/main/resources/logback-dev.xml
  41. 57 0
      job-live/src/main/resources/logback-prod.xml
  42. 57 0
      job-live/src/main/resources/logback-test.xml
  43. 15 0
      job-live/src/test/java/cn/com/ctop/job/kuaishou/SampleTest.java
  44. 144 0
      pom.xml
  45. 64 0
      xxl-job-core/pom.xml
  46. 48 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/AdminBiz.java
  47. 45 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/ExecutorBiz.java
  48. 50 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/client/AdminBizClient.java
  49. 56 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/client/ExecutorBizClient.java
  50. 172 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java
  51. 67 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/model/HandleCallbackParam.java
  52. 28 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/model/IdleBeatParam.java
  53. 28 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/model/KillParam.java
  54. 47 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogParam.java
  55. 56 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogResult.java
  56. 54 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/model/RegistryParam.java
  57. 57 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/model/ReturnT.java
  58. 144 0
      xxl-job-core/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java
  59. 122 0
      xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobContext.java
  60. 255 0
      xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobHelper.java
  61. 35 0
      xxl-job-core/src/main/java/com/xxl/job/core/enums/ExecutorBlockStrategyEnum.java
  62. 13 0
      xxl-job-core/src/main/java/com/xxl/job/core/enums/RegistryConfig.java
  63. 269 0
      xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java
  64. 75 0
      xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSimpleExecutor.java
  65. 126 0
      xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSpringExecutor.java
  66. 90 0
      xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueFactory.java
  67. 53 0
      xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueTypeEnum.java
  68. 80 0
      xxl-job-core/src/main/java/com/xxl/job/core/glue/impl/SpringGlueFactory.java
  69. 38 0
      xxl-job-core/src/main/java/com/xxl/job/core/handler/IJobHandler.java
  70. 24 0
      xxl-job-core/src/main/java/com/xxl/job/core/handler/annotation/JobHandler.java
  71. 30 0
      xxl-job-core/src/main/java/com/xxl/job/core/handler/annotation/XxlJob.java
  72. 38 0
      xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/GlueJobHandler.java
  73. 53 0
      xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/MethodJobHandler.java
  74. 93 0
      xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/ScriptJobHandler.java
  75. 220 0
      xxl-job-core/src/main/java/com/xxl/job/core/log/XxlJobFileAppender.java
  76. 266 0
      xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java
  77. 129 0
      xxl-job-core/src/main/java/com/xxl/job/core/thread/ExecutorRegistryThread.java
  78. 124 0
      xxl-job-core/src/main/java/com/xxl/job/core/thread/JobLogFileCleanThread.java
  79. 252 0
      xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
  80. 260 0
      xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
  81. 156 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/DateUtil.java
  82. 181 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/FileUtil.java
  83. 88 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/GsonTool.java
  84. 203 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/IpUtil.java
  85. 73 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/JdkSerializeTool.java
  86. 70 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/NetUtil.java
  87. 228 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/ScriptUtil.java
  88. 46 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/ShardingUtil.java
  89. 24 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/ThrowableUtil.java
  90. 159 0
      xxl-job-core/src/main/java/com/xxl/job/core/util/XxlJobRemotingUtil.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

+ 638 - 0
README.md

@@ -0,0 +1,638 @@
+<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、爱动超越人工智能科技(北京)有限责任公司
+    - 447、迪普信(北京)科技有限公司
+    - 448、掌站科技(北京)有限公司
+    - 449、深圳市华云中盛股份有限公司
+    - 450、上海原圈科技有限公司
+    - 451、广州赞赏信息科技有限公司
+    - 452、Amber Group
+    - 453、德威国际货运代理(上海)公司
+    - 454、浙江杰夫兄弟智慧科技有限公司
+    - 455、信也科技
+    - 456、开思时代科技(深圳)有限公司
+    - 457、大连槐德科技有限公司
+    - 458、同程生活
+    - 459、松果出行
+    - 460、企鹅杏仁集团
+    - 461、宁波科云信息科技有限公司
+    - 462、上海格蓝威驰信息科技有限公司
+    - 463、杭州趣淘鲸科技有限公司
+    - 464、湖州市数字惠民科技有限公司
+    - 465、乐普(北京)医疗器械股份有限公司
+    - 466、广州市晴川高新技术开发有限公司
+    - 467、山西缇客科技有限公司
+    - 468、徐州卡西穆电子商务有限公司
+    - 469、格创东智科技有限公司
+    - 470、世纪龙信息网络有限责任公司
+    - 471、邦道科技有限公司
+    - 472、河南中盟新云科技股份有限公司
+    - 473、横琴人寿保险有限公司
+    - 474、上海海隆华钟信息技术有限公司
+    - 475、上海久湛
+    - 476、上海仙豆智能机器人有限公司
+    - 477、广州汇尚网络科技有限公司
+    - 478、深圳市阿卡索资讯股份有限公司
+    - 479、青岛佳家康健康管理有限责任公司
+    - 480、蓝城兄弟
+    - 481、成都天府通金融服务股份有限公司
+    - 482、深圳云镖网络科技有限公司
+    - 483、上海影创科技
+    - 484、成都艾拉物联
+    - 485、北京客邻尚品网络技术有限公司
+    - 486、IT实战联盟
+    - 487、杭州尤拉夫科技有限公司
+    - 488、中大检测(湖南)股份有限公司
+    - ……
+
+> 更多接入的公司,欢迎在 [登记地址](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 )

+ 201 - 0
job-live/pom.xml

@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>cn.com.ctop</groupId>
+    <artifactId>job-live</artifactId>
+    <version>2.3.1-SNAPSHOT</version>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.1.3.RELEASE</version>
+        <relativePath/>
+    </parent>
+    <properties>
+        <java.version>1.8</java.version>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <mybatis-plus.version>3.0.6</mybatis-plus.version>
+        <druid.version>1.1.10</druid.version>
+        <commons.version>2.6</commons.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>2.9.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>swagger-bootstrap-ui</artifactId>
+            <version>1.9.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.xuxueli</groupId>
+            <artifactId>xxl-job-core</artifactId>
+            <version>2.3.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>${commons.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>${commons.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!-- mybatis-plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+        <!-- druid -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+        <!-- 动态数据源 -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+            <version>2.5.4</version>
+        </dependency>
+        <!-- junit -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- json -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.56</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus</artifactId>
+            <version>3.3.1.tmp</version>
+        </dependency>
+        <!--mysql-->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>5.1.47</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.sourceforge.jtds</groupId>
+            <artifactId>jtds</artifactId>
+            <version>1.3.1</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.9</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+            <version>4.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.retrofit2</groupId>
+            <artifactId>retrofit</artifactId>
+            <version>2.6.0</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.11</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>4.5.11</version>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.17</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jeecgframework</groupId>
+            <artifactId>autopoi-web</artifactId>
+            <version>1.0.2</version>
+        </dependency>
+
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.yml</include>
+                    <include>**/*.properties</include>
+                    <include>**/*.xml</include>
+                </includes>
+                <filtering>false</filtering>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>**/*.yml</include>
+                    <include>**/*.properties</include>
+                    <include>**/*.xml</include>
+                </includes>
+                <filtering>false</filtering>
+            </resource>
+        </resources>
+    </build>
+
+</project>

+ 22 - 0
job-live/src/main/java/cn/com/ctop/job/live/LiveJobApplication.java

@@ -0,0 +1,22 @@
+package cn.com.ctop.job.live;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+@Slf4j
+@SpringBootApplication
+@ComponentScan(basePackages = {"cn.com.ctop"})
+@Configuration
+public class LiveJobApplication {
+    public static void main(String[] args){
+
+        SpringApplication.run(LiveJobApplication.class, args);
+
+        log.info("\n----------------------------------------------------------\n\t" +
+                "job service is running\n\t" +
+                "----------------------------------------------------------");
+    }
+
+}

+ 13 - 0
job-live/src/main/java/cn/com/ctop/job/live/config/MybatisPlusConfig.java

@@ -0,0 +1,13 @@
+package cn.com.ctop.job.config;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@MapperScan(value = { "cn.com.ctop.**.mapper*"})
+
+public class MybatisPlusConfig {
+
+
+
+}

+ 60 - 0
job-live/src/main/java/cn/com/ctop/job/live/config/XxlJobConfig.java

@@ -0,0 +1,60 @@
+package cn.com.ctop.job.config;
+
+import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * xxl-job config
+ *
+ * @author xuxueli 2017-04-28
+ */
+@Configuration
+public class XxlJobConfig {
+  @Value("${xxl.job.admin.addresses}")
+  private String adminAddresses;
+
+  @Value("${xxl.job.executor.appname}")
+  private String appName;
+
+  @Value("${xxl.job.executor.ip}")
+  private String ip;
+
+  @Value("${xxl.job.executor.port}")
+  private int port;
+
+  @Value("${xxl.job.accessToken}")
+  private String accessToken;
+
+  @Value("${xxl.job.executor.logpath}")
+  private String logPath;
+
+  @Value("${xxl.job.executor.logretentiondays}")
+  private int logRetentionDays;
+
+  @Bean
+  public XxlJobSpringExecutor xxlJobExecutor() {
+    XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
+    xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
+    xxlJobSpringExecutor.setAppname(appName);
+    xxlJobSpringExecutor.setIp(ip);
+    xxlJobSpringExecutor.setPort(port);
+    xxlJobSpringExecutor.setAccessToken(accessToken);
+    xxlJobSpringExecutor.setLogPath(logPath);
+    xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
+
+    return xxlJobSpringExecutor;
+  }
+
+  /**
+   * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
+   *
+   * <p>1、引入依赖: <dependency> <groupId>org.springframework.cloud</groupId>
+   * <artifactId>spring-cloud-commons</artifactId> <version>${version}</version> </dependency>
+   *
+   * <p>2、配置文件,或者容器启动变量 spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
+   *
+   * <p>3、获取IP String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
+   */
+}

+ 73 - 0
job-live/src/main/java/cn/com/ctop/job/live/controller/LiveController.java

@@ -0,0 +1,73 @@
+package cn.com.ctop.job.controller;
+
+import cn.com.ctop.job.live.service.IKuaishouLiveAccountReportDailyService;
+import cn.com.ctop.job.live.service.IKuaishouLiveCampaignReportDailyService;
+import cn.com.ctop.job.live.service.IKuaishouLiveCreativeReportDailyService;
+import cn.com.ctop.job.live.service.IKuaishouLiveUnitReportDailyService;
+import cn.com.ctop.job.live.utils.Result;
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@RestController
+@RequestMapping("/live/kuaishouController")
+@Api(tags = "快手直播")
+@Slf4j
+public class LiveController {
+
+
+    @Autowired
+    private IKuaishouLiveAccountReportDailyService accountReportDailyService;
+
+    @Autowired
+    private IKuaishouLiveCampaignReportDailyService campaignReportDailyService;
+
+    @Autowired
+    private IKuaishouLiveUnitReportDailyService unitReportDailyService;
+
+    @Autowired
+    private IKuaishouLiveCreativeReportDailyService creativeReportDailyService;
+
+
+    /**
+     * 清洗昨日直播账户数据
+     */
+    @GetMapping(value = "/getKuaishouAccountReport")
+    public Result<Object> getKuaishouAccountReport(Long accountId) {
+        String token = "5490f684f0f2bdabdf5d40d9c4f34300";
+        return accountReportDailyService.getKuaishouAccountReport(accountId, token, 1);
+    }
+
+    /**
+     * 清洗昨日直播计划数据
+     */
+    @GetMapping(value = "/getKuaishouCampaignReport")
+    public Result<Object> getKuaishouCampaignReport(Long accountId) {
+        String token = "5490f684f0f2bdabdf5d40d9c4f34300";
+        return campaignReportDailyService.getKuaishouCampaignReport(accountId, token, 1);
+    }
+
+    /**
+     * 清洗昨日直播广告组数据
+     */
+    @GetMapping(value = "/getKuaishouUnitReport")
+    public Result<Object> getKuaishouUnitReport(Long accountId) {
+        String token = "5490f684f0f2bdabdf5d40d9c4f34300";
+        return unitReportDailyService.getKuaishouUnitReport(accountId, token, 1);
+    }
+
+    /**
+     * 清洗昨日直播创意数据
+     */
+    @GetMapping(value = "/getKuaishouCreativeReport")
+    public Result<Object> getKuaishouCreativeReport(Long accountId) {
+        String token = "5490f684f0f2bdabdf5d40d9c4f34300";
+        return creativeReportDailyService.getKuaishouCreativeReport(accountId, token, 1);
+    }
+
+
+}

+ 524 - 0
job-live/src/main/java/cn/com/ctop/job/live/entity/KuaishouLiveAccountReportDaily.java

@@ -0,0 +1,524 @@
+package cn.com.ctop.job.live.entity;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+import org.jeecgframework.poi.excel.annotation.Excel;
+
+import java.math.BigDecimal;
+
+/**
+ * 直播账户报表对象 kuaishou_live_account_report_daily
+ */
+@Data
+public class KuaishouLiveAccountReportDaily {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 账户id
+     */
+    private Long accountId;
+
+    /**
+     * 花费
+     */
+    @Excel(name = "花费")
+    private Long costTotal;
+
+    /**
+     * 曝光数
+     */
+    @Excel(name = "曝光数")
+    private Long adShow;
+
+    /**
+     * 平均千次广告曝光花费
+     */
+    @Excel(name = "平均千次广告曝光花费")
+    private Long adShow1kCost;
+
+    /**
+     * 封面曝光数
+     */
+    @Excel(name = "封面曝光数")
+    private Long impression;
+
+    /**
+     * 封面点击数
+     */
+    @Excel(name = "封面点击数")
+    private Long photoClick;
+
+    /**
+     * 封面点击率
+     */
+    @Excel(name = "封面点击率")
+    private BigDecimal photoClickRatio;
+
+    /**
+     * 素材曝光数
+     */
+    @Excel(name = "素材曝光数")
+    private Long click;
+
+    /**
+     * 行为数
+     */
+    @Excel(name = "行为数")
+    private Long actionbarClick;
+
+    /**
+     * 行为成本
+     */
+    @Excel(name = "行为成本")
+    private Long actionbarClickCost;
+
+    /**
+     * 行为率
+     */
+    @Excel(name = "行为率")
+    private BigDecimal espClickRatio;
+
+    /**
+     * 素材点击率
+     */
+    @Excel(name = "素材点击率")
+    private BigDecimal actionRatio;
+
+    /**
+     * 组件点击数
+     */
+    @Excel(name = "组件点击数")
+    private Long adItemClickCount;
+
+    /**
+     * 直播平均观看时长
+     */
+    @Excel(name = "直播平均观看时长")
+    private Long espLivePlayedSeconds;
+
+    /**
+     * 作品3秒播放数
+     */
+    @Excel(name = "作品3秒播放数")
+    private Long playedThreeSeconds;
+
+    /**
+     * 作品3秒播放率
+     */
+    @Excel(name = "作品3秒播放率")
+    private BigDecimal play3sRatio;
+
+    /**
+     * 作品5秒播放数
+     */
+    @Excel(name = "作品5秒播放数")
+    private Long playedFiveSeconds;
+
+    /**
+     * 作品5秒播放率
+     */
+    @Excel(name = "作品5秒播放率")
+    private BigDecimal play5sRatio;
+
+    /**
+     * 作品完播数
+     */
+    @Excel(name = "作品完播数")
+    private Long playedEnd;
+
+    /**
+     * 作品完播率
+     */
+    @Excel(name = "作品完播率")
+    private BigDecimal playEndRatio;
+
+    /**
+     * 作品分享数
+     */
+    @Excel(name = "作品分享数")
+    private Long share;
+
+    /**
+     * 作品评论数
+     */
+    @Excel(name = "作品评论数")
+    private Long comment;
+
+    /**
+     * 作品点赞数
+     */
+    @Excel(name = "作品点赞数")
+    private Long likes;
+
+    /**
+     * 作品举报数
+     */
+    @Excel(name = "作品举报数")
+    private Long report;
+
+    /**
+     * 作品拉黑数
+     */
+    @Excel(name = "作品拉黑数")
+    private Long block;
+
+    /**
+     * 详情页减少此类作品数
+     */
+    @Excel(name = "详情页减少此类作品数")
+    private Long itemNegative;
+
+    /**
+     * 直播间分享数
+     */
+    @Excel(name = "直播间分享数")
+    private Long liveShare;
+
+    /**
+     * 直播间评论数
+     */
+    @Excel(name = "直播间评论数")
+    private Long liveComment;
+
+    /**
+     * 直播间送礼数
+     */
+    @Excel(name = "直播间送礼数")
+    private Long liveReward;
+
+    /**
+     * 有效播放数
+     */
+    @Excel(name = "有效播放数")
+    private Long effectivePlayCount;
+
+    /**
+     * 有效播放率
+     */
+    @Excel(name = "有效播放率")
+    private BigDecimal effectivePlayRatio;
+
+    /**
+     * 直播间停留1min
+     */
+    @Excel(name = "直播间停留1min")
+    private Long adLivePlayed1mNum;
+
+    /**
+     * 转化数
+     */
+    @Excel(name = "转化数")
+    private Long conversionNum;
+
+    /**
+     * 转化成本
+     */
+    @Excel(name = "转化成本")
+    private Long conversionCostEsp;
+
+    /**
+     * 直接GMV
+     */
+    @Excel(name = "直接GMV")
+    private Long gmv;
+
+    /**
+     * 当日累计GMV
+     */
+    @Excel(name = "当日累计GMV")
+    private Long t0Gmv;
+
+    /**
+     * 次日累计GMV
+     */
+    @Excel(name = "次日累计GMV")
+    private Long t1Gmv;
+
+    /**
+     * 7日累计GMV
+     */
+    @Excel(name = "7日累计GMV")
+    private Long t7Gmv;
+
+    /**
+     * 15日累计GMV
+     */
+    @Excel(name = "15日累计GMV")
+    private Long t15Gmv;
+
+    /**
+     * 30日累计GMV
+     */
+    @Excel(name = "30日累计GMV")
+    private Long t30Gmv;
+
+    /**
+     * 直接ROI
+     */
+    @Excel(name = "直接ROI")
+    private BigDecimal roi;
+
+    /**
+     * 当日累计ROI
+     */
+    @Excel(name = "当日累计ROI")
+    private BigDecimal t0Roi;
+
+    /**
+     * 次日累计ROI
+     */
+    @Excel(name = "次日累计ROI")
+    private BigDecimal t1Roi;
+
+    /**
+     * 7日累计ROI
+     */
+    @Excel(name = "7日累计ROI")
+    private BigDecimal t7Roi;
+
+    /**
+     * 15日累计ROI
+     */
+    @Excel(name = "15日累计ROI")
+    private BigDecimal t15Roi;
+
+    /**
+     * 30日累计ROI
+     */
+    @Excel(name = "30日累计ROI")
+    private BigDecimal t30Roi;
+
+    /**
+     * 直接订单数
+     */
+    @Excel(name = "直接订单数")
+    private Long paiedOrder;
+
+    /**
+     * 直接下单率
+     */
+    @Excel(name = "直接下单率")
+    private BigDecimal orderRatio;
+
+    /**
+     * 当日累计订单数
+     */
+    @Excel(name = "当日累计订单数")
+    private Long t0OrderCnt;
+
+    /**
+     * 当日累计订单成本
+     */
+    @Excel(name = "当日累计订单成本")
+    private Long t0OrderCntCost;
+
+    /**
+     * 累计订单下单率
+     */
+    @Excel(name = "累计订单下单率")
+    private BigDecimal t0OrderCntRatio;
+
+    /**
+     * 次日累计订单数
+     */
+    @Excel(name = "次日累计订单数")
+    private Long t1OrderCnt;
+
+    /**
+     * 7日累计订单数
+     */
+    @Excel(name = "7日累计订单数")
+    private Long t7OrderCnt;
+
+    /**
+     * 15日累计订单数
+     */
+    @Excel(name = "15日累计订单数")
+    private Long t15OrderCnt;
+
+    /**
+     * 30日累计订单数
+     */
+    @Excel(name = "30日累计订单数")
+    private Long t30OrderCnt;
+
+    /**
+     * 涨粉数
+     */
+    @Excel(name = "涨粉数")
+    private Long merchantRecoFans;
+
+    /**
+     * 次日涨粉留存数
+     */
+    @Excel(name = "次日涨粉留存数")
+    private Long t1Retention;
+
+    /**
+     * 7日涨粉留存数
+     */
+    @Excel(name = "7日涨粉留存数")
+    private Long t7Retention;
+
+    /**
+     * 15日涨粉留存数
+     */
+    @Excel(name = "15日涨粉留存数")
+    private Long t15Retention;
+
+    /**
+     * 30日涨粉留存数
+     */
+    @Excel(name = "30日涨粉留存数")
+    private Long t30Retention;
+
+    /**
+     * 次日涨粉留存率
+     */
+    @Excel(name = "次日涨粉留存率")
+    private BigDecimal t1RetentionRatio;
+
+    /**
+     * 7日涨粉留存率
+     */
+    @Excel(name = "7日涨粉留存率")
+    private BigDecimal t7RetentionRatio;
+
+    /**
+     * 15日涨粉留存率
+     */
+    @Excel(name = "15日涨粉留存率")
+    private BigDecimal t15RetentionRatio;
+
+    /**
+     * 30日涨粉留存率
+     */
+    @Excel(name = "30日涨粉留存率")
+    private BigDecimal t30RetentionRatio;
+
+    /**
+     * 预约成功数
+     */
+    @Excel(name = "预约成功数")
+    private Long reservationSuccess;
+
+    /**
+     * 预约成功成本
+     */
+    @Excel(name = "预约成功成本")
+    private Long reservationCost;
+
+    /**
+     * 直播观看数
+     */
+    @Excel(name = "直播观看数")
+    private Long standardLivePlayedStarted;
+
+    /**
+     * 直播观看成本
+     */
+    @Excel(name = "直播观看成本")
+    private Long liveAudienceCost;
+
+    /**
+     * 直播间商品点击数
+     */
+    @Excel(name = "直播间商品点击数")
+    private Long liveEventGoodsView;
+
+    /**
+     * 直播间商品点击率
+     */
+    @Excel(name = "直播间商品点击率")
+    private BigDecimal goodsClickRatio;
+
+    /**
+     * 直接平台新客
+     */
+    @Excel(name = "直接平台新客")
+    private Long directAttrPlatNewBuyerCnt;
+
+    /**
+     * 30日累计平台新客
+     */
+    @Excel(name = "30日累计平台新客")
+    private Long t30AttrPlatTotalBuyerCnt;
+
+    /**
+     * 直接店铺新客
+     */
+    @Excel(name = "直接店铺新客")
+    private Long directAttrSellerNewBuyerCnt;
+
+    /**
+     * 30日累计店铺新客
+     */
+    @Excel(name = "30日累计店铺新客")
+    private Long t30AttrSellerTotalBuyerCnt;
+
+    /**
+     * 涨粉当日GMV
+     */
+    @Excel(name = "涨粉当日GMV")
+    private Long fansT0Gmv;
+
+    /**
+     * 涨粉次日GMV
+     */
+    @Excel(name = "涨粉次日GMV")
+    private Long fansT1Gmv;
+
+    /**
+     * 涨粉7日GMV
+     */
+    @Excel(name = "涨粉7日GMV")
+    private Long fansT7Gmv;
+
+    /**
+     * 涨粉15日GMV
+     */
+    @Excel(name = "涨粉15日GMV")
+    private Long fansT15Gmv;
+
+    /**
+     * 涨粉30日GMV
+     */
+    @Excel(name = "涨粉30日GMV")
+    private Long fansT30Gmv;
+
+    /**
+     * 涨粉当日ROI
+     */
+    @Excel(name = "涨粉当日ROI")
+    private BigDecimal fansT0Roi;
+
+    /**
+     * 涨粉次日ROI
+     */
+    @Excel(name = "涨粉次日ROI")
+    private BigDecimal fansT1Roi;
+
+    /**
+     * 涨粉7日ROI
+     */
+    @Excel(name = "涨粉7日ROI")
+    private BigDecimal fansT7Roi;
+
+    /**
+     * 涨粉15日ROI
+     */
+    @Excel(name = "涨粉15日ROI")
+    private BigDecimal fansT15Roi;
+
+    /**
+     * 涨粉30日ROI
+     */
+    @Excel(name = "涨粉30日ROI")
+    private BigDecimal fansT30Roi;
+
+    /**
+     * 时间分区
+     */
+    private Long reportDate;
+
+}

+ 540 - 0
job-live/src/main/java/cn/com/ctop/job/live/entity/KuaishouLiveCampaignReportDaily.java

@@ -0,0 +1,540 @@
+package cn.com.ctop.job.live.entity;
+
+import lombok.Data;
+import org.jeecgframework.poi.excel.annotation.Excel;
+
+import java.math.BigDecimal;
+
+/**
+ * 直播计划报对象 kuaishou_live_campaign_report_daily
+ */
+@Data
+public class KuaishouLiveCampaignReportDaily {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 账户id
+     */
+    private Long accountId;
+
+    /**
+     * 计划id
+     */
+    private Long campaignId;
+
+    /**
+     * 计划名称
+     */
+    @Excel(name = "计划名称")
+    private String campaignName;
+
+    /**
+     * 营销目标
+     */
+    @Excel(name = "营销目标")
+    private String campaignTypeStr;
+
+    /**
+     * 花费
+     */
+    @Excel(name = "花费")
+    private Long costTotal;
+
+    /**
+     * 曝光数
+     */
+    @Excel(name = "曝光数")
+    private Long adShow;
+
+    /**
+     * 平均千次广告曝光花费
+     */
+    @Excel(name = "平均千次广告曝光花费")
+    private Long adShow1kCost;
+
+    /**
+     * 封面曝光数
+     */
+    @Excel(name = "封面曝光数")
+    private Long impression;
+
+    /**
+     * 封面点击数
+     */
+    @Excel(name = "封面点击数")
+    private Long photoClick;
+
+    /**
+     * 封面点击率
+     */
+    @Excel(name = "封面点击率")
+    private BigDecimal photoClickRatio;
+
+    /**
+     * 素材曝光数
+     */
+    @Excel(name = "素材曝光数")
+    private Long click;
+
+    /**
+     * 行为数
+     */
+    @Excel(name = "行为数")
+    private Long actionbarClick;
+
+    /**
+     * 行为成本
+     */
+    @Excel(name = "行为成本")
+    private Long actionbarClickCost;
+
+    /**
+     * 行为率
+     */
+    @Excel(name = "行为率")
+    private BigDecimal espClickRatio;
+
+    /**
+     * 素材点击率
+     */
+    @Excel(name = "素材点击率")
+    private BigDecimal actionRatio;
+
+    /**
+     * 组件点击数
+     */
+    @Excel(name = "组件点击数")
+    private Long adItemClickCount;
+
+    /**
+     * 直播平均观看时长
+     */
+    @Excel(name = "直播平均观看时长")
+    private Long espLivePlayedSeconds;
+
+    /**
+     * 作品3秒播放数
+     */
+    @Excel(name = "作品3秒播放数")
+    private Long playedThreeSeconds;
+
+    /**
+     * 作品3秒播放率
+     */
+    @Excel(name = "作品3秒播放率")
+    private BigDecimal play3sRatio;
+
+    /**
+     * 作品5秒播放数
+     */
+    @Excel(name = "作品5秒播放数")
+    private Long playedFiveSeconds;
+
+    /**
+     * 作品5秒播放率
+     */
+    @Excel(name = "作品5秒播放率")
+    private BigDecimal play5sRatio;
+
+    /**
+     * 作品完播数
+     */
+    @Excel(name = "作品完播数")
+    private Long playedEnd;
+
+    /**
+     * 作品完播率
+     */
+    @Excel(name = "作品完播率")
+    private BigDecimal playEndRatio;
+
+    /**
+     * 作品分享数
+     */
+    @Excel(name = "作品分享数")
+    private Long share;
+
+    /**
+     * 作品评论数
+     */
+    @Excel(name = "作品评论数")
+    private Long comment;
+
+    /**
+     * 作品点赞数
+     */
+    @Excel(name = "作品点赞数")
+    private Long likes;
+
+    /**
+     * 作品举报数
+     */
+    @Excel(name = "作品举报数")
+    private Long report;
+
+    /**
+     * 作品拉黑数
+     */
+    @Excel(name = "作品拉黑数")
+    private Long block;
+
+    /**
+     * 详情页减少此类作品数
+     */
+    @Excel(name = "详情页减少此类作品数")
+    private Long itemNegative;
+
+    /**
+     * 直播间分享数
+     */
+    @Excel(name = "直播间分享数")
+    private Long liveShare;
+
+    /**
+     * 直播间评论数
+     */
+    @Excel(name = "直播间评论数")
+    private Long liveComment;
+
+    /**
+     * 直播间送礼数
+     */
+    @Excel(name = "直播间送礼数")
+    private Long liveReward;
+
+    /**
+     * 有效播放数
+     */
+    @Excel(name = "有效播放数")
+    private Long effectivePlayCount;
+
+    /**
+     * 有效播放率
+     */
+    @Excel(name = "有效播放率")
+    private BigDecimal effectivePlayRatio;
+
+    /**
+     * 直播间停留1min
+     */
+    @Excel(name = "直播间停留1min")
+    private Long adLivePlayed1mNum;
+
+    /**
+     * 转化数
+     */
+    @Excel(name = "转化数")
+    private Long conversionNum;
+
+    /**
+     * 转化成本
+     */
+    @Excel(name = "转化成本")
+    private Long conversionCostEsp;
+
+    /**
+     * 直接GMV
+     */
+    @Excel(name = "直接GMV")
+    private Long gmv;
+
+    /**
+     * 当日累计GMV
+     */
+    @Excel(name = "当日累计GMV")
+    private Long t0Gmv;
+
+    /**
+     * 次日累计GMV
+     */
+    @Excel(name = "次日累计GMV")
+    private Long t1Gmv;
+
+    /**
+     * 7日累计GMV
+     */
+    @Excel(name = "7日累计GMV")
+    private Long t7Gmv;
+
+    /**
+     * 15日累计GMV
+     */
+    @Excel(name = "15日累计GMV")
+    private Long t15Gmv;
+
+    /**
+     * 30日累计GMV
+     */
+    @Excel(name = "30日累计GMV")
+    private Long t30Gmv;
+
+    /**
+     * 直接ROI
+     */
+    @Excel(name = "直接ROI")
+    private BigDecimal roi;
+
+    /**
+     * 当日累计ROI
+     */
+    @Excel(name = "当日累计ROI")
+    private BigDecimal t0Roi;
+
+    /**
+     * 次日累计ROI
+     */
+    @Excel(name = "次日累计ROI")
+    private BigDecimal t1Roi;
+
+    /**
+     * 7日累计ROI
+     */
+    @Excel(name = "7日累计ROI")
+    private BigDecimal t7Roi;
+
+    /**
+     * 15日累计ROI
+     */
+    @Excel(name = "15日累计ROI")
+    private BigDecimal t15Roi;
+
+    /**
+     * 30日累计ROI
+     */
+    @Excel(name = "30日累计ROI")
+    private BigDecimal t30Roi;
+
+    /**
+     * 直接订单数
+     */
+    @Excel(name = "直接订单数")
+    private Long paiedOrder;
+
+    /**
+     * 直接下单率
+     */
+    @Excel(name = "直接下单率")
+    private BigDecimal orderRatio;
+
+    /**
+     * 当日累计订单数
+     */
+    @Excel(name = "当日累计订单数")
+    private Long t0OrderCnt;
+
+    /**
+     * 当日累计订单成本
+     */
+    @Excel(name = "当日累计订单成本")
+    private Long t0OrderCntCost;
+
+    /**
+     * 累计订单下单率
+     */
+    @Excel(name = "累计订单下单率")
+    private BigDecimal t0OrderCntRatio;
+
+    /**
+     * 次日累计订单数
+     */
+    @Excel(name = "次日累计订单数")
+    private Long t1OrderCnt;
+
+    /**
+     * 7日累计订单数
+     */
+    @Excel(name = "7日累计订单数")
+    private Long t7OrderCnt;
+
+    /**
+     * 15日累计订单数
+     */
+    @Excel(name = "15日累计订单数")
+    private Long t15OrderCnt;
+
+    /**
+     * 30日累计订单数
+     */
+    @Excel(name = "30日累计订单数")
+    private Long t30OrderCnt;
+
+    /**
+     * 涨粉数
+     */
+    @Excel(name = "涨粉数")
+    private Long merchantRecoFans;
+
+    /**
+     * 次日涨粉留存数
+     */
+    @Excel(name = "次日涨粉留存数")
+    private Long t1Retention;
+
+    /**
+     * 7日涨粉留存数
+     */
+    @Excel(name = "7日涨粉留存数")
+    private Long t7Retention;
+
+    /**
+     * 15日涨粉留存数
+     */
+    @Excel(name = "15日涨粉留存数")
+    private Long t15Retention;
+
+    /**
+     * 30日涨粉留存数
+     */
+    @Excel(name = "30日涨粉留存数")
+    private Long t30Retention;
+
+    /**
+     * 次日涨粉留存率
+     */
+    @Excel(name = "次日涨粉留存率")
+    private BigDecimal t1RetentionRatio;
+
+    /**
+     * 7日涨粉留存率
+     */
+    @Excel(name = "7日涨粉留存率")
+    private BigDecimal t7RetentionRatio;
+
+    /**
+     * 15日涨粉留存率
+     */
+    @Excel(name = "15日涨粉留存率")
+    private BigDecimal t15RetentionRatio;
+
+    /**
+     * 30日涨粉留存率
+     */
+    @Excel(name = "30日涨粉留存率")
+    private BigDecimal t30RetentionRatio;
+
+    /**
+     * 预约成功数
+     */
+    @Excel(name = "预约成功数")
+    private Long reservationSuccess;
+
+    /**
+     * 预约成功成本
+     */
+    @Excel(name = "预约成功成本")
+    private Long reservationCost;
+
+    /**
+     * 直播观看数
+     */
+    @Excel(name = "直播观看数")
+    private Long standardLivePlayedStarted;
+
+    /**
+     * 直播观看成本
+     */
+    @Excel(name = "直播观看成本")
+    private Long liveAudienceCost;
+
+    /**
+     * 直播间商品点击数
+     */
+    @Excel(name = "直播间商品点击数")
+    private Long liveEventGoodsView;
+
+    /**
+     * 直播间商品点击率
+     */
+    @Excel(name = "直播间商品点击率")
+    private BigDecimal goodsClickRatio;
+
+    /**
+     * 直接平台新客
+     */
+    @Excel(name = "直接平台新客")
+    private Long directAttrPlatNewBuyerCnt;
+
+    /**
+     * 30日累计平台新客
+     */
+    @Excel(name = "30日累计平台新客")
+    private Long t30AttrPlatTotalBuyerCnt;
+
+    /**
+     * 直接店铺新客
+     */
+    @Excel(name = "直接店铺新客")
+    private Long directAttrSellerNewBuyerCnt;
+
+    /**
+     * 30日累计店铺新客
+     */
+    @Excel(name = "30日累计店铺新客")
+    private Long t30AttrSellerTotalBuyerCnt;
+
+    /**
+     * 涨粉当日GMV
+     */
+    @Excel(name = "涨粉当日GMV")
+    private Long fansT0Gmv;
+
+    /**
+     * 涨粉次日GMV
+     */
+    @Excel(name = "涨粉次日GMV")
+    private Long fansT1Gmv;
+
+    /**
+     * 涨粉7日GMV
+     */
+    @Excel(name = "涨粉7日GMV")
+    private Long fansT7Gmv;
+
+    /**
+     * 涨粉15日GMV
+     */
+    @Excel(name = "涨粉15日GMV")
+    private Long fansT15Gmv;
+
+    /**
+     * 涨粉30日GMV
+     */
+    @Excel(name = "涨粉30日GMV")
+    private Long fansT30Gmv;
+
+    /**
+     * 涨粉当日ROI
+     */
+    @Excel(name = "涨粉当日ROI")
+    private BigDecimal fansT0Roi;
+
+    /**
+     * 涨粉次日ROI
+     */
+    @Excel(name = "涨粉次日ROI")
+    private BigDecimal fansT1Roi;
+
+    /**
+     * 涨粉7日ROI
+     */
+    @Excel(name = "涨粉7日ROI")
+    private BigDecimal fansT7Roi;
+
+    /**
+     * 涨粉15日ROI
+     */
+    @Excel(name = "涨粉15日ROI")
+    private BigDecimal fansT15Roi;
+
+    /**
+     * 涨粉30日ROI
+     */
+    @Excel(name = "涨粉30日ROI")
+    private BigDecimal fansT30Roi;
+
+    /**
+     * 时间分区
+     */
+    private Long reportDate;
+
+}

+ 587 - 0
job-live/src/main/java/cn/com/ctop/job/live/entity/KuaishouLiveCreativeReportDaily.java

@@ -0,0 +1,587 @@
+package cn.com.ctop.job.live.entity;
+
+import lombok.Data;
+import org.jeecgframework.poi.excel.annotation.Excel;
+
+import java.math.BigDecimal;
+
+/**
+ * 直播创意报对象 kuaishou_live_creative_report_daily
+ */
+@Data
+public class KuaishouLiveCreativeReportDaily {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 账户id
+     */
+    private Long accountId;
+
+    /**
+     * 计划id
+     */
+    @Excel(name = "计划id")
+    private Long campaignId;
+
+    /**
+     * 计划名称
+     */
+    @Excel(name = "计划名称")
+    private String campaignName;
+
+    /**
+     * 广告组id
+     */
+    private Long unitId;
+
+    /**
+     * 广告组名称
+     */
+    @Excel(name = "广告组名称")
+    private String unitName;
+
+    /**
+     * 创意id
+     */
+    private Long creativeId;
+
+    /**
+     * 创意名称
+     */
+    @Excel(name = "创意名称")
+    private String creativeName;
+
+    /**
+     * 创意样式
+     */
+    @Excel(name = "创意样式")
+    private String liveCreativeTypeStr;
+
+    /**
+     * 创意创建方式
+     */
+    @Excel(name = "创意创建方式")
+    private String creativeSourceTypeStr;
+
+    /**
+     * 转化目标
+     */
+    @Excel(name = "转化目标")
+    private String ocpcActionTypeStr;
+
+    /**
+     * 投放方式
+     */
+    @Excel(name = "投放方式")
+    private String speedTypeStr;
+
+    /**
+     * 营销目标
+     */
+    @Excel(name = "营销目标")
+    private String campaignTypeStr;
+
+    /**
+     * 花费
+     */
+    @Excel(name = "花费")
+    private Long costTotal;
+
+    /**
+     * 曝光数
+     */
+    @Excel(name = "曝光数")
+    private Long adShow;
+
+    /**
+     * 平均千次广告曝光花费
+     */
+    @Excel(name = "平均千次广告曝光花费")
+    private Long adShow1kCost;
+
+    /**
+     * 封面曝光数
+     */
+    @Excel(name = "封面曝光数")
+    private Long impression;
+
+    /**
+     * 封面点击数
+     */
+    @Excel(name = "封面点击数")
+    private Long photoClick;
+
+    /**
+     * 封面点击率
+     */
+    @Excel(name = "封面点击率")
+    private BigDecimal photoClickRatio;
+
+    /**
+     * 素材曝光数
+     */
+    @Excel(name = "素材曝光数")
+    private Long click;
+
+    /**
+     * 行为数
+     */
+    @Excel(name = "行为数")
+    private Long actionbarClick;
+
+    /**
+     * 行为成本
+     */
+    @Excel(name = "行为成本")
+    private Long actionbarClickCost;
+
+    /**
+     * 行为率
+     */
+    @Excel(name = "行为率")
+    private BigDecimal espClickRatio;
+
+    /**
+     * 素材点击率
+     */
+    @Excel(name = "素材点击率")
+    private BigDecimal actionRatio;
+
+    /**
+     * 组件点击数
+     */
+    @Excel(name = "组件点击数")
+    private Long adItemClickCount;
+
+    /**
+     * 直播平均观看时长
+     */
+    @Excel(name = "直播平均观看时长")
+    private Long espLivePlayedSeconds;
+
+    /**
+     * 作品3秒播放数
+     */
+    @Excel(name = "作品3秒播放数")
+    private Long playedThreeSeconds;
+
+    /**
+     * 作品3秒播放率
+     */
+    @Excel(name = "作品3秒播放率")
+    private BigDecimal play3sRatio;
+
+    /**
+     * 作品5秒播放数
+     */
+    @Excel(name = "作品5秒播放数")
+    private Long playedFiveSeconds;
+
+    /**
+     * 作品5秒播放率
+     */
+    @Excel(name = "作品5秒播放率")
+    private BigDecimal play5sRatio;
+
+    /**
+     * 作品完播数
+     */
+    @Excel(name = "作品完播数")
+    private Long playedEnd;
+
+    /**
+     * 作品完播率
+     */
+    @Excel(name = "作品完播率")
+    private BigDecimal playEndRatio;
+
+    /**
+     * 作品分享数
+     */
+    @Excel(name = "作品分享数")
+    private Long share;
+
+    /**
+     * 作品评论数
+     */
+    @Excel(name = "作品评论数")
+    private Long comment;
+
+    /**
+     * 作品点赞数
+     */
+    @Excel(name = "作品点赞数")
+    private Long likes;
+
+    /**
+     * 作品举报数
+     */
+    @Excel(name = "作品举报数")
+    private Long report;
+
+    /**
+     * 作品拉黑数
+     */
+    @Excel(name = "作品拉黑数")
+    private Long block;
+
+    /**
+     * 详情页减少此类作品数
+     */
+    @Excel(name = "详情页减少此类作品数")
+    private Long itemNegative;
+
+    /**
+     * 直播间分享数
+     */
+    @Excel(name = "直播间分享数")
+    private Long liveShare;
+
+    /**
+     * 直播间评论数
+     */
+    @Excel(name = "直播间评论数")
+    private Long liveComment;
+
+    /**
+     * 直播间送礼数
+     */
+    @Excel(name = "直播间送礼数")
+    private Long liveReward;
+
+    /**
+     * 有效播放数
+     */
+    @Excel(name = "有效播放数")
+    private Long effectivePlayCount;
+
+    /**
+     * 有效播放率
+     */
+    @Excel(name = "有效播放率")
+    private BigDecimal effectivePlayRatio;
+
+    /**
+     * 直播间停留1min
+     */
+    @Excel(name = "直播间停留1min")
+    private Long adLivePlayed1mNum;
+
+    /**
+     * 转化数
+     */
+    @Excel(name = "转化数")
+    private Long conversionNum;
+
+    /**
+     * 转化成本
+     */
+    @Excel(name = "转化成本")
+    private Long conversionCostEsp;
+
+    /**
+     * 直接GMV
+     */
+    @Excel(name = "直接GMV")
+    private Long gmv;
+
+    /**
+     * 当日累计GMV
+     */
+    @Excel(name = "当日累计GMV")
+    private Long t0Gmv;
+
+    /**
+     * 次日累计GMV
+     */
+    @Excel(name = "次日累计GMV")
+    private Long t1Gmv;
+
+    /**
+     * 7日累计GMV
+     */
+    @Excel(name = "7日累计GMV")
+    private Long t7Gmv;
+
+    /**
+     * 15日累计GMV
+     */
+    @Excel(name = "15日累计GMV")
+    private Long t15Gmv;
+
+    /**
+     * 30日累计GMV
+     */
+    @Excel(name = "30日累计GMV")
+    private Long t30Gmv;
+
+    /**
+     * 直接ROI
+     */
+    @Excel(name = "直接ROI")
+    private BigDecimal roi;
+
+    /**
+     * 当日累计ROI
+     */
+    @Excel(name = "当日累计ROI")
+    private BigDecimal t0Roi;
+
+    /**
+     * 次日累计ROI
+     */
+    @Excel(name = "次日累计ROI")
+    private BigDecimal t1Roi;
+
+    /**
+     * 7日累计ROI
+     */
+    @Excel(name = "7日累计ROI")
+    private BigDecimal t7Roi;
+
+    /**
+     * 15日累计ROI
+     */
+    @Excel(name = "15日累计ROI")
+    private BigDecimal t15Roi;
+
+    /**
+     * 30日累计ROI
+     */
+    @Excel(name = "30日累计ROI")
+    private BigDecimal t30Roi;
+
+    /**
+     * 直接订单数
+     */
+    @Excel(name = "直接订单数")
+    private Long paiedOrder;
+
+    /**
+     * 直接下单率
+     */
+    @Excel(name = "直接下单率")
+    private BigDecimal orderRatio;
+
+    /**
+     * 当日累计订单数
+     */
+    @Excel(name = "当日累计订单数")
+    private Long t0OrderCnt;
+
+    /**
+     * 当日累计订单成本
+     */
+    @Excel(name = "当日累计订单成本")
+    private Long t0OrderCntCost;
+
+    /**
+     * 累计订单下单率
+     */
+    @Excel(name = "累计订单下单率")
+    private BigDecimal t0OrderCntRatio;
+
+    /**
+     * 次日累计订单数
+     */
+    @Excel(name = "次日累计订单数")
+    private Long t1OrderCnt;
+
+    /**
+     * 7日累计订单数
+     */
+    @Excel(name = "7日累计订单数")
+    private Long t7OrderCnt;
+
+    /**
+     * 15日累计订单数
+     */
+    @Excel(name = "15日累计订单数")
+    private Long t15OrderCnt;
+
+    /**
+     * 30日累计订单数
+     */
+    @Excel(name = "30日累计订单数")
+    private Long t30OrderCnt;
+
+    /**
+     * 涨粉数
+     */
+    @Excel(name = "涨粉数")
+    private Long merchantRecoFans;
+
+    /**
+     * 次日涨粉留存数
+     */
+    @Excel(name = "次日涨粉留存数")
+    private Long t1Retention;
+
+    /**
+     * 7日涨粉留存数
+     */
+    @Excel(name = "7日涨粉留存数")
+    private Long t7Retention;
+
+    /**
+     * 15日涨粉留存数
+     */
+    @Excel(name = "15日涨粉留存数")
+    private Long t15Retention;
+
+    /**
+     * 30日涨粉留存数
+     */
+    @Excel(name = "30日涨粉留存数")
+    private Long t30Retention;
+
+    /**
+     * 次日涨粉留存率
+     */
+    @Excel(name = "次日涨粉留存率")
+    private BigDecimal t1RetentionRatio;
+
+    /**
+     * 7日涨粉留存率
+     */
+    @Excel(name = "7日涨粉留存率")
+    private BigDecimal t7RetentionRatio;
+
+    /**
+     * 15日涨粉留存率
+     */
+    @Excel(name = "15日涨粉留存率")
+    private BigDecimal t15RetentionRatio;
+
+    /**
+     * 30日涨粉留存率
+     */
+    @Excel(name = "30日涨粉留存率")
+    private BigDecimal t30RetentionRatio;
+
+    /**
+     * 预约成功数
+     */
+    @Excel(name = "预约成功数")
+    private Long reservationSuccess;
+
+    /**
+     * 预约成功成本
+     */
+    @Excel(name = "预约成功成本")
+    private Long reservationCost;
+
+    /**
+     * 直播观看数
+     */
+    @Excel(name = "直播观看数")
+    private Long standardLivePlayedStarted;
+
+    /**
+     * 直播观看成本
+     */
+    @Excel(name = "直播观看成本")
+    private Long liveAudienceCost;
+
+    /**
+     * 直播间商品点击数
+     */
+    @Excel(name = "直播间商品点击数")
+    private Long liveEventGoodsView;
+
+    /**
+     * 直播间商品点击率
+     */
+    @Excel(name = "直播间商品点击率")
+    private BigDecimal goodsClickRatio;
+
+    /**
+     * 直接平台新客
+     */
+    @Excel(name = "直接平台新客")
+    private Long directAttrPlatNewBuyerCnt;
+
+    /**
+     * 30日累计平台新客
+     */
+    @Excel(name = "30日累计平台新客")
+    private Long t30AttrPlatTotalBuyerCnt;
+
+    /**
+     * 直接店铺新客
+     */
+    @Excel(name = "直接店铺新客")
+    private Long directAttrSellerNewBuyerCnt;
+
+    /**
+     * 30日累计店铺新客
+     */
+    @Excel(name = "30日累计店铺新客")
+    private Long t30AttrSellerTotalBuyerCnt;
+
+    /**
+     * 涨粉当日GMV
+     */
+    @Excel(name = "涨粉当日GMV")
+    private Long fansT0Gmv;
+
+    /**
+     * 涨粉次日GMV
+     */
+    @Excel(name = "涨粉次日GMV")
+    private Long fansT1Gmv;
+
+    /**
+     * 涨粉7日GMV
+     */
+    @Excel(name = "涨粉7日GMV")
+    private Long fansT7Gmv;
+
+    /**
+     * 涨粉15日GMV
+     */
+    @Excel(name = "涨粉15日GMV")
+    private Long fansT15Gmv;
+
+    /**
+     * 涨粉30日GMV
+     */
+    @Excel(name = "涨粉30日GMV")
+    private Long fansT30Gmv;
+
+    /**
+     * 涨粉当日ROI
+     */
+    @Excel(name = "涨粉当日ROI")
+    private BigDecimal fansT0Roi;
+
+    /**
+     * 涨粉次日ROI
+     */
+    @Excel(name = "涨粉次日ROI")
+    private BigDecimal fansT1Roi;
+
+    /**
+     * 涨粉7日ROI
+     */
+    @Excel(name = "涨粉7日ROI")
+    private BigDecimal fansT7Roi;
+
+    /**
+     * 涨粉15日ROI
+     */
+    @Excel(name = "涨粉15日ROI")
+    private BigDecimal fansT15Roi;
+
+    /**
+     * 涨粉30日ROI
+     */
+    @Excel(name = "涨粉30日ROI")
+    private BigDecimal fansT30Roi;
+
+    /**
+     * 时间分区
+     */
+    private Long reportDate;
+
+}

+ 571 - 0
job-live/src/main/java/cn/com/ctop/job/live/entity/KuaishouLiveUnitReportDaily.java

@@ -0,0 +1,571 @@
+package cn.com.ctop.job.live.entity;
+
+import lombok.Data;
+import org.jeecgframework.poi.excel.annotation.Excel;
+
+import java.math.BigDecimal;
+
+/**
+ * 直播广告组报对象 kuaishou_live_unit_report_daily
+ * @date 2022-07-19
+ */
+@Data
+public class KuaishouLiveUnitReportDaily {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 账户id
+     */
+    private Long accountId;
+
+    /**
+     * 计划id
+     */
+    @Excel(name = "计划id")
+    private Long campaignId;
+
+    /**
+     * 计划名称
+     */
+    @Excel(name = "计划名称")
+    private String campaignName;
+
+    /**
+     * 广告组id
+     */
+    private Long unitId;
+
+    /**
+     * 广告组名称
+     */
+    @Excel(name = "广告组名称")
+    private String unitName;
+
+    /**
+     * 转化目标
+     */
+    @Excel(name = "转化目标")
+    private String ocpcActionTypeStr;
+
+    /**
+     * 投放方式
+     */
+    @Excel(name = "投放方式")
+    private String speedTypeStr;
+
+    /**
+     * 创意组成方式
+     */
+    @Excel(name = "创意组成方式")
+    private String creativeBuildTypeStr;
+
+    /**
+     * 营销目标
+     */
+    @Excel(name = "营销目标")
+    private String campaignTypeStr;
+
+    /**
+     * 花费
+     */
+    @Excel(name = "花费")
+    private Long costTotal;
+
+    /**
+     * 曝光数
+     */
+    @Excel(name = "曝光数")
+    private Long adShow;
+
+    /**
+     * 平均千次广告曝光花费
+     */
+    @Excel(name = "平均千次广告曝光花费")
+    private Long adShow1kCost;
+
+    /**
+     * 封面曝光数
+     */
+    @Excel(name = "封面曝光数")
+    private Long impression;
+
+    /**
+     * 封面点击数
+     */
+    @Excel(name = "封面点击数")
+    private Long photoClick;
+
+    /**
+     * 封面点击率
+     */
+    @Excel(name = "封面点击率")
+    private BigDecimal photoClickRatio;
+
+    /**
+     * 素材曝光数
+     */
+    @Excel(name = "素材曝光数")
+    private Long click;
+
+    /**
+     * 行为数
+     */
+    @Excel(name = "行为数")
+    private Long actionbarClick;
+
+    /**
+     * 行为成本
+     */
+    @Excel(name = "行为成本")
+    private Long actionbarClickCost;
+
+    /**
+     * 行为率
+     */
+    @Excel(name = "行为率")
+    private BigDecimal espClickRatio;
+
+    /**
+     * 素材点击率
+     */
+    @Excel(name = "素材点击率")
+    private BigDecimal actionRatio;
+
+    /**
+     * 组件点击数
+     */
+    @Excel(name = "组件点击数")
+    private Long adItemClickCount;
+
+    /**
+     * 直播平均观看时长
+     */
+    @Excel(name = "直播平均观看时长")
+    private Long espLivePlayedSeconds;
+
+    /**
+     * 作品3秒播放数
+     */
+    @Excel(name = "作品3秒播放数")
+    private Long playedThreeSeconds;
+
+    /**
+     * 作品3秒播放率
+     */
+    @Excel(name = "作品3秒播放率")
+    private BigDecimal play3sRatio;
+
+    /**
+     * 作品5秒播放数
+     */
+    @Excel(name = "作品5秒播放数")
+    private Long playedFiveSeconds;
+
+    /**
+     * 作品5秒播放率
+     */
+    @Excel(name = "作品5秒播放率")
+    private BigDecimal play5sRatio;
+
+    /**
+     * 作品完播数
+     */
+    @Excel(name = "作品完播数")
+    private Long playedEnd;
+
+    /**
+     * 作品完播率
+     */
+    @Excel(name = "作品完播率")
+    private BigDecimal playEndRatio;
+
+    /**
+     * 作品分享数
+     */
+    @Excel(name = "作品分享数")
+    private Long share;
+
+    /**
+     * 作品评论数
+     */
+    @Excel(name = "作品评论数")
+    private Long comment;
+
+    /**
+     * 作品点赞数
+     */
+    @Excel(name = "作品点赞数")
+    private Long likes;
+
+    /**
+     * 作品举报数
+     */
+    @Excel(name = "作品举报数")
+    private Long report;
+
+    /**
+     * 作品拉黑数
+     */
+    @Excel(name = "作品拉黑数")
+    private Long block;
+
+    /**
+     * 详情页减少此类作品数
+     */
+    @Excel(name = "详情页减少此类作品数")
+    private Long itemNegative;
+
+    /**
+     * 直播间分享数
+     */
+    @Excel(name = "直播间分享数")
+    private Long liveShare;
+
+    /**
+     * 直播间评论数
+     */
+    @Excel(name = "直播间评论数")
+    private Long liveComment;
+
+    /**
+     * 直播间送礼数
+     */
+    @Excel(name = "直播间送礼数")
+    private Long liveReward;
+
+    /**
+     * 有效播放数
+     */
+    @Excel(name = "有效播放数")
+    private Long effectivePlayCount;
+
+    /**
+     * 有效播放率
+     */
+    @Excel(name = "有效播放率")
+    private BigDecimal effectivePlayRatio;
+
+    /**
+     * 直播间停留1min
+     */
+    @Excel(name = "直播间停留1min")
+    private Long adLivePlayed1mNum;
+
+    /**
+     * 转化数
+     */
+    @Excel(name = "转化数")
+    private Long conversionNum;
+
+    /**
+     * 转化成本
+     */
+    @Excel(name = "转化成本")
+    private Long conversionCostEsp;
+
+    /**
+     * 直接GMV
+     */
+    @Excel(name = "直接GMV")
+    private Long gmv;
+
+    /**
+     * 当日累计GMV
+     */
+    @Excel(name = "当日累计GMV")
+    private Long t0Gmv;
+
+    /**
+     * 次日累计GMV
+     */
+    @Excel(name = "次日累计GMV")
+    private Long t1Gmv;
+
+    /**
+     * 7日累计GMV
+     */
+    @Excel(name = "7日累计GMV")
+    private Long t7Gmv;
+
+    /**
+     * 15日累计GMV
+     */
+    @Excel(name = "15日累计GMV")
+    private Long t15Gmv;
+
+    /**
+     * 30日累计GMV
+     */
+    @Excel(name = "30日累计GMV")
+    private Long t30Gmv;
+
+    /**
+     * 直接ROI
+     */
+    @Excel(name = "直接ROI")
+    private BigDecimal roi;
+
+    /**
+     * 当日累计ROI
+     */
+    @Excel(name = "当日累计ROI")
+    private BigDecimal t0Roi;
+
+    /**
+     * 次日累计ROI
+     */
+    @Excel(name = "次日累计ROI")
+    private BigDecimal t1Roi;
+
+    /**
+     * 7日累计ROI
+     */
+    @Excel(name = "7日累计ROI")
+    private BigDecimal t7Roi;
+
+    /**
+     * 15日累计ROI
+     */
+    @Excel(name = "15日累计ROI")
+    private BigDecimal t15Roi;
+
+    /**
+     * 30日累计ROI
+     */
+    @Excel(name = "30日累计ROI")
+    private BigDecimal t30Roi;
+
+    /**
+     * 直接订单数
+     */
+    @Excel(name = "直接订单数")
+    private Long paiedOrder;
+
+    /**
+     * 直接下单率
+     */
+    @Excel(name = "直接下单率")
+    private BigDecimal orderRatio;
+
+    /**
+     * 当日累计订单数
+     */
+    @Excel(name = "当日累计订单数")
+    private Long t0OrderCnt;
+
+    /**
+     * 当日累计订单成本
+     */
+    @Excel(name = "当日累计订单成本")
+    private Long t0OrderCntCost;
+
+    /**
+     * 累计订单下单率
+     */
+    @Excel(name = "累计订单下单率")
+    private BigDecimal t0OrderCntRatio;
+
+    /**
+     * 次日累计订单数
+     */
+    @Excel(name = "次日累计订单数")
+    private Long t1OrderCnt;
+
+    /**
+     * 7日累计订单数
+     */
+    @Excel(name = "7日累计订单数")
+    private Long t7OrderCnt;
+
+    /**
+     * 15日累计订单数
+     */
+    @Excel(name = "15日累计订单数")
+    private Long t15OrderCnt;
+
+    /**
+     * 30日累计订单数
+     */
+    @Excel(name = "30日累计订单数")
+    private Long t30OrderCnt;
+
+    /**
+     * 涨粉数
+     */
+    @Excel(name = "涨粉数")
+    private Long merchantRecoFans;
+
+    /**
+     * 次日涨粉留存数
+     */
+    @Excel(name = "次日涨粉留存数")
+    private Long t1Retention;
+
+    /**
+     * 7日涨粉留存数
+     */
+    @Excel(name = "7日涨粉留存数")
+    private Long t7Retention;
+
+    /**
+     * 15日涨粉留存数
+     */
+    @Excel(name = "15日涨粉留存数")
+    private Long t15Retention;
+
+    /**
+     * 30日涨粉留存数
+     */
+    @Excel(name = "30日涨粉留存数")
+    private Long t30Retention;
+
+    /**
+     * 次日涨粉留存率
+     */
+    @Excel(name = "次日涨粉留存率")
+    private BigDecimal t1RetentionRatio;
+
+    /**
+     * 7日涨粉留存率
+     */
+    @Excel(name = "7日涨粉留存率")
+    private BigDecimal t7RetentionRatio;
+
+    /**
+     * 15日涨粉留存率
+     */
+    @Excel(name = "15日涨粉留存率")
+    private BigDecimal t15RetentionRatio;
+
+    /**
+     * 30日涨粉留存率
+     */
+    @Excel(name = "30日涨粉留存率")
+    private BigDecimal t30RetentionRatio;
+
+    /**
+     * 预约成功数
+     */
+    @Excel(name = "预约成功数")
+    private Long reservationSuccess;
+
+    /**
+     * 预约成功成本
+     */
+    @Excel(name = "预约成功成本")
+    private Long reservationCost;
+
+    /**
+     * 直播观看数
+     */
+    @Excel(name = "直播观看数")
+    private Long standardLivePlayedStarted;
+
+    /**
+     * 直播观看成本
+     */
+    @Excel(name = "直播观看成本")
+    private Long liveAudienceCost;
+
+    /**
+     * 直播间商品点击数
+     */
+    @Excel(name = "直播间商品点击数")
+    private Long liveEventGoodsView;
+
+    /**
+     * 直播间商品点击率
+     */
+    @Excel(name = "直播间商品点击率")
+    private BigDecimal goodsClickRatio;
+
+    /**
+     * 直接平台新客
+     */
+    @Excel(name = "直接平台新客")
+    private Long directAttrPlatNewBuyerCnt;
+
+    /**
+     * 30日累计平台新客
+     */
+    @Excel(name = "30日累计平台新客")
+    private Long t30AttrPlatTotalBuyerCnt;
+
+    /**
+     * 直接店铺新客
+     */
+    @Excel(name = "直接店铺新客")
+    private Long directAttrSellerNewBuyerCnt;
+
+    /**
+     * 30日累计店铺新客
+     */
+    @Excel(name = "30日累计店铺新客")
+    private Long t30AttrSellerTotalBuyerCnt;
+
+    /**
+     * 涨粉当日GMV
+     */
+    @Excel(name = "涨粉当日GMV")
+    private Long fansT0Gmv;
+
+    /**
+     * 涨粉次日GMV
+     */
+    @Excel(name = "涨粉次日GMV")
+    private Long fansT1Gmv;
+
+    /**
+     * 涨粉7日GMV
+     */
+    @Excel(name = "涨粉7日GMV")
+    private Long fansT7Gmv;
+
+    /**
+     * 涨粉15日GMV
+     */
+    @Excel(name = "涨粉15日GMV")
+    private Long fansT15Gmv;
+
+    /**
+     * 涨粉30日GMV
+     */
+    @Excel(name = "涨粉30日GMV")
+    private Long fansT30Gmv;
+
+    /**
+     * 涨粉当日ROI
+     */
+    @Excel(name = "涨粉当日ROI")
+    private BigDecimal fansT0Roi;
+
+    /**
+     * 涨粉次日ROI
+     */
+    @Excel(name = "涨粉次日ROI")
+    private BigDecimal fansT1Roi;
+
+    /**
+     * 涨粉7日ROI
+     */
+    @Excel(name = "涨粉7日ROI")
+    private BigDecimal fansT7Roi;
+
+    /**
+     * 涨粉15日ROI
+     */
+    @Excel(name = "涨粉15日ROI")
+    private BigDecimal fansT15Roi;
+
+    /**
+     * 涨粉30日ROI
+     */
+    @Excel(name = "涨粉30日ROI")
+    private BigDecimal fansT30Roi;
+
+    /**
+     * 时间分区
+     */
+    private Long reportDate;
+
+}

+ 18 - 0
job-live/src/main/java/cn/com/ctop/job/live/mapper/KuaishouLiveAccountReportDailyMapper.java

@@ -0,0 +1,18 @@
+package cn.com.ctop.job.live.mapper;
+
+import cn.com.ctop.job.live.entity.KuaishouLiveAccountReportDaily;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 直播账户报Mapper接口
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+public interface KuaishouLiveAccountReportDailyMapper {
+
+
+    void replaceBatch(@Param("list") List<KuaishouLiveAccountReportDaily> list);
+}

+ 17 - 0
job-live/src/main/java/cn/com/ctop/job/live/mapper/KuaishouLiveCampaignReportDailyMapper.java

@@ -0,0 +1,17 @@
+package cn.com.ctop.job.live.mapper;
+
+import cn.com.ctop.job.live.entity.KuaishouLiveCampaignReportDaily;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 直播计划报Mapper接口
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+public interface KuaishouLiveCampaignReportDailyMapper {
+
+    void replaceBatch(@Param("list") List<KuaishouLiveCampaignReportDaily> list);
+}

+ 18 - 0
job-live/src/main/java/cn/com/ctop/job/live/mapper/KuaishouLiveCreativeReportDailyMapper.java

@@ -0,0 +1,18 @@
+package cn.com.ctop.job.live.mapper;
+
+import cn.com.ctop.job.live.entity.KuaishouLiveCreativeReportDaily;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 直播创意报Mapper接口
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+public interface KuaishouLiveCreativeReportDailyMapper {
+
+    void replaceBatch(@Param("list") List<KuaishouLiveCreativeReportDaily> list);
+
+}

+ 18 - 0
job-live/src/main/java/cn/com/ctop/job/live/mapper/KuaishouLiveUnitReportDailyMapper.java

@@ -0,0 +1,18 @@
+package cn.com.ctop.job.live.mapper;
+
+import cn.com.ctop.job.live.entity.KuaishouLiveUnitReportDaily;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 直播广告组报Mapper接口
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+public interface KuaishouLiveUnitReportDailyMapper {
+
+    void replaceBatch(@Param("list") List<KuaishouLiveUnitReportDaily> list);
+
+}

+ 186 - 0
job-live/src/main/java/cn/com/ctop/job/live/mapper/xml/KuaishouLiveAccountReportDailyMapper.xml

@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.com.ctop.job.live.mapper.KuaishouLiveAccountReportDailyMapper">
+
+    <insert id="replaceBatch">
+        replace into kuaishou_live_account_report_daily(
+        account_id,
+        cost_total,
+        ad_show,
+        ad_show1k_cost,
+        impression,
+        photo_click,
+        photo_click_ratio,
+        click,
+        actionbar_click,
+        actionbar_click_cost,
+        esp_click_ratio,
+        action_ratio,
+        ad_item_click_count,
+        esp_live_played_seconds,
+        played_three_seconds,
+        play3s_ratio,
+        played_five_seconds,
+        play5s_ratio,
+        played_end,
+        play_end_ratio,
+        share,
+        comment,
+        likes,
+        report,
+        block,
+        item_negative,
+        live_share,
+        live_comment,
+        live_reward,
+        effective_play_count,
+        effective_play_ratio,
+        ad_live_played1m_num,
+        conversion_num,
+        conversion_cost_esp,
+        gmv,
+        t0_gmv,
+        t1_gmv,
+        t7_gmv,
+        t15_gmv,
+        t30_gmv,
+        roi,
+        t0_roi,
+        t1_roi,
+        t7_roi,
+        t15_roi,
+        t30_roi,
+        paied_order,
+        order_ratio,
+        t0_order_cnt,
+        t0_order_cnt_cost,
+        t0_order_cnt_ratio,
+        t1_order_cnt,
+        t7_order_cnt,
+        t15_order_cnt,
+        t30_order_cnt,
+        merchant_reco_fans,
+        t1_retention,
+        t7_retention,
+        t15_retention,
+        t30_retention,
+        t1_retention_ratio,
+        t7_retention_ratio,
+        t15_retention_ratio,
+        t30_retention_ratio,
+        reservation_success,
+        reservation_cost,
+        standard_live_played_started,
+        live_audience_cost,
+        live_event_goods_view,
+        goods_click_ratio,
+        direct_attr_plat_new_buyer_cnt,
+        t30_attr_plat_total_buyer_cnt,
+        direct_attr_seller_new_buyer_cnt,
+        t30_attr_seller_total_buyer_cnt,
+        fans_t0_gmv,
+        fans_t1_gmv,
+        fans_t7_gmv,
+        fans_t15_gmv,
+        fans_t30_gmv,
+        fans_t0_roi,
+        fans_t1_roi,
+        fans_t7_roi,
+        fans_t15_roi,
+        fans_t30_roi,
+        report_date
+
+        )
+        values
+        <foreach collection="list" item="report" separator=",">
+            (
+            #{report.accountId},
+            #{report.costTotal},
+            #{report.adShow},
+            #{report.adShow1kCost},
+            #{report.impression},
+            #{report.photoClick},
+            #{report.photoClickRatio},
+            #{report.click},
+            #{report.actionbarClick},
+            #{report.actionbarClickCost},
+            #{report.espClickRatio},
+            #{report.actionRatio},
+            #{report.adItemClickCount},
+            #{report.espLivePlayedSeconds},
+            #{report.playedThreeSeconds},
+            #{report.play3sRatio},
+            #{report.playedFiveSeconds},
+            #{report.play5sRatio},
+            #{report.playedEnd},
+            #{report.playEndRatio},
+            #{report.share},
+            #{report.comment},
+            #{report.likes},
+            #{report.report},
+            #{report.block},
+            #{report.itemNegative},
+            #{report.liveShare},
+            #{report.liveComment},
+            #{report.liveReward},
+            #{report.effectivePlayCount},
+            #{report.effectivePlayRatio},
+            #{report.adLivePlayed1mNum},
+            #{report.conversionNum},
+            #{report.conversionCostEsp},
+            #{report.gmv},
+            #{report.t0Gmv},
+            #{report.t1Gmv},
+            #{report.t7Gmv},
+            #{report.t15Gmv},
+            #{report.t30Gmv},
+            #{report.roi},
+            #{report.t0Roi},
+            #{report.t1Roi},
+            #{report.t7Roi},
+            #{report.t15Roi},
+            #{report.t30Roi},
+            #{report.paiedOrder},
+            #{report.orderRatio},
+            #{report.t0OrderCnt},
+            #{report.t0OrderCntCost},
+            #{report.t0OrderCntRatio},
+            #{report.t1OrderCnt},
+            #{report.t7OrderCnt},
+            #{report.t15OrderCnt},
+            #{report.t30OrderCnt},
+            #{report.merchantRecoFans},
+            #{report.t1Retention},
+            #{report.t7Retention},
+            #{report.t15Retention},
+            #{report.t30Retention},
+            #{report.t1RetentionRatio},
+            #{report.t7RetentionRatio},
+            #{report.t15RetentionRatio},
+            #{report.t30RetentionRatio},
+            #{report.reservationSuccess},
+            #{report.reservationCost},
+            #{report.standardLivePlayedStarted},
+            #{report.liveAudienceCost},
+            #{report.liveEventGoodsView},
+            #{report.goodsClickRatio},
+            #{report.directAttrPlatNewBuyerCnt},
+            #{report.t30AttrPlatTotalBuyerCnt},
+            #{report.directAttrSellerNewBuyerCnt},
+            #{report.t30AttrSellerTotalBuyerCnt},
+            #{report.fansT0Gmv},
+            #{report.fansT1Gmv},
+            #{report.fansT7Gmv},
+            #{report.fansT15Gmv},
+            #{report.fansT30Gmv},
+            #{report.fansT0Roi},
+            #{report.fansT1Roi},
+            #{report.fansT7Roi},
+            #{report.fansT15Roi},
+            #{report.fansT30Roi},
+            #{report.reportDate}
+            )
+        </foreach>
+    </insert>
+
+</mapper>

+ 194 - 0
job-live/src/main/java/cn/com/ctop/job/live/mapper/xml/KuaishouLiveCampaignReportDailyMapper.xml

@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.com.ctop.job.live.mapper.KuaishouLiveCampaignReportDailyMapper">
+
+
+    <insert id="replaceBatch">
+        replace into kuaishou_live_campaign_report_daily(
+        account_id,
+        campaign_id,
+        campaign_name,
+        campaign_type_str,
+        cost_total,
+        ad_show,
+        ad_show1k_cost,
+        impression,
+        photo_click,
+        photo_click_ratio,
+        click,
+        actionbar_click,
+        actionbar_click_cost,
+        esp_click_ratio,
+        action_ratio,
+        ad_item_click_count,
+        esp_live_played_seconds,
+        played_three_seconds,
+        play3s_ratio,
+        played_five_seconds,
+        play5s_ratio,
+        played_end,
+        play_end_ratio,
+        share,
+        comment,
+        likes,
+        report,
+        block,
+        item_negative,
+        live_share,
+        live_comment,
+        live_reward,
+        effective_play_count,
+        effective_play_ratio,
+        ad_live_played1m_num,
+        conversion_num,
+        conversion_cost_esp,
+        gmv,
+        t0_gmv,
+        t1_gmv,
+        t7_gmv,
+        t15_gmv,
+        t30_gmv,
+        roi,
+        t0_roi,
+        t1_roi,
+        t7_roi,
+        t15_roi,
+        t30_roi,
+        paied_order,
+        order_ratio,
+        t0_order_cnt,
+        t0_order_cnt_cost,
+        t0_order_cnt_ratio,
+        t1_order_cnt,
+        t7_order_cnt,
+        t15_order_cnt,
+        t30_order_cnt,
+        merchant_reco_fans,
+        t1_retention,
+        t7_retention,
+        t15_retention,
+        t30_retention,
+        t1_retention_ratio,
+        t7_retention_ratio,
+        t15_retention_ratio,
+        t30_retention_ratio,
+        reservation_success,
+        reservation_cost,
+        standard_live_played_started,
+        live_audience_cost,
+        live_event_goods_view,
+        goods_click_ratio,
+        direct_attr_plat_new_buyer_cnt,
+        t30_attr_plat_total_buyer_cnt,
+        direct_attr_seller_new_buyer_cnt,
+        t30_attr_seller_total_buyer_cnt,
+        fans_t0_gmv,
+        fans_t1_gmv,
+        fans_t7_gmv,
+        fans_t15_gmv,
+        fans_t30_gmv,
+        fans_t0_roi,
+        fans_t1_roi,
+        fans_t7_roi,
+        fans_t15_roi,
+        fans_t30_roi,
+        report_date
+
+        )
+        values
+        <foreach collection="list" item="report" separator=",">
+            (
+            #{report.accountId},
+            #{report.campaignId},
+            #{report.campaignName},
+            #{report.campaignTypeStr},
+            #{report.costTotal},
+            #{report.adShow},
+            #{report.adShow1kCost},
+            #{report.impression},
+            #{report.photoClick},
+            #{report.photoClickRatio},
+            #{report.click},
+            #{report.actionbarClick},
+            #{report.actionbarClickCost},
+            #{report.espClickRatio},
+            #{report.actionRatio},
+            #{report.adItemClickCount},
+            #{report.espLivePlayedSeconds},
+            #{report.playedThreeSeconds},
+            #{report.play3sRatio},
+            #{report.playedFiveSeconds},
+            #{report.play5sRatio},
+            #{report.playedEnd},
+            #{report.playEndRatio},
+            #{report.share},
+            #{report.comment},
+            #{report.likes},
+            #{report.report},
+            #{report.block},
+            #{report.itemNegative},
+            #{report.liveShare},
+            #{report.liveComment},
+            #{report.liveReward},
+            #{report.effectivePlayCount},
+            #{report.effectivePlayRatio},
+            #{report.adLivePlayed1mNum},
+            #{report.conversionNum},
+            #{report.conversionCostEsp},
+            #{report.gmv},
+            #{report.t0Gmv},
+            #{report.t1Gmv},
+            #{report.t7Gmv},
+            #{report.t15Gmv},
+            #{report.t30Gmv},
+            #{report.roi},
+            #{report.t0Roi},
+            #{report.t1Roi},
+            #{report.t7Roi},
+            #{report.t15Roi},
+            #{report.t30Roi},
+            #{report.paiedOrder},
+            #{report.orderRatio},
+            #{report.t0OrderCnt},
+            #{report.t0OrderCntCost},
+            #{report.t0OrderCntRatio},
+            #{report.t1OrderCnt},
+            #{report.t7OrderCnt},
+            #{report.t15OrderCnt},
+            #{report.t30OrderCnt},
+            #{report.merchantRecoFans},
+            #{report.t1Retention},
+            #{report.t7Retention},
+            #{report.t15Retention},
+            #{report.t30Retention},
+            #{report.t1RetentionRatio},
+            #{report.t7RetentionRatio},
+            #{report.t15RetentionRatio},
+            #{report.t30RetentionRatio},
+            #{report.reservationSuccess},
+            #{report.reservationCost},
+            #{report.standardLivePlayedStarted},
+            #{report.liveAudienceCost},
+            #{report.liveEventGoodsView},
+            #{report.goodsClickRatio},
+            #{report.directAttrPlatNewBuyerCnt},
+            #{report.t30AttrPlatTotalBuyerCnt},
+            #{report.directAttrSellerNewBuyerCnt},
+            #{report.t30AttrSellerTotalBuyerCnt},
+            #{report.fansT0Gmv},
+            #{report.fansT1Gmv},
+            #{report.fansT7Gmv},
+            #{report.fansT15Gmv},
+            #{report.fansT30Gmv},
+            #{report.fansT0Roi},
+            #{report.fansT1Roi},
+            #{report.fansT7Roi},
+            #{report.fansT15Roi},
+            #{report.fansT30Roi},
+            #{report.reportDate}
+            )
+        </foreach>
+    </insert>
+
+
+</mapper>

+ 209 - 0
job-live/src/main/java/cn/com/ctop/job/live/mapper/xml/KuaishouLiveCreativeReportDailyMapper.xml

@@ -0,0 +1,209 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.com.ctop.job.live.mapper.KuaishouLiveCreativeReportDailyMapper">
+
+
+    <insert id="replaceBatch">
+        replace into kuaishou_live_creative_report_daily(
+        account_id,
+        campaign_id,
+        campaign_name,
+        unit_id,
+        unit_name,
+        creative_id,
+        creative_name,
+        live_creative_type_str,
+        creative_source_type_str,
+        ocpc_action_type_str,
+        speed_type_str,
+        campaign_type_str,
+        cost_total,
+        ad_show,
+        ad_show1k_cost,
+        impression,
+        photo_click,
+        photo_click_ratio,
+        click,
+        actionbar_click,
+        actionbar_click_cost,
+        esp_click_ratio,
+        action_ratio,
+        ad_item_click_count,
+        esp_live_played_seconds,
+        played_three_seconds,
+        play3s_ratio,
+        played_five_seconds,
+        play5s_ratio,
+        played_end,
+        play_end_ratio,
+        share,
+        comment,
+        likes,
+        report,
+        block,
+        item_negative,
+        live_share,
+        live_comment,
+        live_reward,
+        effective_play_count,
+        effective_play_ratio,
+        ad_live_played1m_num,
+        conversion_num,
+        conversion_cost_esp,
+        gmv,
+        t0_gmv,
+        t1_gmv,
+        t7_gmv,
+        t15_gmv,
+        t30_gmv,
+        roi,
+        t0_roi,
+        t1_roi,
+        t7_roi,
+        t15_roi,
+        t30_roi,
+        paied_order,
+        order_ratio,
+        t0_order_cnt,
+        t0_order_cnt_cost,
+        t0_order_cnt_ratio,
+        t1_order_cnt,
+        t7_order_cnt,
+        t15_order_cnt,
+        t30_order_cnt,
+        merchant_reco_fans,
+        t1_retention,
+        t7_retention,
+        t15_retention,
+        t30_retention,
+        t1_retention_ratio,
+        t7_retention_ratio,
+        t15_retention_ratio,
+        t30_retention_ratio,
+        reservation_success,
+        reservation_cost,
+        standard_live_played_started,
+        live_audience_cost,
+        live_event_goods_view,
+        goods_click_ratio,
+        direct_attr_plat_new_buyer_cnt,
+        t30_attr_plat_total_buyer_cnt,
+        direct_attr_seller_new_buyer_cnt,
+        t30_attr_seller_total_buyer_cnt,
+        fans_t0_gmv,
+        fans_t1_gmv,
+        fans_t7_gmv,
+        fans_t15_gmv,
+        fans_t30_gmv,
+        fans_t0_roi,
+        fans_t1_roi,
+        fans_t7_roi,
+        fans_t15_roi,
+        fans_t30_roi,
+        report_date
+
+        )
+        values
+        <foreach collection="list" item="report" separator=",">
+            (
+            #{report.accountId},
+            #{report.campaignId},
+            #{report.campaignName},
+            #{report.unitId},
+            #{report.unitName},
+            #{report.creativeId},
+            #{report.creativeName},
+            #{report.liveCreativeTypeStr},
+            #{report.creativeSourceTypeStr},
+            #{report.ocpcActionTypeStr},
+            #{report.speedTypeStr},
+            #{report.campaignTypeStr},
+            #{report.costTotal},
+            #{report.adShow},
+            #{report.adShow1kCost},
+            #{report.impression},
+            #{report.photoClick},
+            #{report.photoClickRatio},
+            #{report.click},
+            #{report.actionbarClick},
+            #{report.actionbarClickCost},
+            #{report.espClickRatio},
+            #{report.actionRatio},
+            #{report.adItemClickCount},
+            #{report.espLivePlayedSeconds},
+            #{report.playedThreeSeconds},
+            #{report.play3sRatio},
+            #{report.playedFiveSeconds},
+            #{report.play5sRatio},
+            #{report.playedEnd},
+            #{report.playEndRatio},
+            #{report.share},
+            #{report.comment},
+            #{report.likes},
+            #{report.report},
+            #{report.block},
+            #{report.itemNegative},
+            #{report.liveShare},
+            #{report.liveComment},
+            #{report.liveReward},
+            #{report.effectivePlayCount},
+            #{report.effectivePlayRatio},
+            #{report.adLivePlayed1mNum},
+            #{report.conversionNum},
+            #{report.conversionCostEsp},
+            #{report.gmv},
+            #{report.t0Gmv},
+            #{report.t1Gmv},
+            #{report.t7Gmv},
+            #{report.t15Gmv},
+            #{report.t30Gmv},
+            #{report.roi},
+            #{report.t0Roi},
+            #{report.t1Roi},
+            #{report.t7Roi},
+            #{report.t15Roi},
+            #{report.t30Roi},
+            #{report.paiedOrder},
+            #{report.orderRatio},
+            #{report.t0OrderCnt},
+            #{report.t0OrderCntCost},
+            #{report.t0OrderCntRatio},
+            #{report.t1OrderCnt},
+            #{report.t7OrderCnt},
+            #{report.t15OrderCnt},
+            #{report.t30OrderCnt},
+            #{report.merchantRecoFans},
+            #{report.t1Retention},
+            #{report.t7Retention},
+            #{report.t15Retention},
+            #{report.t30Retention},
+            #{report.t1RetentionRatio},
+            #{report.t7RetentionRatio},
+            #{report.t15RetentionRatio},
+            #{report.t30RetentionRatio},
+            #{report.reservationSuccess},
+            #{report.reservationCost},
+            #{report.standardLivePlayedStarted},
+            #{report.liveAudienceCost},
+            #{report.liveEventGoodsView},
+            #{report.goodsClickRatio},
+            #{report.directAttrPlatNewBuyerCnt},
+            #{report.t30AttrPlatTotalBuyerCnt},
+            #{report.directAttrSellerNewBuyerCnt},
+            #{report.t30AttrSellerTotalBuyerCnt},
+            #{report.fansT0Gmv},
+            #{report.fansT1Gmv},
+            #{report.fansT7Gmv},
+            #{report.fansT15Gmv},
+            #{report.fansT30Gmv},
+            #{report.fansT0Roi},
+            #{report.fansT1Roi},
+            #{report.fansT7Roi},
+            #{report.fansT15Roi},
+            #{report.fansT30Roi},
+            #{report.reportDate}
+            )
+        </foreach>
+    </insert>
+
+</mapper>

+ 204 - 0
job-live/src/main/java/cn/com/ctop/job/live/mapper/xml/KuaishouLiveUnitReportDailyMapper.xml

@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.com.ctop.job.live.mapper.KuaishouLiveUnitReportDailyMapper">
+
+
+    <insert id="replaceBatch">
+        replace into kuaishou_live_unit_report_daily(
+        account_id,
+        campaign_id,
+        campaign_name,
+        unit_id,
+        unit_name,
+        ocpc_action_type_str,
+        speed_type_str,
+        creative_build_type_str,
+        campaign_type_str,
+        cost_total,
+        ad_show,
+        ad_show1k_cost,
+        impression,
+        photo_click,
+        photo_click_ratio,
+        click,
+        actionbar_click,
+        actionbar_click_cost,
+        esp_click_ratio,
+        action_ratio,
+        ad_item_click_count,
+        esp_live_played_seconds,
+        played_three_seconds,
+        play3s_ratio,
+        played_five_seconds,
+        play5s_ratio,
+        played_end,
+        play_end_ratio,
+        share,
+        comment,
+        likes,
+        report,
+        block,
+        item_negative,
+        live_share,
+        live_comment,
+        live_reward,
+        effective_play_count,
+        effective_play_ratio,
+        ad_live_played1m_num,
+        conversion_num,
+        conversion_cost_esp,
+        gmv,
+        t0_gmv,
+        t1_gmv,
+        t7_gmv,
+        t15_gmv,
+        t30_gmv,
+        roi,
+        t0_roi,
+        t1_roi,
+        t7_roi,
+        t15_roi,
+        t30_roi,
+        paied_order,
+        order_ratio,
+        t0_order_cnt,
+        t0_order_cnt_cost,
+        t0_order_cnt_ratio,
+        t1_order_cnt,
+        t7_order_cnt,
+        t15_order_cnt,
+        t30_order_cnt,
+        merchant_reco_fans,
+        t1_retention,
+        t7_retention,
+        t15_retention,
+        t30_retention,
+        t1_retention_ratio,
+        t7_retention_ratio,
+        t15_retention_ratio,
+        t30_retention_ratio,
+        reservation_success,
+        reservation_cost,
+        standard_live_played_started,
+        live_audience_cost,
+        live_event_goods_view,
+        goods_click_ratio,
+        direct_attr_plat_new_buyer_cnt,
+        t30_attr_plat_total_buyer_cnt,
+        direct_attr_seller_new_buyer_cnt,
+        t30_attr_seller_total_buyer_cnt,
+        fans_t0_gmv,
+        fans_t1_gmv,
+        fans_t7_gmv,
+        fans_t15_gmv,
+        fans_t30_gmv,
+        fans_t0_roi,
+        fans_t1_roi,
+        fans_t7_roi,
+        fans_t15_roi,
+        fans_t30_roi,
+        report_date
+
+        )
+        values
+        <foreach collection="list" item="report" separator=",">
+            (
+            #{report.accountId},
+            #{report.campaignId},
+            #{report.campaignName},
+            #{report.unitId},
+            #{report.unitName},
+            #{report.ocpcActionTypeStr},
+            #{report.speedTypeStr},
+            #{report.creativeBuildTypeStr},
+            #{report.campaignTypeStr},
+            #{report.costTotal},
+            #{report.adShow},
+            #{report.adShow1kCost},
+            #{report.impression},
+            #{report.photoClick},
+            #{report.photoClickRatio},
+            #{report.click},
+            #{report.actionbarClick},
+            #{report.actionbarClickCost},
+            #{report.espClickRatio},
+            #{report.actionRatio},
+            #{report.adItemClickCount},
+            #{report.espLivePlayedSeconds},
+            #{report.playedThreeSeconds},
+            #{report.play3sRatio},
+            #{report.playedFiveSeconds},
+            #{report.play5sRatio},
+            #{report.playedEnd},
+            #{report.playEndRatio},
+            #{report.share},
+            #{report.comment},
+            #{report.likes},
+            #{report.report},
+            #{report.block},
+            #{report.itemNegative},
+            #{report.liveShare},
+            #{report.liveComment},
+            #{report.liveReward},
+            #{report.effectivePlayCount},
+            #{report.effectivePlayRatio},
+            #{report.adLivePlayed1mNum},
+            #{report.conversionNum},
+            #{report.conversionCostEsp},
+            #{report.gmv},
+            #{report.t0Gmv},
+            #{report.t1Gmv},
+            #{report.t7Gmv},
+            #{report.t15Gmv},
+            #{report.t30Gmv},
+            #{report.roi},
+            #{report.t0Roi},
+            #{report.t1Roi},
+            #{report.t7Roi},
+            #{report.t15Roi},
+            #{report.t30Roi},
+            #{report.paiedOrder},
+            #{report.orderRatio},
+            #{report.t0OrderCnt},
+            #{report.t0OrderCntCost},
+            #{report.t0OrderCntRatio},
+            #{report.t1OrderCnt},
+            #{report.t7OrderCnt},
+            #{report.t15OrderCnt},
+            #{report.t30OrderCnt},
+            #{report.merchantRecoFans},
+            #{report.t1Retention},
+            #{report.t7Retention},
+            #{report.t15Retention},
+            #{report.t30Retention},
+            #{report.t1RetentionRatio},
+            #{report.t7RetentionRatio},
+            #{report.t15RetentionRatio},
+            #{report.t30RetentionRatio},
+            #{report.reservationSuccess},
+            #{report.reservationCost},
+            #{report.standardLivePlayedStarted},
+            #{report.liveAudienceCost},
+            #{report.liveEventGoodsView},
+            #{report.goodsClickRatio},
+            #{report.directAttrPlatNewBuyerCnt},
+            #{report.t30AttrPlatTotalBuyerCnt},
+            #{report.directAttrSellerNewBuyerCnt},
+            #{report.t30AttrSellerTotalBuyerCnt},
+            #{report.fansT0Gmv},
+            #{report.fansT1Gmv},
+            #{report.fansT7Gmv},
+            #{report.fansT15Gmv},
+            #{report.fansT30Gmv},
+            #{report.fansT0Roi},
+            #{report.fansT1Roi},
+            #{report.fansT7Roi},
+            #{report.fansT15Roi},
+            #{report.fansT30Roi},
+            #{report.reportDate}
+            )
+        </foreach>
+    </insert>
+
+
+</mapper>

+ 14 - 0
job-live/src/main/java/cn/com/ctop/job/live/service/IKuaishouLiveAccountReportDailyService.java

@@ -0,0 +1,14 @@
+package cn.com.ctop.job.live.service;
+
+import cn.com.ctop.job.live.utils.Result;
+
+/**
+ * 直播账户报Service接口
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+public interface IKuaishouLiveAccountReportDailyService {
+
+    Result<Object> getKuaishouAccountReport(Long accountId, String token, int page);
+}

+ 14 - 0
job-live/src/main/java/cn/com/ctop/job/live/service/IKuaishouLiveCampaignReportDailyService.java

@@ -0,0 +1,14 @@
+package cn.com.ctop.job.live.service;
+
+import cn.com.ctop.job.live.utils.Result;
+
+/**
+ * 直播计划报Service接口
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+public interface IKuaishouLiveCampaignReportDailyService {
+
+    Result<Object> getKuaishouCampaignReport(Long accountId, String token, int page);
+}

+ 14 - 0
job-live/src/main/java/cn/com/ctop/job/live/service/IKuaishouLiveCreativeReportDailyService.java

@@ -0,0 +1,14 @@
+package cn.com.ctop.job.live.service;
+
+import cn.com.ctop.job.live.utils.Result;
+
+/**
+ * 直播创意报Service接口
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+public interface IKuaishouLiveCreativeReportDailyService {
+
+    Result<Object> getKuaishouCreativeReport(Long accountId, String token, int page);
+}

+ 14 - 0
job-live/src/main/java/cn/com/ctop/job/live/service/IKuaishouLiveUnitReportDailyService.java

@@ -0,0 +1,14 @@
+package cn.com.ctop.job.live.service;
+
+import cn.com.ctop.job.live.utils.Result;
+
+/**
+ * 直播广告组报Service接口
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+public interface IKuaishouLiveUnitReportDailyService {
+
+    Result<Object> getKuaishouUnitReport(Long accountId, String token, int page);
+}

+ 75 - 0
job-live/src/main/java/cn/com/ctop/job/live/service/impl/KuaishouLiveAccountReportDailyServiceImpl.java

@@ -0,0 +1,75 @@
+package cn.com.ctop.job.live.service.impl;
+
+import cn.com.ctop.job.live.entity.KuaishouLiveAccountReportDaily;
+import cn.com.ctop.job.live.mapper.KuaishouLiveAccountReportDailyMapper;
+import cn.com.ctop.job.live.service.IKuaishouLiveAccountReportDailyService;
+import cn.com.ctop.job.live.utils.APIUtil;
+import cn.com.ctop.job.live.utils.Check;
+import cn.com.ctop.job.live.utils.DateUtils;
+import cn.com.ctop.job.live.utils.KuaishouAPIConstant;
+import cn.com.ctop.job.live.utils.Result;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 直播账户报Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+@Slf4j
+@Service
+public class KuaishouLiveAccountReportDailyServiceImpl implements IKuaishouLiveAccountReportDailyService {
+
+    @Autowired
+    private KuaishouLiveAccountReportDailyMapper mapper;
+
+    @Override
+    public Result<Object> getKuaishouAccountReport(Long accountId, String token, int page) {
+        log.info("调用接口---getKuaishouAccountReport(获取直播账户报表)---账户:{} ===第{}页===", accountId, page);
+        Result<Object> result = new Result<>();
+        Map<String, Object> param = new HashMap<String, Object>();
+        param.put("advertiser_id", accountId);
+        JSONObject params = new JSONObject();
+        String yesterday = DateUtils.getSubtractTime(new Date(), 0);
+//        String yesterday = "2022-06-14";
+        params.put("start_time", DateUtils.timeToStamp(yesterday + " 00:00:00"));
+        params.put("end_time", DateUtils.timeToStamp(yesterday + " 23:23:59"));
+        params.put("view_type", 1);
+        params.put("group_type", 1);
+        JSONObject pageInfo = new JSONObject();
+        pageInfo.put("page", 1);
+        pageInfo.put("page_size", 2000);
+        param.put("page_info", pageInfo);
+        param.put("param", params);
+        JSONObject apiResult = APIUtil.getApiResult(KuaishouAPIConstant.REPORT_LIST, token, param);
+        if (apiResult.getInteger("code") == 0) {
+            apiResult = apiResult.getJSONObject("data");
+            JSONArray data = apiResult.getJSONArray("data");
+            List<KuaishouLiveAccountReportDaily> list = new ArrayList<>();
+            for (int i = 0; i < data.size(); i++) {
+                JSONObject object = data.getJSONObject(i);
+                KuaishouLiveAccountReportDaily accountReport = JSONObject.parseObject(object.toJSONString(), KuaishouLiveAccountReportDaily.class);
+                accountReport.setAccountId(accountId);
+                accountReport.setReportDate(Long.valueOf(DateUtils.timestampToStr(object.getLong("report_date"))));
+                list.add(accountReport);
+            }
+            if (!Check.isNull(list)) {
+                mapper.replaceBatch(list);
+            }
+        } else {
+            log.error("获取直播账户报表,账户{},{}", accountId, apiResult.toJSONString());
+        }
+        return result.success("success");
+    }
+
+}

+ 74 - 0
job-live/src/main/java/cn/com/ctop/job/live/service/impl/KuaishouLiveCampaignReportDailyServiceImpl.java

@@ -0,0 +1,74 @@
+package cn.com.ctop.job.live.service.impl;
+
+import cn.com.ctop.job.live.entity.KuaishouLiveCampaignReportDaily;
+import cn.com.ctop.job.live.mapper.KuaishouLiveCampaignReportDailyMapper;
+import cn.com.ctop.job.live.service.IKuaishouLiveCampaignReportDailyService;
+import cn.com.ctop.job.live.utils.APIUtil;
+import cn.com.ctop.job.live.utils.Check;
+import cn.com.ctop.job.live.utils.DateUtils;
+import cn.com.ctop.job.live.utils.KuaishouAPIConstant;
+import cn.com.ctop.job.live.utils.Result;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 直播计划报Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+@Slf4j
+@Service
+public class KuaishouLiveCampaignReportDailyServiceImpl implements IKuaishouLiveCampaignReportDailyService {
+
+    @Autowired
+    private KuaishouLiveCampaignReportDailyMapper mapper;
+
+    @Override
+    public Result<Object> getKuaishouCampaignReport(Long accountId, String token, int page) {
+        log.info("调用接口---getKuaishouAccountReport(获取直播计划报表)---账户:{} ===第{}页===", accountId, page);
+        Result<Object> result = new Result<>();
+        Map<String, Object> param = new HashMap<String, Object>();
+        param.put("advertiser_id", accountId);
+        JSONObject params = new JSONObject();
+        String yesterday = DateUtils.getSubtractTime(new Date(), 0);
+//        String yesterday = "2022-06-14";
+        params.put("start_time", DateUtils.timeToStamp(yesterday + " 00:00:00"));
+        params.put("end_time", DateUtils.timeToStamp(yesterday + " 23:23:59"));
+        params.put("view_type", 2);
+        params.put("group_type", 1);
+        JSONObject pageInfo = new JSONObject();
+        pageInfo.put("page", 1);
+        pageInfo.put("page_size", 2000);
+        param.put("page_info", pageInfo);
+        param.put("param", params);
+        JSONObject apiResult = APIUtil.getApiResult(KuaishouAPIConstant.REPORT_LIST, token, param);
+        if (apiResult.getInteger("code") == 0) {
+            apiResult = apiResult.getJSONObject("data");
+            JSONArray data = apiResult.getJSONArray("data");
+            List<KuaishouLiveCampaignReportDaily> list = new ArrayList<>();
+            for (int i = 0; i < data.size(); i++) {
+                JSONObject object = data.getJSONObject(i);
+                KuaishouLiveCampaignReportDaily campaignReport = JSONObject.parseObject(object.toJSONString(), KuaishouLiveCampaignReportDaily.class);
+                campaignReport.setAccountId(accountId);
+                campaignReport.setReportDate(Long.valueOf(DateUtils.timestampToStr(object.getLong("report_date"))));
+                list.add(campaignReport);
+            }
+            if (!Check.isNull(list)) {
+                mapper.replaceBatch(list);
+            }
+        } else {
+            log.error("获取直播计划报表,账户{},{}", accountId, apiResult.toJSONString());
+        }
+        return result.success("success");
+    }
+}

+ 74 - 0
job-live/src/main/java/cn/com/ctop/job/live/service/impl/KuaishouLiveCreativeReportDailyServiceImpl.java

@@ -0,0 +1,74 @@
+package cn.com.ctop.job.live.service.impl;
+
+import cn.com.ctop.job.live.entity.KuaishouLiveCreativeReportDaily;
+import cn.com.ctop.job.live.mapper.KuaishouLiveCreativeReportDailyMapper;
+import cn.com.ctop.job.live.service.IKuaishouLiveCreativeReportDailyService;
+import cn.com.ctop.job.live.utils.APIUtil;
+import cn.com.ctop.job.live.utils.Check;
+import cn.com.ctop.job.live.utils.DateUtils;
+import cn.com.ctop.job.live.utils.KuaishouAPIConstant;
+import cn.com.ctop.job.live.utils.Result;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 直播创意报Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+@Slf4j
+@Service
+public class KuaishouLiveCreativeReportDailyServiceImpl implements IKuaishouLiveCreativeReportDailyService {
+
+    @Autowired
+    private KuaishouLiveCreativeReportDailyMapper mapper;
+
+    @Override
+    public Result<Object> getKuaishouCreativeReport(Long accountId, String token, int page) {
+        log.info("调用接口---getKuaishouAccountReport(获取直播创意报表)---账户:{} ===第{}页===", accountId, page);
+        Result<Object> result = new Result<>();
+        Map<String, Object> param = new HashMap<String, Object>();
+        param.put("advertiser_id", accountId);
+        JSONObject params = new JSONObject();
+        String yesterday = DateUtils.getSubtractTime(new Date(), 0);
+//        String yesterday = "2022-06-14";
+        params.put("start_time", DateUtils.timeToStamp(yesterday + " 00:00:00"));
+        params.put("end_time", DateUtils.timeToStamp(yesterday + " 23:23:59"));
+        params.put("view_type", 4);
+        params.put("group_type", 1);
+        JSONObject pageInfo = new JSONObject();
+        pageInfo.put("page", 1);
+        pageInfo.put("page_size", 2000);
+        param.put("page_info", pageInfo);
+        param.put("param", params);
+        JSONObject apiResult = APIUtil.getApiResult(KuaishouAPIConstant.REPORT_LIST, token, param);
+        if (apiResult.getInteger("code") == 0) {
+            apiResult = apiResult.getJSONObject("data");
+            JSONArray data = apiResult.getJSONArray("data");
+            List<KuaishouLiveCreativeReportDaily> list = new ArrayList<>();
+            for (int i = 0; i < data.size(); i++) {
+                JSONObject object = data.getJSONObject(i);
+                KuaishouLiveCreativeReportDaily creativeReport = JSONObject.parseObject(object.toJSONString(), KuaishouLiveCreativeReportDaily.class);
+                creativeReport.setAccountId(accountId);
+                creativeReport.setReportDate(Long.valueOf(DateUtils.timestampToStr(object.getLong("report_date"))));
+                list.add(creativeReport);
+            }
+            if (!Check.isNull(list)) {
+                mapper.replaceBatch(list);
+            }
+        } else {
+            log.error("获取直播创意报表,账户{},{}", accountId, apiResult.toJSONString());
+        }
+        return result.success("success");
+    }
+}

+ 74 - 0
job-live/src/main/java/cn/com/ctop/job/live/service/impl/KuaishouLiveUnitReportDailyServiceImpl.java

@@ -0,0 +1,74 @@
+package cn.com.ctop.job.live.service.impl;
+
+import cn.com.ctop.job.live.entity.KuaishouLiveUnitReportDaily;
+import cn.com.ctop.job.live.mapper.KuaishouLiveUnitReportDailyMapper;
+import cn.com.ctop.job.live.service.IKuaishouLiveUnitReportDailyService;
+import cn.com.ctop.job.live.utils.APIUtil;
+import cn.com.ctop.job.live.utils.Check;
+import cn.com.ctop.job.live.utils.DateUtils;
+import cn.com.ctop.job.live.utils.KuaishouAPIConstant;
+import cn.com.ctop.job.live.utils.Result;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 直播广告组报Service业务层处理
+ *
+ * @author ruoyi
+ * @date 2022-07-19
+ */
+@Slf4j
+@Service
+public class KuaishouLiveUnitReportDailyServiceImpl implements IKuaishouLiveUnitReportDailyService {
+
+    @Autowired
+    private KuaishouLiveUnitReportDailyMapper mapper;
+
+    @Override
+    public Result<Object> getKuaishouUnitReport(Long accountId, String token, int page) {
+        log.info("调用接口---getKuaishouAccountReport(获取直播广告组报表)---账户:{} ===第{}页===", accountId, page);
+        Result<Object> result = new Result<>();
+        Map<String, Object> param = new HashMap<String, Object>();
+        param.put("advertiser_id", accountId);
+        JSONObject params = new JSONObject();
+        String yesterday = DateUtils.getSubtractTime(new Date(), 0);
+//        String yesterday = "2022-06-14";
+        params.put("start_time", DateUtils.timeToStamp(yesterday + " 00:00:00"));
+        params.put("end_time", DateUtils.timeToStamp(yesterday + " 23:23:59"));
+        params.put("view_type", 3);
+        params.put("group_type", 1);
+        JSONObject pageInfo = new JSONObject();
+        pageInfo.put("page", 1);
+        pageInfo.put("page_size", 2000);
+        param.put("page_info", pageInfo);
+        param.put("param", params);
+        JSONObject apiResult = APIUtil.getApiResult(KuaishouAPIConstant.REPORT_LIST, token, param);
+        if (apiResult.getInteger("code") == 0) {
+            apiResult = apiResult.getJSONObject("data");
+            JSONArray data = apiResult.getJSONArray("data");
+            List<KuaishouLiveUnitReportDaily> list = new ArrayList<>();
+            for (int i = 0; i < data.size(); i++) {
+                JSONObject object = data.getJSONObject(i);
+                KuaishouLiveUnitReportDaily unitReport = JSONObject.parseObject(object.toJSONString(), KuaishouLiveUnitReportDaily.class);
+                unitReport.setAccountId(accountId);
+                unitReport.setReportDate(Long.valueOf(DateUtils.timestampToStr(object.getLong("report_date"))));
+                list.add(unitReport);
+            }
+            if (!Check.isNull(list)) {
+                mapper.replaceBatch(list);
+            }
+        } else {
+            log.error("获取直播广告组报表,账户{},{}", accountId, apiResult.toJSONString());
+        }
+        return result.success("success");
+    }
+}

+ 62 - 0
job-live/src/main/java/cn/com/ctop/job/live/utils/APIUtil.java

@@ -0,0 +1,62 @@
+package cn.com.ctop.job.live.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+public class APIUtil {
+
+    public static final String IP_PATH = "https://ad.e.kuaishou.com";
+
+    public static JSONObject getApiResult(String apiPath, String token, Map<String, Object> param) {
+        try {
+            String url = IP_PATH + apiPath;
+            Map<String, String> headers = new HashMap<String, String>();
+            headers.put("Content-Type", "application/json");
+            headers.put("Access-Token", token);
+            System.out.println(JSONObject.toJSON(param));//TODO
+            String result = HttpUtils.httpPostRequest(url, param, headers);
+            JSONObject resultJson = JSONObject.parseObject(result);
+            if (resultJson.toString().length() < 500) {
+                System.out.println(JSONObject.toJSON(resultJson)); //TODO
+            }
+            return resultJson;
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.error("调用媒体接口异常,账户:{},接口:{}", param.get("advertiser_id"), apiPath);
+            JSONObject result = new JSONObject();
+            result.put("code", -1);
+            result.put("message", "调用媒体接口异常");
+            return result;
+        }
+    }
+
+
+    public static JSONObject getApiResultByGet(String apiPath, String token, JSONObject param) {
+        try {
+            String url = IP_PATH + apiPath;
+            Map<String, String> headers = new HashMap<String, String>();
+            headers.put("Content-Type", "application/json");
+            headers.put("Access-Token", token);
+            System.out.println(JSONObject.toJSON(param)); //TODO
+            String result = HttpUtils.httpGetRequest(url, headers, param);
+            JSONObject resultJson = JSONObject.parseObject(result);
+            if (resultJson.toString().length() < 500) {
+                System.out.println(JSONObject.toJSON(resultJson)); //TODO
+            }
+            return resultJson;
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.error("调用媒体接口异常,账户:{},接口:{}", param.get("advertiser_id"), apiPath);
+            JSONObject result = new JSONObject();
+            result.put("code", -1);
+            result.put("message", "调用媒体接口异常");
+            return result;
+        }
+    }
+
+}

+ 66 - 0
job-live/src/main/java/cn/com/ctop/job/live/utils/Check.java

@@ -0,0 +1,66 @@
+package cn.com.ctop.job.live.utils;
+
+import com.alibaba.fastjson.JSONArray;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+public class Check {
+    private Check() {
+    }
+
+    public static boolean isNull(List<?> list) {
+        return list == null || list.isEmpty();
+    }
+
+    public static boolean isNull(Long l) {
+        return l == null || l == 0;
+    }
+
+    public static boolean isNull(Collection<?> collection) {
+        return collection == null || collection.isEmpty();
+    }
+
+    public static boolean isNull(JSONArray jsonArray) {
+        return jsonArray == null || jsonArray.isEmpty();
+    }
+
+
+    public static boolean isNull(String str) {
+        return str == null || str.isEmpty();
+    }
+
+    public static boolean isNull(byte[] bytes) {
+        return bytes == null || bytes.length == 0;
+    }
+
+    public static boolean hasNull(Object... objects) {
+        for (Object obj : objects) {
+            if (obj == null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean allEmpty(String... strs) {
+        for (String str : strs) {
+            if (StringUtils.isNotEmpty(str)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean isNull(Object object) {
+        return object == null;
+    }
+
+    public static boolean isNullMap(Map<?, ?> map) {
+        return map == null || map.isEmpty();
+    }
+}

+ 303 - 0
job-live/src/main/java/cn/com/ctop/job/live/utils/DateUtils.java

@@ -0,0 +1,303 @@
+package cn.com.ctop.job.live.utils;
+
+import org.apache.commons.lang3.time.DateFormatUtils;
+
+import java.lang.management.ManagementFactory;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 时间工具类
+ *
+ * @author ruoyi
+ */
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
+    public static String YYYY = "yyyy";
+
+    public static String YYYY_MM = "yyyy-MM";
+
+    public static String YYYY_MM_DD = "yyyy-MM-dd";
+
+    public static String YYYYMMDD = "yyyyMMdd";
+
+    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+    private static String[] parsePatterns = {
+            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+    /**
+     * 获取当前Date型日期
+     *
+     * @return Date() 当前日期
+     */
+    public static Date getNowDate() {
+        return new Date();
+    }
+
+    /**
+     * 获取当前日期, 默认格式为yyyy-MM-dd
+     *
+     * @return String
+     */
+    public static String getDate() {
+        return dateTimeNow(YYYY_MM_DD);
+    }
+
+    public static final String getTime() {
+        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+    }
+
+    public static final String dateTimeNow() {
+        return dateTimeNow(YYYYMMDDHHMMSS);
+    }
+
+    public static final String dateTimeNow(final String format) {
+        return parseDateToStr(format, new Date());
+    }
+
+    public static final String dateTime(final Date date) {
+        return parseDateToStr(YYYY_MM_DD, date);
+    }
+
+    public static final String parseDateToStr(final String format, final Date date) {
+        return new SimpleDateFormat(format).format(date);
+    }
+
+    public static final Date dateTime(final String format, final String ts) {
+        try {
+            return new SimpleDateFormat(format).parse(ts);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 日期路径 即年/月/日 如2018/08/08
+     */
+    public static final String datePath() {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyy/MM/dd");
+    }
+
+    /**
+     * 日期路径 即年/月/日 如20180808
+     */
+    public static final String dateTime() {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyyMMdd");
+    }
+
+    /**
+     * 日期型字符串转化为日期 格式
+     */
+    public static Date parseDate(Object str) {
+        if (str == null) {
+            return null;
+        }
+        try {
+            return parseDate(str.toString(), parsePatterns);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 获取服务器启动时间
+     */
+    public static Date getServerStartDate() {
+        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+        return new Date(time);
+    }
+
+    /**
+     * 计算相差天数
+     */
+    public static int differentDaysByMillisecond(Date date1, Date date2) {
+        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
+    }
+
+    /**
+     * 计算两个时间差
+     */
+    public static String getDatePoor(Date endDate, Date nowDate) {
+        long nd = 1000 * 24 * 60 * 60;
+        long nh = 1000 * 60 * 60;
+        long nm = 1000 * 60;
+        // long ns = 1000;
+        // 获得两个时间的毫秒时间差异
+        long diff = endDate.getTime() - nowDate.getTime();
+        // 计算差多少天
+        long day = diff / nd;
+        // 计算差多少小时
+        long hour = diff % nd / nh;
+        // 计算差多少分钟
+        long min = diff % nd % nh / nm;
+        // 计算差多少秒//输出结果
+        // long sec = diff % nd % nh % nm / ns;
+        return day + "天" + hour + "小时" + min + "分钟";
+    }
+
+    /**
+     * 增加 LocalDateTime ==> Date
+     */
+    public static Date toDate(LocalDateTime temporalAccessor) {
+        ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
+        return Date.from(zdt.toInstant());
+    }
+
+    /**
+     * 增加 LocalDate ==> Date
+     */
+    public static Date toDate(LocalDate temporalAccessor) {
+        LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
+        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
+        return Date.from(zdt.toInstant());
+    }
+
+
+    /**
+     * 时间字符串转long类型
+     * 2022-05-01 ===>>>> 20220501
+     *
+     * @param date
+     * @return
+     */
+    public static Long strDateToInt(String date) {
+
+        return Long.valueOf(date.replaceAll("-", ""));
+    }
+
+
+    /**
+     * @param
+     * @description: 获取当前周的开始和截至时间
+     * @return: java.util.Map
+     * @author: zianY
+     * @time: 2022/6/1
+     */
+    public static Map getWeekBeginAndEnd() {
+        Map<String, String> timeMap = new HashMap<>();
+        String begin = parseDateToStr(YYYY_MM_DD, getBeginDayOfWeek());
+        String end = parseDateToStr(YYYY_MM_DD, getEendDayOfWeek());
+        timeMap.put("begin", begin + " 00:00:00");
+        timeMap.put("end", end + " 23:59:59");
+        return timeMap;
+    }
+
+
+    /**
+     * @param
+     * @description: 获取本周开始时间
+     * @return: java.util.Date
+     * @author: zianY
+     * @time: 2022/6/1
+     */
+    public static Date getBeginDayOfWeek() {
+        Date date = new Date();
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
+        if (dayOfWeek == 1) {
+            dayOfWeek += 7;
+        }
+        cal.add(Calendar.DATE, 2 - dayOfWeek);
+        return cal.getTime();
+    }
+
+    /**
+     * @param
+     * @description: 获取本周结束时间
+     * @return: java.util.Date
+     * @author: zianY
+     * @time: 2022/6/1
+     */
+    public static Date getEendDayOfWeek() {
+
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(getBeginDayOfWeek());
+        cal.add(Calendar.DAY_OF_WEEK, 6);
+        return cal.getTime();
+    }
+
+    public static void main(String[] args) {
+        //System.out.println(parseDateToStr(YYYY_MM_DD,getBeginDayOfWeek()));
+        // System.out.println(parseDateToStr(YYYY_MM_DD,getEendDayOfWeek()));
+
+        System.out.println(strDateToInt("2022-05"));
+    }
+
+    /**
+     * 指定模式的时间格式
+     */
+    private static SimpleDateFormat getSDFormat(String pattern) {
+        return new SimpleDateFormat(pattern);
+    }
+
+    /**
+     * 指定日期按指定格式显示
+     */
+    public static String formatDate(Date date, String pattern) {
+        return getSDFormat(pattern).format(date);
+    }
+
+
+    /**
+     * 日期转换为时间戳
+     */
+    public static long timeToStamp(String timers) {
+        Date d = new Date();
+        long timeStemp = 0;
+        try {
+            SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            // 日期转换为时间戳
+            d = sf.parse(timers);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        timeStemp = d.getTime();
+        return timeStemp;
+    }
+
+    /**
+     * 返回 日期 - 天数 的string 日期
+     *
+     * @param time        时间
+     * @param daysBetween 天数
+     * @return
+     */
+    public static String getSubtractTime(Date time, Integer daysBetween) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Calendar rightNow = Calendar.getInstance();
+        rightNow.setTime(time);
+        if (daysBetween.intValue() > 0) {
+            rightNow.add(Calendar.DAY_OF_YEAR, -daysBetween.intValue());
+        } else {
+            rightNow.add(Calendar.DAY_OF_YEAR, -1);
+        }
+        Date rightNowTime = rightNow.getTime();
+        String nowTime = sdf.format(rightNowTime);
+        return nowTime;
+    }
+
+    /**
+     * 时间戳转换为字符串
+     */
+    public static String timestampToStr(Long timestamp) {
+        Date date = new Date();
+        date.setTime(timestamp);
+        return (DateFormatUtils.format(date, YYYYMMDD));
+    }
+}

+ 28 - 0
job-live/src/main/java/cn/com/ctop/job/live/utils/HttpGetWithBodyEntity.java

@@ -0,0 +1,28 @@
+package cn.com.ctop.job.live.utils;
+
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+
+import java.net.URI;
+
+public class HttpGetWithBodyEntity extends HttpEntityEnclosingRequestBase {
+    private final static String METHOD_NAME = "GET";
+
+    @Override
+    public String getMethod() {
+        return METHOD_NAME;
+    }
+
+    public HttpGetWithBodyEntity() {
+        super();
+    }
+
+    public HttpGetWithBodyEntity(final URI uri) {
+        super();
+        setURI(uri);
+    }
+
+    HttpGetWithBodyEntity(final String uri) {
+        super();
+        setURI(URI.create(uri));
+    }
+}

+ 137 - 0
job-live/src/main/java/cn/com/ctop/job/live/utils/HttpUtils.java

@@ -0,0 +1,137 @@
+package cn.com.ctop.job.live.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.http.util.EntityUtils;
+
+import javax.net.ssl.SSLContext;
+import java.nio.charset.Charset;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+@Slf4j
+public class HttpUtils {
+
+  public static CookieStore COOKIESTORE = new BasicCookieStore();
+  /** setProxy(new HttpHost("106.125.239.179", 4245)) */
+  public static Map<String, Map<String, String>> fontsMap = new HashMap<>();
+
+  public static String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36";
+
+  public static CloseableHttpClient createSslClientDefault() {
+    try {
+      SSLContext sslContext =
+          new SSLContextBuilder()
+              .loadTrustMaterial(
+                  null,
+                  new TrustStrategy() {
+                    // 信任所有证书
+                    @Override
+                    public boolean isTrusted(X509Certificate[] chain, String authType)
+                        throws CertificateException {
+                      return true;
+                    }
+                  })
+              .build();
+      RequestConfig globalConfig =
+          RequestConfig.custom()
+              .setConnectTimeout(30000)
+              .setSocketTimeout(30000)
+              .setCookieSpec(CookieSpecs.STANDARD)
+              .build();
+      SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext);
+      return HttpClients.custom()
+          .setDefaultCookieStore(COOKIESTORE)
+          .setDefaultRequestConfig(globalConfig)
+          .setConnectionReuseStrategy((response, context) -> false)
+          .setSSLSocketFactory(sslFactory)
+          .build();
+    } catch (Exception e) {
+      log.error("处理Https证书异常", e);
+    }
+    return HttpClients.createDefault();
+  }
+
+  public static String httpPostRequest(String url, Map<String, Object> param, Map<String, String> headers) {
+    HttpClient httpClient = createSslClientDefault();
+    String strReturn = "";
+    try {
+      HttpPost httppost = new HttpPost(url);
+      httppost.setHeader("User-Agent", USER_AGENT);
+      httppost.addHeader("Content-Type", "application/json");
+      if (headers != null && headers.size() > 0) {
+        for (String key : headers.keySet()) {
+          httppost.setHeader(key, headers.get(key));
+        }
+      }
+      if (param != null && param.size() > 0) {
+        httppost.setEntity(new StringEntity(new Gson().toJson(param), Charset.forName("UTF-8")));
+      }
+      RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(300 * 1000).setConnectTimeout(300 * 1000).build();
+      httppost.setConfig(requestConfig);
+      HttpEntity respentity;
+
+      HttpResponse response = httpClient.execute(httppost);
+      int statusCode = response.getStatusLine().getStatusCode();
+      if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
+        String newUrl = response.getFirstHeader("Location").getValue();
+        return httpPostRequest(newUrl, param, headers);
+      } else if (statusCode == HttpStatus.SC_OK) {
+        respentity = response.getEntity();
+        strReturn = EntityUtils.toString(respentity);
+        return strReturn;
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      log.error(e.getMessage());
+    }
+    return strReturn;
+  }
+
+
+  public static String httpGetRequest(String url, Map<String, String> headers, JSONObject params) {
+    HttpClient httpClient = createSslClientDefault();
+    String strReturn = "";
+    try {
+      HttpGetWithBodyEntity httpGet = new HttpGetWithBodyEntity(url);
+      if (headers != null) {
+        Iterator<String> keyIter = headers.keySet().iterator();
+        while (keyIter.hasNext()) {
+          String curKey = keyIter.next();
+          if (curKey != null && headers.get(curKey) != null) {
+            httpGet.addHeader(curKey, headers.get(curKey));
+          }
+        }
+      }
+      httpGet.setEntity(new StringEntity(params.toJSONString(), Charset.forName("UTF-8")));
+      HttpEntity respentity;
+      HttpResponse response = httpClient.execute(httpGet);
+      respentity = response.getEntity();
+      strReturn = EntityUtils.toString(respentity);
+    } catch (Exception e) {
+      e.printStackTrace();
+      log.error(e.getMessage());
+    }
+    return strReturn;
+  }
+
+}

+ 14 - 0
job-live/src/main/java/cn/com/ctop/job/live/utils/KuaishouAPIConstant.java

@@ -0,0 +1,14 @@
+package cn.com.ctop.job.live.utils;
+
+/**
+ * 快手磁力金牛API接口常量
+ */
+public class KuaishouAPIConstant {
+
+    /**
+     * 报表查询
+     */
+    public static final String REPORT_LIST = "/rest/openapi/gw/esp/v1/report/list";
+
+
+}

+ 173 - 0
job-live/src/main/java/cn/com/ctop/job/live/utils/Result.java

@@ -0,0 +1,173 @@
+package cn.com.ctop.job.live.utils;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 接口返回数据格式
+ *
+ * @author scott
+ * @email jeecgos@163.com
+ * @date 2019年1月19日
+ */
+@Data
+@ApiModel(value = "接口返回对象", description = "接口返回对象")
+public class Result<T> implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 成功标志
+     */
+    @ApiModelProperty(value = "成功标志")
+    private boolean success = true;
+
+    /**
+     * 返回处理消息
+     */
+    @ApiModelProperty(value = "返回处理消息")
+    private String message = "操作成功!";
+
+    /**
+     * 返回代码
+     */
+    @ApiModelProperty(value = "返回代码")
+    private Integer code = 0;
+
+    /**
+     * 返回数据对象 data
+     */
+    @ApiModelProperty(value = "返回数据对象")
+    private T result;
+
+    /**
+     * 时间戳
+     */
+    @ApiModelProperty(value = "时间戳")
+    private long timestamp = System.currentTimeMillis();
+
+    @JsonIgnore
+    private String onlTable;
+
+    public Result() {
+
+    }
+
+    public Result<T> success(String message) {
+        this.message = message;
+        this.code = 200;
+        this.success = true;
+        return this;
+    }
+
+    @Deprecated
+    public static Result<Object> ok() {
+        Result<Object> r = new Result<Object>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage("成功");
+        return r;
+    }
+
+    @Deprecated
+    public static Result<Object> ok(String msg) {
+        Result<Object> r = new Result<Object>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage(msg);
+        return r;
+    }
+
+    @Deprecated
+    public static Result<Object> ok(Object data) {
+        Result<Object> r = new Result<Object>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setResult(data);
+        return r;
+    }
+
+    public static <T> Result<T> OK() {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage("成功");
+        return r;
+    }
+
+    public static <T> Result<T> OK(T data) {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setResult(data);
+        return r;
+    }
+
+    public static <T> Result<T> OK(String msg, T data) {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage(msg);
+        r.setResult(data);
+        return r;
+    }
+
+    public static <T> Result<T> error(String msg, T data) {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage(msg);
+        r.setResult(data);
+        return r;
+    }
+
+    public static <T> Result<T> successMsg(String msg, T t) {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(0);
+        r.setResult(t);
+        r.setMessage(msg);
+        return r;
+    }
+
+    public static <T> Result<T> errorMsg(String msg) {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(-1);
+        //r.setResult(t);
+        r.setMessage(msg);
+        return r;
+    }
+
+
+    public static Result<Object> error(String msg) {
+        return error(500, msg);
+    }
+
+    public static Result<Object> error(int code, String msg) {
+        Result<Object> r = new Result<Object>();
+        r.setCode(code);
+        r.setMessage(msg);
+        r.setSuccess(false);
+        return r;
+    }
+
+    public Result<T> error500(String message) {
+        this.message = message;
+        this.code = 500;
+        this.success = false;
+        return this;
+    }
+
+    /**
+     * 无权限访问返回结果
+     */
+    public static Result<Object> noauth(String msg) {
+        return error(510, msg);
+    }
+
+}

+ 124 - 0
job-live/src/main/resources/application.yml

@@ -0,0 +1,124 @@
+server:
+  port: 8083
+  ip: 127.0.0.1
+  servlet:
+    context-path: /
+    compression:
+      enabled: true
+      mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: -1
+      max-request-size: -1
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    #    name: fendo
+    #    url: jdbc:mysql://39.97.120.42:3306/track?characterEncoding=UTF-8&useUnicode=true&useSSL=false
+    #    username: hcst
+    #    password: test@20190531
+    type: com.alibaba.druid.pool.DruidDataSource
+    driver-class-name: com.mysql.jdbc.Driver
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        connection-init-sql: set names utf8mb4
+
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          url: jdbc:mysql://192.168.0.184:3390/kuaishou_live?characterEncoding=UTF-8&useUnicode=true
+          username: hcst
+          password: hcst@2021
+          driver-class-name: com.mysql.jdbc.Driver
+
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath*:org/jeecg/modules/**/xml/*Mapper.xml,classpath*:cn/com/ctop/**/xml/*Mapper.xml
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    configuration:
+      # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+logging:
+  level:
+    root: info
+  config: classpath:logback-dev.xml
+
+xxl:
+  job:
+    accessToken:
+    executor:
+      appname: module-job-kuaishou
+      port: 9997
+      ip:
+      logpath: /data/applogs/xxl-job/jobhandler
+      logretentiondays: 30
+    admin:
+      addresses: http://192.168.0.192:8080/xxl-job-admin
+api:
+  kuaishou:
+    postUrl: https://ad.e.kuaishou.com
+
+
+

+ 49 - 0
job-live/src/main/resources/generatorConfig.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE generatorConfiguration
+        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
+        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
+<generatorConfiguration>
+    <!-- 导入配置文件 -->
+    <properties resource="application.properties"/>
+
+    <!-- defaultModelType="flat" 设置复合主键时不单独为主键创建实体 -->
+    <context id="MySql" defaultModelType="flat">
+
+        <!-- 生成的POJO实现java.io.Serializable接口 -->
+        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
+
+        <!--注释-->
+        <commentGenerator>
+            <!-- 将数据库中表的字段描述信息添加到注释 -->
+            <property name="addRemarkComments" value="true"/>
+            <!-- 注释里不添加日期 -->
+            <property name="suppressDate" value="true"/>
+        </commentGenerator>
+        <!-- 数据库连接,直接通过${}读取application.properties里的配置 -->
+        <jdbcConnection
+                driverClass="${spring.datasource.driver-class-name}"
+                connectionURL="${spring.datasource.url}"
+                userId="${spring.datasource.username}"
+                password="${spring.datasource.password}"/>
+
+        <!-- 生成POJO对象,并将类放到com.songguoliang.springboot.entity包下 -->
+        <javaModelGenerator targetPackage="com.songguoliang.springboot.entity"
+                            targetProject="src/main/java"></javaModelGenerator>
+        <!-- 生成mapper xml文件,并放到resources下的mapper文件夹下 -->
+        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"></sqlMapGenerator>
+
+
+        <!-- 生成mapper xml对应dao接口,放到com.songguoliang.springboot.mapper包下-->
+        <javaClientGenerator targetPackage="com.songguoliang.springboot.mapper" targetProject="src/main/java"
+                             type="XMLMAPPER"></javaClientGenerator>
+
+        <!-- table标签可以有多个,至少一个,tableName指定表名,可以使用_和%通配符 -->
+        <table tableName="tbl_user">
+            <!-- 是否只生成POJO对象 -->
+            <property name="modelOnly" value="false"/>
+            <!-- 数据库中表名有时我们都会带个前缀,而实体又不想带前缀,这个配置可以把实体的前缀去掉 -->
+            <domainObjectRenamingRule searchString="^Tbl" replaceString=""/>
+
+        </table>
+    </context>
+</generatorConfiguration>

+ 4 - 0
job-live/src/main/resources/log4j.properties

@@ -0,0 +1,4 @@
+log4j.rootLogger=INFO,stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS}-[%p] [%t] %-5l - %m%n

+ 57 - 0
job-live/src/main/resources/logback-dev.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <contextName>logback</contextName>
+
+
+    <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
+    <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
+
+    <!-- 定义日志存储的路径,不要配置相对路径 -->
+    <property name="FILE_PATH" value="D:/Desktop/log/spring-log.%d{yyyy-MM-dd}.%i.log"/>
+
+    <!-- 控制台输出日志 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <!-- 日志级别过滤INFO以下 -->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>INFO</level>
+        </filter>
+        <encoder>
+            <!-- 按照上面配置的LOG_PATTERN来打印日志 -->
+            <pattern>${LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <!--每天生成一个日志文件,保存30天的日志文件。rollingFile用来切分文件的 -->
+    <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
+
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+
+            <fileNamePattern>${FILE_PATH}</fileNamePattern>
+
+            <!-- keep 15 days' worth of history -->
+            <maxHistory>30</maxHistory>
+
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <!-- 日志文件的最大大小 -->
+                <maxFileSize>2MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+
+            <!-- 超出删除老文件 -->
+            <totalSizeCap>1GB</totalSizeCap>
+        </rollingPolicy>
+
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- project default level -->
+    <logger name="net.sh.rgface.serive" level="ERROR"/>
+
+    <!-- 日志输出级别 -->
+    <root level="INFO">
+        <appender-ref ref="console"/>
+        <appender-ref ref="rollingFile"/>
+    </root>
+</configuration>

+ 57 - 0
job-live/src/main/resources/logback-prod.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <contextName>logback</contextName>
+
+
+    <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
+    <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
+
+    <!-- 定义日志存储的路径,不要配置相对路径 -->
+    <property name="FILE_PATH" value="/mnt/webapps/logs/spring-log.%d{yyyy-MM-dd}.%i.log"/>
+
+    <!-- 控制台输出日志 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <!-- 日志级别过滤INFO以下 -->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>INFO</level>
+        </filter>
+        <encoder>
+            <!-- 按照上面配置的LOG_PATTERN来打印日志 -->
+            <pattern>${LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <!--每天生成一个日志文件,保存30天的日志文件。rollingFile用来切分文件的 -->
+    <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
+
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+
+            <fileNamePattern>${FILE_PATH}</fileNamePattern>
+
+            <!-- keep 15 days' worth of history -->
+            <maxHistory>30</maxHistory>
+
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <!-- 日志文件的最大大小 -->
+                <maxFileSize>2MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+
+            <!-- 超出删除老文件 -->
+            <totalSizeCap>1GB</totalSizeCap>
+        </rollingPolicy>
+
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- project default level -->
+    <logger name="net.sh.rgface.serive" level="ERROR"/>
+
+    <!-- 日志输出级别 -->
+    <root level="INFO">
+        <appender-ref ref="console"/>
+        <appender-ref ref="rollingFile"/>
+    </root>
+</configuration>

+ 57 - 0
job-live/src/main/resources/logback-test.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <contextName>logback</contextName>
+
+
+    <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
+    <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
+
+    <!-- 定义日志存储的路径,不要配置相对路径 -->
+    <property name="FILE_PATH" value="/data/webapp/log/spring-log.%d{yyyy-MM-dd}.%i.log"/>
+
+    <!-- 控制台输出日志 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <!-- 日志级别过滤INFO以下 -->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>INFO</level>
+        </filter>
+        <encoder>
+            <!-- 按照上面配置的LOG_PATTERN来打印日志 -->
+            <pattern>${LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <!--每天生成一个日志文件,保存30天的日志文件。rollingFile用来切分文件的 -->
+    <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
+
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+
+            <fileNamePattern>${FILE_PATH}</fileNamePattern>
+
+            <!-- keep 15 days' worth of history -->
+            <maxHistory>30</maxHistory>
+
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <!-- 日志文件的最大大小 -->
+                <maxFileSize>2MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+
+            <!-- 超出删除老文件 -->
+            <totalSizeCap>1GB</totalSizeCap>
+        </rollingPolicy>
+
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- project default level -->
+    <logger name="net.sh.rgface.serive" level="ERROR"/>
+
+    <!-- 日志输出级别 -->
+    <root level="INFO">
+        <appender-ref ref="console"/>
+        <appender-ref ref="rollingFile"/>
+    </root>
+</configuration>

+ 15 - 0
job-live/src/test/java/cn/com/ctop/job/kuaishou/SampleTest.java

@@ -0,0 +1,15 @@
+//package cn.com.ctop.job.kuaishou;
+//
+//
+//import lombok.extern.slf4j.Slf4j;
+//import org.junit.runner.RunWith;
+//import org.springframework.boot.test.context.SpringBootTest;
+//import org.springframework.test.context.junit4.SpringRunner;
+//
+//@RunWith(SpringRunner.class)
+//@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+//@Slf4j
+//public class SampleTest {
+//
+//
+//}

+ 144 - 0
pom.xml

@@ -0,0 +1,144 @@
+<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.1-SNAPSHOT</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>job-live</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.8.6</gson.version>
+
+		<spring.version>5.3.6</spring.version>
+		<spring-boot.version>2.4.5</spring-boot.version>
+
+		<mybatis-spring-boot-starter.version>2.1.4</mybatis-spring-boot-starter.version>
+		<mysql-connector-java.version>8.0.24</mysql-connector-java.version>
+
+		<slf4j-api.version>1.7.30</slf4j-api.version>
+		<junit-jupiter.version>5.7.1</junit-jupiter.version>
+		<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
+
+		<groovy.version>3.0.8</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>

+ 64 - 0
xxl-job-core/pom.xml

@@ -0,0 +1,64 @@
+<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.1-SNAPSHOT</version>
+	</parent>
+	<artifactId>xxl-job-core</artifactId>
+	<packaging>jar</packaging>
+
+	<name>${project.artifactId}</name>
+	<description>A distributed task scheduling framework.</description>
+	<url>https://www.xuxueli.com/</url>
+
+	<dependencies>
+
+		<!-- ********************** embed server: netty + gson ********************** -->
+		<dependency>
+			<groupId>io.netty</groupId>
+			<artifactId>netty-all</artifactId>
+			<version>${netty-all.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+			<version>${gson.version}</version>
+		</dependency>
+
+		<!-- ********************** plugin ********************** -->
+		<!-- groovy-all -->
+		<dependency>
+			<groupId>org.codehaus.groovy</groupId>
+			<artifactId>groovy</artifactId>
+			<version>${groovy.version}</version>
+		</dependency>
+
+		<!-- spring-context -->
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-context</artifactId>
+			<version>${spring.version}</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<!-- ********************** base ********************** -->
+		<!-- slf4j -->
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<version>${slf4j-api.version}</version>
+		</dependency>
+
+		<!-- javax.annotation-api -->
+		<dependency>
+			<groupId>javax.annotation</groupId>
+			<artifactId>javax.annotation-api</artifactId>
+			<version>${javax.annotation-api.version}</version>
+			<scope>provided</scope>
+		</dependency>
+
+	</dependencies>
+
+</project>

+ 48 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/AdminBiz.java

@@ -0,0 +1,48 @@
+package com.xxl.job.core.biz;
+
+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 java.util.List;
+
+/**
+ * @author xuxueli 2017-07-27 21:52:49
+ */
+public interface AdminBiz {
+
+
+    // ---------------------- callback ----------------------
+
+    /**
+     * callback
+     *
+     * @param callbackParamList
+     * @return
+     */
+    public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList);
+
+
+    // ---------------------- registry ----------------------
+
+    /**
+     * registry
+     *
+     * @param registryParam
+     * @return
+     */
+    public ReturnT<String> registry(RegistryParam registryParam);
+
+    /**
+     * registry remove
+     *
+     * @param registryParam
+     * @return
+     */
+    public ReturnT<String> registryRemove(RegistryParam registryParam);
+
+
+    // ---------------------- biz (custome) ----------------------
+    // group、job ... manage
+
+}

+ 45 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/ExecutorBiz.java

@@ -0,0 +1,45 @@
+package com.xxl.job.core.biz;
+
+import com.xxl.job.core.biz.model.*;
+
+/**
+ * Created by xuxueli on 17/3/1.
+ */
+public interface ExecutorBiz {
+
+    /**
+     * beat
+     * @return
+     */
+    public ReturnT<String> beat();
+
+    /**
+     * idle beat
+     *
+     * @param idleBeatParam
+     * @return
+     */
+    public ReturnT<String> idleBeat(IdleBeatParam idleBeatParam);
+
+    /**
+     * run
+     * @param triggerParam
+     * @return
+     */
+    public ReturnT<String> run(TriggerParam triggerParam);
+
+    /**
+     * kill
+     * @param killParam
+     * @return
+     */
+    public ReturnT<String> kill(KillParam killParam);
+
+    /**
+     * log
+     * @param logParam
+     * @return
+     */
+    public ReturnT<LogResult> log(LogParam logParam);
+
+}

+ 50 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/client/AdminBizClient.java

@@ -0,0 +1,50 @@
+package com.xxl.job.core.biz.client;
+
+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.XxlJobRemotingUtil;
+
+import java.util.List;
+
+/**
+ * admin api test
+ *
+ * @author xuxueli 2017-07-28 22:14:52
+ */
+public class AdminBizClient implements AdminBiz {
+
+    public AdminBizClient() {
+    }
+    public AdminBizClient(String addressUrl, String accessToken) {
+        this.addressUrl = addressUrl;
+        this.accessToken = accessToken;
+
+        // valid
+        if (!this.addressUrl.endsWith("/")) {
+            this.addressUrl = this.addressUrl + "/";
+        }
+    }
+
+    private String addressUrl ;
+    private String accessToken;
+    private int timeout = 3;
+
+
+    @Override
+    public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
+        return XxlJobRemotingUtil.postBody(addressUrl+"api/callback", accessToken, timeout, callbackParamList, String.class);
+    }
+
+    @Override
+    public ReturnT<String> registry(RegistryParam registryParam) {
+        return XxlJobRemotingUtil.postBody(addressUrl + "api/registry", accessToken, timeout, registryParam, String.class);
+    }
+
+    @Override
+    public ReturnT<String> registryRemove(RegistryParam registryParam) {
+        return XxlJobRemotingUtil.postBody(addressUrl + "api/registryRemove", accessToken, timeout, registryParam, String.class);
+    }
+
+}

+ 56 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/client/ExecutorBizClient.java

@@ -0,0 +1,56 @@
+package com.xxl.job.core.biz.client;
+
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.biz.model.*;
+import com.xxl.job.core.util.XxlJobRemotingUtil;
+
+/**
+ * admin api test
+ *
+ * @author xuxueli 2017-07-28 22:14:52
+ */
+public class ExecutorBizClient implements ExecutorBiz {
+
+    public ExecutorBizClient() {
+    }
+    public ExecutorBizClient(String addressUrl, String accessToken) {
+        this.addressUrl = addressUrl;
+        this.accessToken = accessToken;
+
+        // valid
+        if (!this.addressUrl.endsWith("/")) {
+            this.addressUrl = this.addressUrl + "/";
+        }
+    }
+
+    private String addressUrl ;
+    private String accessToken;
+    private int timeout = 3;
+
+
+    @Override
+    public ReturnT<String> beat() {
+        return XxlJobRemotingUtil.postBody(addressUrl+"beat", accessToken, timeout, "", String.class);
+    }
+
+    @Override
+    public ReturnT<String> idleBeat(IdleBeatParam idleBeatParam){
+        return XxlJobRemotingUtil.postBody(addressUrl+"idleBeat", accessToken, timeout, idleBeatParam, String.class);
+    }
+
+    @Override
+    public ReturnT<String> run(TriggerParam triggerParam) {
+        return XxlJobRemotingUtil.postBody(addressUrl + "run", accessToken, timeout, triggerParam, String.class);
+    }
+
+    @Override
+    public ReturnT<String> kill(KillParam killParam) {
+        return XxlJobRemotingUtil.postBody(addressUrl + "kill", accessToken, timeout, killParam, String.class);
+    }
+
+    @Override
+    public ReturnT<LogResult> log(LogParam logParam) {
+        return XxlJobRemotingUtil.postBody(addressUrl + "log", accessToken, timeout, logParam, LogResult.class);
+    }
+
+}

+ 172 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java

@@ -0,0 +1,172 @@
+package com.xxl.job.core.biz.impl;
+
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.biz.model.*;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.job.core.executor.XxlJobExecutor;
+import com.xxl.job.core.glue.GlueFactory;
+import com.xxl.job.core.glue.GlueTypeEnum;
+import com.xxl.job.core.handler.IJobHandler;
+import com.xxl.job.core.handler.impl.GlueJobHandler;
+import com.xxl.job.core.handler.impl.ScriptJobHandler;
+import com.xxl.job.core.log.XxlJobFileAppender;
+import com.xxl.job.core.thread.JobThread;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+
+/**
+ * Created by xuxueli on 17/3/1.
+ */
+public class ExecutorBizImpl implements ExecutorBiz {
+    private static Logger logger = LoggerFactory.getLogger(ExecutorBizImpl.class);
+
+    @Override
+    public ReturnT<String> beat() {
+        return ReturnT.SUCCESS;
+    }
+
+    @Override
+    public ReturnT<String> idleBeat(IdleBeatParam idleBeatParam) {
+
+        // isRunningOrHasQueue
+        boolean isRunningOrHasQueue = false;
+        JobThread jobThread = XxlJobExecutor.loadJobThread(idleBeatParam.getJobId());
+        if (jobThread != null && jobThread.isRunningOrHasQueue()) {
+            isRunningOrHasQueue = true;
+        }
+
+        if (isRunningOrHasQueue) {
+            return new ReturnT<String>(ReturnT.FAIL_CODE, "job thread is running or has trigger queue.");
+        }
+        return ReturnT.SUCCESS;
+    }
+
+    @Override
+    public ReturnT<String> run(TriggerParam triggerParam) {
+        // load old:jobHandler + jobThread
+        JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
+        IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
+        String removeOldReason = null;
+
+        // valid:jobHandler + jobThread
+        GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
+        if (GlueTypeEnum.BEAN == glueTypeEnum) {
+
+            // new jobhandler
+            IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
+
+            // valid old jobThread
+            if (jobThread!=null && jobHandler != newJobHandler) {
+                // change handler, need kill old thread
+                removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";
+
+                jobThread = null;
+                jobHandler = null;
+            }
+
+            // valid handler
+            if (jobHandler == null) {
+                jobHandler = newJobHandler;
+                if (jobHandler == null) {
+                    return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
+                }
+            }
+
+        } else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
+
+            // valid old jobThread
+            if (jobThread != null &&
+                    !(jobThread.getHandler() instanceof GlueJobHandler
+                        && ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
+                // change handler or gluesource updated, need kill old thread
+                removeOldReason = "change job source or glue type, and terminate the old job thread.";
+
+                jobThread = null;
+                jobHandler = null;
+            }
+
+            // valid handler
+            if (jobHandler == null) {
+                try {
+                    IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
+                    jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
+                } catch (Exception e) {
+                    logger.error(e.getMessage(), e);
+                    return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
+                }
+            }
+        } else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
+
+            // valid old jobThread
+            if (jobThread != null &&
+                    !(jobThread.getHandler() instanceof ScriptJobHandler
+                            && ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
+                // change script or gluesource updated, need kill old thread
+                removeOldReason = "change job source or glue type, and terminate the old job thread.";
+
+                jobThread = null;
+                jobHandler = null;
+            }
+
+            // valid handler
+            if (jobHandler == null) {
+                jobHandler = new ScriptJobHandler(triggerParam.getJobId(), triggerParam.getGlueUpdatetime(), triggerParam.getGlueSource(), GlueTypeEnum.match(triggerParam.getGlueType()));
+            }
+        } else {
+            return new ReturnT<String>(ReturnT.FAIL_CODE, "glueType[" + triggerParam.getGlueType() + "] is not valid.");
+        }
+
+        // executor block strategy
+        if (jobThread != null) {
+            ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);
+            if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {
+                // discard when running
+                if (jobThread.isRunningOrHasQueue()) {
+                    return new ReturnT<String>(ReturnT.FAIL_CODE, "block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());
+                }
+            } else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {
+                // kill running jobThread
+                if (jobThread.isRunningOrHasQueue()) {
+                    removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();
+
+                    jobThread = null;
+                }
+            } else {
+                // just queue trigger
+            }
+        }
+
+        // replace thread (new or exists invalid)
+        if (jobThread == null) {
+            jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
+        }
+
+        // push data to queue
+        ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
+        return pushResult;
+    }
+
+    @Override
+    public ReturnT<String> kill(KillParam killParam) {
+        // kill handlerThread, and create new one
+        JobThread jobThread = XxlJobExecutor.loadJobThread(killParam.getJobId());
+        if (jobThread != null) {
+            XxlJobExecutor.removeJobThread(killParam.getJobId(), "scheduling center kill job.");
+            return ReturnT.SUCCESS;
+        }
+
+        return new ReturnT<String>(ReturnT.SUCCESS_CODE, "job thread already killed.");
+    }
+
+    @Override
+    public ReturnT<LogResult> log(LogParam logParam) {
+        // log filename: logPath/yyyy-MM-dd/9999.log
+        String logFileName = XxlJobFileAppender.makeLogFileName(new Date(logParam.getLogDateTim()), logParam.getLogId());
+
+        LogResult logResult = XxlJobFileAppender.readLog(logFileName, logParam.getFromLineNum());
+        return new ReturnT<LogResult>(logResult);
+    }
+
+}

+ 67 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/model/HandleCallbackParam.java

@@ -0,0 +1,67 @@
+package com.xxl.job.core.biz.model;
+
+import java.io.Serializable;
+
+/**
+ * Created by xuxueli on 17/3/2.
+ */
+public class HandleCallbackParam implements Serializable {
+    private static final long serialVersionUID = 42L;
+
+    private long logId;
+    private long logDateTim;
+
+    private int handleCode;
+    private String handleMsg;
+
+    public HandleCallbackParam(){}
+    public HandleCallbackParam(long logId, long logDateTim, int handleCode, String handleMsg) {
+        this.logId = logId;
+        this.logDateTim = logDateTim;
+        this.handleCode = handleCode;
+        this.handleMsg = handleMsg;
+    }
+
+    public long getLogId() {
+        return logId;
+    }
+
+    public void setLogId(long logId) {
+        this.logId = logId;
+    }
+
+    public long getLogDateTim() {
+        return logDateTim;
+    }
+
+    public void setLogDateTim(long logDateTim) {
+        this.logDateTim = logDateTim;
+    }
+
+    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;
+    }
+
+    @Override
+    public String toString() {
+        return "HandleCallbackParam{" +
+                "logId=" + logId +
+                ", logDateTim=" + logDateTim +
+                ", handleCode=" + handleCode +
+                ", handleMsg='" + handleMsg + '\'' +
+                '}';
+    }
+
+}

+ 28 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/model/IdleBeatParam.java

@@ -0,0 +1,28 @@
+package com.xxl.job.core.biz.model;
+
+import java.io.Serializable;
+
+/**
+ * @author xuxueli 2020-04-11 22:27
+ */
+public class IdleBeatParam implements Serializable {
+    private static final long serialVersionUID = 42L;
+
+    public IdleBeatParam() {
+    }
+    public IdleBeatParam(int jobId) {
+        this.jobId = jobId;
+    }
+
+    private int jobId;
+
+
+    public int getJobId() {
+        return jobId;
+    }
+
+    public void setJobId(int jobId) {
+        this.jobId = jobId;
+    }
+
+}

+ 28 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/model/KillParam.java

@@ -0,0 +1,28 @@
+package com.xxl.job.core.biz.model;
+
+import java.io.Serializable;
+
+/**
+ * @author xuxueli 2020-04-11 22:27
+ */
+public class KillParam implements Serializable {
+    private static final long serialVersionUID = 42L;
+
+    public KillParam() {
+    }
+    public KillParam(int jobId) {
+        this.jobId = jobId;
+    }
+
+    private int jobId;
+
+
+    public int getJobId() {
+        return jobId;
+    }
+
+    public void setJobId(int jobId) {
+        this.jobId = jobId;
+    }
+
+}

+ 47 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogParam.java

@@ -0,0 +1,47 @@
+package com.xxl.job.core.biz.model;
+
+import java.io.Serializable;
+
+/**
+ * @author xuxueli 2020-04-11 22:27
+ */
+public class LogParam implements Serializable {
+    private static final long serialVersionUID = 42L;
+
+    public LogParam() {
+    }
+    public LogParam(long logDateTim, long logId, int fromLineNum) {
+        this.logDateTim = logDateTim;
+        this.logId = logId;
+        this.fromLineNum = fromLineNum;
+    }
+
+    private long logDateTim;
+    private long logId;
+    private int fromLineNum;
+
+    public long getLogDateTim() {
+        return logDateTim;
+    }
+
+    public void setLogDateTim(long logDateTim) {
+        this.logDateTim = logDateTim;
+    }
+
+    public long getLogId() {
+        return logId;
+    }
+
+    public void setLogId(long logId) {
+        this.logId = logId;
+    }
+
+    public int getFromLineNum() {
+        return fromLineNum;
+    }
+
+    public void setFromLineNum(int fromLineNum) {
+        this.fromLineNum = fromLineNum;
+    }
+
+}

+ 56 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/model/LogResult.java

@@ -0,0 +1,56 @@
+package com.xxl.job.core.biz.model;
+
+import java.io.Serializable;
+
+/**
+ * Created by xuxueli on 17/3/23.
+ */
+public class LogResult implements Serializable {
+    private static final long serialVersionUID = 42L;
+
+    public LogResult() {
+    }
+    public LogResult(int fromLineNum, int toLineNum, String logContent, boolean isEnd) {
+        this.fromLineNum = fromLineNum;
+        this.toLineNum = toLineNum;
+        this.logContent = logContent;
+        this.isEnd = isEnd;
+    }
+
+    private int fromLineNum;
+    private int toLineNum;
+    private String logContent;
+    private boolean isEnd;
+
+    public int getFromLineNum() {
+        return fromLineNum;
+    }
+
+    public void setFromLineNum(int fromLineNum) {
+        this.fromLineNum = fromLineNum;
+    }
+
+    public int getToLineNum() {
+        return toLineNum;
+    }
+
+    public void setToLineNum(int toLineNum) {
+        this.toLineNum = toLineNum;
+    }
+
+    public String getLogContent() {
+        return logContent;
+    }
+
+    public void setLogContent(String logContent) {
+        this.logContent = logContent;
+    }
+
+    public boolean isEnd() {
+        return isEnd;
+    }
+
+    public void setEnd(boolean end) {
+        isEnd = end;
+    }
+}

+ 54 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/model/RegistryParam.java

@@ -0,0 +1,54 @@
+package com.xxl.job.core.biz.model;
+
+import java.io.Serializable;
+
+/**
+ * Created by xuxueli on 2017-05-10 20:22:42
+ */
+public class RegistryParam implements Serializable {
+    private static final long serialVersionUID = 42L;
+
+    private String registryGroup;
+    private String registryKey;
+    private String registryValue;
+
+    public RegistryParam(){}
+    public RegistryParam(String registryGroup, String registryKey, String registryValue) {
+        this.registryGroup = registryGroup;
+        this.registryKey = registryKey;
+        this.registryValue = registryValue;
+    }
+
+    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;
+    }
+
+    @Override
+    public String toString() {
+        return "RegistryParam{" +
+                "registryGroup='" + registryGroup + '\'' +
+                ", registryKey='" + registryKey + '\'' +
+                ", registryValue='" + registryValue + '\'' +
+                '}';
+    }
+}

+ 57 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/model/ReturnT.java

@@ -0,0 +1,57 @@
+package com.xxl.job.core.biz.model;
+
+import java.io.Serializable;
+
+/**
+ * common return
+ * @author xuxueli 2015-12-4 16:32:31
+ * @param <T>
+ */
+public class ReturnT<T> implements Serializable {
+	public static final long serialVersionUID = 42L;
+
+	public static final int SUCCESS_CODE = 200;
+	public static final int FAIL_CODE = 500;
+
+	public static final ReturnT<String> SUCCESS = new ReturnT<String>(null);
+	public static final ReturnT<String> FAIL = new ReturnT<String>(FAIL_CODE, null);
+
+	private int code;
+	private String msg;
+	private T content;
+
+	public ReturnT(){}
+	public ReturnT(int code, String msg) {
+		this.code = code;
+		this.msg = msg;
+	}
+	public ReturnT(T content) {
+		this.code = SUCCESS_CODE;
+		this.content = content;
+	}
+	
+	public int getCode() {
+		return code;
+	}
+	public void setCode(int code) {
+		this.code = code;
+	}
+	public String getMsg() {
+		return msg;
+	}
+	public void setMsg(String msg) {
+		this.msg = msg;
+	}
+	public T getContent() {
+		return content;
+	}
+	public void setContent(T content) {
+		this.content = content;
+	}
+
+	@Override
+	public String toString() {
+		return "ReturnT [code=" + code + ", msg=" + msg + ", content=" + content + "]";
+	}
+
+}

+ 144 - 0
xxl-job-core/src/main/java/com/xxl/job/core/biz/model/TriggerParam.java

@@ -0,0 +1,144 @@
+package com.xxl.job.core.biz.model;
+
+import java.io.Serializable;
+
+/**
+ * Created by xuxueli on 16/7/22.
+ */
+public class TriggerParam implements Serializable{
+    private static final long serialVersionUID = 42L;
+
+    private int jobId;
+
+    private String executorHandler;
+    private String executorParams;
+    private String executorBlockStrategy;
+    private int executorTimeout;
+
+    private long logId;
+    private long logDateTime;
+
+    private String glueType;
+    private String glueSource;
+    private long glueUpdatetime;
+
+    private int broadcastIndex;
+    private int broadcastTotal;
+
+
+    public int getJobId() {
+        return jobId;
+    }
+
+    public void setJobId(int jobId) {
+        this.jobId = jobId;
+    }
+
+    public String getExecutorHandler() {
+        return executorHandler;
+    }
+
+    public void setExecutorHandler(String executorHandler) {
+        this.executorHandler = executorHandler;
+    }
+
+    public String getExecutorParams() {
+        return executorParams;
+    }
+
+    public void setExecutorParams(String executorParams) {
+        this.executorParams = executorParams;
+    }
+
+    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 long getLogId() {
+        return logId;
+    }
+
+    public void setLogId(long logId) {
+        this.logId = logId;
+    }
+
+    public long getLogDateTime() {
+        return logDateTime;
+    }
+
+    public void setLogDateTime(long logDateTime) {
+        this.logDateTime = logDateTime;
+    }
+
+    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 long getGlueUpdatetime() {
+        return glueUpdatetime;
+    }
+
+    public void setGlueUpdatetime(long glueUpdatetime) {
+        this.glueUpdatetime = glueUpdatetime;
+    }
+
+    public int getBroadcastIndex() {
+        return broadcastIndex;
+    }
+
+    public void setBroadcastIndex(int broadcastIndex) {
+        this.broadcastIndex = broadcastIndex;
+    }
+
+    public int getBroadcastTotal() {
+        return broadcastTotal;
+    }
+
+    public void setBroadcastTotal(int broadcastTotal) {
+        this.broadcastTotal = broadcastTotal;
+    }
+
+
+    @Override
+    public String toString() {
+        return "TriggerParam{" +
+                "jobId=" + jobId +
+                ", executorHandler='" + executorHandler + '\'' +
+                ", executorParams='" + executorParams + '\'' +
+                ", executorBlockStrategy='" + executorBlockStrategy + '\'' +
+                ", executorTimeout=" + executorTimeout +
+                ", logId=" + logId +
+                ", logDateTime=" + logDateTime +
+                ", glueType='" + glueType + '\'' +
+                ", glueSource='" + glueSource + '\'' +
+                ", glueUpdatetime=" + glueUpdatetime +
+                ", broadcastIndex=" + broadcastIndex +
+                ", broadcastTotal=" + broadcastTotal +
+                '}';
+    }
+
+}

+ 122 - 0
xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobContext.java

@@ -0,0 +1,122 @@
+package com.xxl.job.core.context;
+
+/**
+ * xxl-job context
+ *
+ * @author xuxueli 2020-05-21
+ * [Dear hj]
+ */
+public class XxlJobContext {
+
+    public static final int HANDLE_CODE_SUCCESS = 200;
+    public static final int HANDLE_CODE_FAIL = 500;
+    public static final int HANDLE_CODE_TIMEOUT = 502;
+
+    // ---------------------- base info ----------------------
+
+    /**
+     * job id
+     */
+    private final long jobId;
+
+    /**
+     * job param
+     */
+    private final String jobParam;
+
+    // ---------------------- for log ----------------------
+
+    /**
+     * job log filename
+     */
+    private final String jobLogFileName;
+
+    // ---------------------- for shard ----------------------
+
+    /**
+     * shard index
+     */
+    private final int shardIndex;
+
+    /**
+     * shard total
+     */
+    private final int shardTotal;
+
+    // ---------------------- for handle ----------------------
+
+    /**
+     * handleCode:The result status of job execution
+     *
+     *      200 : success
+     *      500 : fail
+     *      502 : timeout
+     *
+     */
+    private int handleCode;
+
+    /**
+     * handleMsg:The simple log msg of job execution
+     */
+    private String handleMsg;
+
+
+    public XxlJobContext(long jobId, String jobParam, String jobLogFileName, int shardIndex, int shardTotal) {
+        this.jobId = jobId;
+        this.jobParam = jobParam;
+        this.jobLogFileName = jobLogFileName;
+        this.shardIndex = shardIndex;
+        this.shardTotal = shardTotal;
+
+        this.handleCode = HANDLE_CODE_SUCCESS;  // default success
+    }
+
+    public long getJobId() {
+        return jobId;
+    }
+
+    public String getJobParam() {
+        return jobParam;
+    }
+
+    public String getJobLogFileName() {
+        return jobLogFileName;
+    }
+
+    public int getShardIndex() {
+        return shardIndex;
+    }
+
+    public int getShardTotal() {
+        return shardTotal;
+    }
+
+    public void setHandleCode(int handleCode) {
+        this.handleCode = handleCode;
+    }
+
+    public int getHandleCode() {
+        return handleCode;
+    }
+
+    public void setHandleMsg(String handleMsg) {
+        this.handleMsg = handleMsg;
+    }
+
+    public String getHandleMsg() {
+        return handleMsg;
+    }
+
+    // ---------------------- tool ----------------------
+
+    private static InheritableThreadLocal<XxlJobContext> contextHolder = new InheritableThreadLocal<XxlJobContext>(); // support for child thread of job handler)
+
+    public static void setXxlJobContext(XxlJobContext xxlJobContext){
+        contextHolder.set(xxlJobContext);
+    }
+
+    public static XxlJobContext getXxlJobContext(){
+        return contextHolder.get();
+    }
+
+}

+ 255 - 0
xxl-job-core/src/main/java/com/xxl/job/core/context/XxlJobHelper.java

@@ -0,0 +1,255 @@
+package com.xxl.job.core.context;
+
+import com.xxl.job.core.log.XxlJobFileAppender;
+import com.xxl.job.core.util.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MessageFormatter;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Date;
+
+/**
+ * helper for xxl-job
+ *
+ * @author xuxueli 2020-11-05
+ */
+public class XxlJobHelper {
+
+    // ---------------------- base info ----------------------
+
+    /**
+     * current JobId
+     *
+     * @return
+     */
+    public static long getJobId() {
+        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
+        if (xxlJobContext == null) {
+            return -1;
+        }
+
+        return xxlJobContext.getJobId();
+    }
+
+    /**
+     * current JobParam
+     *
+     * @return
+     */
+    public static String getJobParam() {
+        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
+        if (xxlJobContext == null) {
+            return null;
+        }
+
+        return xxlJobContext.getJobParam();
+    }
+
+    // ---------------------- for log ----------------------
+
+    /**
+     * current JobLogFileName
+     *
+     * @return
+     */
+    public static String getJobLogFileName() {
+        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
+        if (xxlJobContext == null) {
+            return null;
+        }
+
+        return xxlJobContext.getJobLogFileName();
+    }
+
+    // ---------------------- for shard ----------------------
+
+    /**
+     * current ShardIndex
+     *
+     * @return
+     */
+    public static int getShardIndex() {
+        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
+        if (xxlJobContext == null) {
+            return -1;
+        }
+
+        return xxlJobContext.getShardIndex();
+    }
+
+    /**
+     * current ShardTotal
+     *
+     * @return
+     */
+    public static int getShardTotal() {
+        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
+        if (xxlJobContext == null) {
+            return -1;
+        }
+
+        return xxlJobContext.getShardTotal();
+    }
+
+    // ---------------------- tool for log ----------------------
+
+    private static Logger logger = LoggerFactory.getLogger("xxl-job logger");
+
+    /**
+     * append log with pattern
+     *
+     * @param appendLogPattern  like "aaa {} bbb {} ccc"
+     * @param appendLogArguments    like "111, true"
+     */
+    public static boolean log(String appendLogPattern, Object ... appendLogArguments) {
+
+        FormattingTuple ft = MessageFormatter.arrayFormat(appendLogPattern, appendLogArguments);
+        String appendLog = ft.getMessage();
+
+        /*appendLog = appendLogPattern;
+        if (appendLogArguments!=null && appendLogArguments.length>0) {
+            appendLog = MessageFormat.format(appendLogPattern, appendLogArguments);
+        }*/
+
+        StackTraceElement callInfo = new Throwable().getStackTrace()[1];
+        return logDetail(callInfo, appendLog);
+    }
+
+    /**
+     * append exception stack
+     *
+     * @param e
+     */
+    public static boolean log(Throwable e) {
+
+        StringWriter stringWriter = new StringWriter();
+        e.printStackTrace(new PrintWriter(stringWriter));
+        String appendLog = stringWriter.toString();
+
+        StackTraceElement callInfo = new Throwable().getStackTrace()[1];
+        return logDetail(callInfo, appendLog);
+    }
+
+    /**
+     * append log
+     *
+     * @param callInfo
+     * @param appendLog
+     */
+    private static boolean logDetail(StackTraceElement callInfo, String appendLog) {
+        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
+        if (xxlJobContext == null) {
+            return false;
+        }
+
+        /*// "yyyy-MM-dd HH:mm:ss [ClassName]-[MethodName]-[LineNumber]-[ThreadName] log";
+        StackTraceElement[] stackTraceElements = new Throwable().getStackTrace();
+        StackTraceElement callInfo = stackTraceElements[1];*/
+
+        StringBuffer stringBuffer = new StringBuffer();
+        stringBuffer.append(DateUtil.formatDateTime(new Date())).append(" ")
+                .append("["+ callInfo.getClassName() + "#" + callInfo.getMethodName() +"]").append("-")
+                .append("["+ callInfo.getLineNumber() +"]").append("-")
+                .append("["+ Thread.currentThread().getName() +"]").append(" ")
+                .append(appendLog!=null?appendLog:"");
+        String formatAppendLog = stringBuffer.toString();
+
+        // appendlog
+        String logFileName = xxlJobContext.getJobLogFileName();
+
+        if (logFileName!=null && logFileName.trim().length()>0) {
+            XxlJobFileAppender.appendLog(logFileName, formatAppendLog);
+            return true;
+        } else {
+            logger.info(">>>>>>>>>>> {}", formatAppendLog);
+            return false;
+        }
+    }
+
+    // ---------------------- tool for handleResult ----------------------
+
+    /**
+     * handle success
+     *
+     * @return
+     */
+    public static boolean handleSuccess(){
+        return handleResult(XxlJobContext.HANDLE_CODE_SUCCESS, null);
+    }
+
+    /**
+     * handle success with log msg
+     *
+     * @param handleMsg
+     * @return
+     */
+    public static boolean handleSuccess(String handleMsg) {
+        return handleResult(XxlJobContext.HANDLE_CODE_SUCCESS, handleMsg);
+    }
+
+    /**
+     * handle fail
+     *
+     * @return
+     */
+    public static boolean handleFail(){
+        return handleResult(XxlJobContext.HANDLE_CODE_FAIL, null);
+    }
+
+    /**
+     * handle fail with log msg
+     *
+     * @param handleMsg
+     * @return
+     */
+    public static boolean handleFail(String handleMsg) {
+        return handleResult(XxlJobContext.HANDLE_CODE_FAIL, handleMsg);
+    }
+
+    /**
+     * handle timeout
+     *
+     * @return
+     */
+    public static boolean handleTimeout(){
+        return handleResult(XxlJobContext.HANDLE_CODE_TIMEOUT, null);
+    }
+
+    /**
+     * handle timeout with log msg
+     *
+     * @param handleMsg
+     * @return
+     */
+    public static boolean handleTimeout(String handleMsg){
+        return handleResult(XxlJobContext.HANDLE_CODE_TIMEOUT, handleMsg);
+    }
+
+    /**
+     * @param handleCode
+     *
+     *      200 : success
+     *      500 : fail
+     *      502 : timeout
+     *
+     * @param handleMsg
+     * @return
+     */
+    public static boolean handleResult(int handleCode, String handleMsg) {
+        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
+        if (xxlJobContext == null) {
+            return false;
+        }
+
+        xxlJobContext.setHandleCode(handleCode);
+        if (handleMsg != null) {
+            xxlJobContext.setHandleMsg(handleMsg);
+        }
+        return true;
+    }
+
+
+}

+ 35 - 0
xxl-job-core/src/main/java/com/xxl/job/core/enums/ExecutorBlockStrategyEnum.java

@@ -0,0 +1,35 @@
+package com.xxl.job.core.enums;
+
+/**
+ * Created by xuxueli on 17/5/9.
+ */
+public enum ExecutorBlockStrategyEnum {
+
+    SERIAL_EXECUTION("Serial execution"),
+    /*CONCURRENT_EXECUTION("并行"),*/
+    DISCARD_LATER("Discard Later"),
+    COVER_EARLY("Cover Early");
+
+    private String title;
+    private ExecutorBlockStrategyEnum (String title) {
+        this.title = title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+    public String getTitle() {
+        return title;
+    }
+
+    public static ExecutorBlockStrategyEnum match(String name, ExecutorBlockStrategyEnum defaultItem) {
+        if (name != null) {
+            for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+                if (item.name().equals(name)) {
+                    return item;
+                }
+            }
+        }
+        return defaultItem;
+    }
+}

+ 13 - 0
xxl-job-core/src/main/java/com/xxl/job/core/enums/RegistryConfig.java

@@ -0,0 +1,13 @@
+package com.xxl.job.core.enums;
+
+/**
+ * Created by xuxueli on 17/5/10.
+ */
+public class RegistryConfig {
+
+    public static final int BEAT_TIMEOUT = 30;
+    public static final int DEAD_TIMEOUT = BEAT_TIMEOUT * 3;
+
+    public enum RegistType{ EXECUTOR, ADMIN }
+
+}

+ 269 - 0
xxl-job-core/src/main/java/com/xxl/job/core/executor/XxlJobExecutor.java

@@ -0,0 +1,269 @@
+package com.xxl.job.core.executor;
+
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.client.AdminBizClient;
+import com.xxl.job.core.handler.IJobHandler;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import com.xxl.job.core.handler.impl.MethodJobHandler;
+import com.xxl.job.core.log.XxlJobFileAppender;
+import com.xxl.job.core.server.EmbedServer;
+import com.xxl.job.core.thread.JobLogFileCleanThread;
+import com.xxl.job.core.thread.JobThread;
+import com.xxl.job.core.thread.TriggerCallbackThread;
+import com.xxl.job.core.util.IpUtil;
+import com.xxl.job.core.util.NetUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Created by xuxueli on 2016/3/2 21:14.
+ */
+public class XxlJobExecutor  {
+    private static final Logger logger = LoggerFactory.getLogger(XxlJobExecutor.class);
+
+    // ---------------------- param ----------------------
+    private String adminAddresses;
+    private String accessToken;
+    private String appname;
+    private String address;
+    private String ip;
+    private int port;
+    private String logPath;
+    private int logRetentionDays;
+
+    public void setAdminAddresses(String adminAddresses) {
+        this.adminAddresses = adminAddresses;
+    }
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+    public void setAppname(String appname) {
+        this.appname = appname;
+    }
+    public void setAddress(String address) {
+        this.address = address;
+    }
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+    public void setPort(int port) {
+        this.port = port;
+    }
+    public void setLogPath(String logPath) {
+        this.logPath = logPath;
+    }
+    public void setLogRetentionDays(int logRetentionDays) {
+        this.logRetentionDays = logRetentionDays;
+    }
+
+
+    // ---------------------- start + stop ----------------------
+    public void start() throws Exception {
+
+        // init logpath
+        XxlJobFileAppender.initLogPath(logPath);
+
+        // init invoker, admin-client
+        initAdminBizList(adminAddresses, accessToken);
+
+
+        // init JobLogFileCleanThread
+        JobLogFileCleanThread.getInstance().start(logRetentionDays);
+
+        // init TriggerCallbackThread
+        TriggerCallbackThread.getInstance().start();
+
+        // init executor-server
+        initEmbedServer(address, ip, port, appname, accessToken);
+    }
+    public void destroy(){
+        // destroy executor-server
+        stopEmbedServer();
+
+        // destroy jobThreadRepository
+        if (jobThreadRepository.size() > 0) {
+            for (Map.Entry<Integer, JobThread> item: jobThreadRepository.entrySet()) {
+                JobThread oldJobThread = removeJobThread(item.getKey(), "web container destroy and kill the job.");
+                // wait for job thread push result to callback queue
+                if (oldJobThread != null) {
+                    try {
+                        oldJobThread.join();
+                    } catch (InterruptedException e) {
+                        logger.error(">>>>>>>>>>> xxl-job, JobThread destroy(join) error, jobId:{}", item.getKey(), e);
+                    }
+                }
+            }
+            jobThreadRepository.clear();
+        }
+        jobHandlerRepository.clear();
+
+
+        // destroy JobLogFileCleanThread
+        JobLogFileCleanThread.getInstance().toStop();
+
+        // destroy TriggerCallbackThread
+        TriggerCallbackThread.getInstance().toStop();
+
+    }
+
+
+    // ---------------------- admin-client (rpc invoker) ----------------------
+    private static List<AdminBiz> adminBizList;
+    private void initAdminBizList(String adminAddresses, String accessToken) throws Exception {
+        if (adminAddresses!=null && adminAddresses.trim().length()>0) {
+            for (String address: adminAddresses.trim().split(",")) {
+                if (address!=null && address.trim().length()>0) {
+
+                    AdminBiz adminBiz = new AdminBizClient(address.trim(), accessToken);
+
+                    if (adminBizList == null) {
+                        adminBizList = new ArrayList<AdminBiz>();
+                    }
+                    adminBizList.add(adminBiz);
+                }
+            }
+        }
+    }
+    public static List<AdminBiz> getAdminBizList(){
+        return adminBizList;
+    }
+
+    // ---------------------- executor-server (rpc provider) ----------------------
+    private EmbedServer embedServer = null;
+
+    private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {
+
+        // fill ip port
+        port = port>0?port: NetUtil.findAvailablePort(9999);
+        ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();
+
+        // generate address
+        if (address==null || address.trim().length()==0) {
+            String ip_port_address = IpUtil.getIpPort(ip, port);   // registry-address:default use address to registry , otherwise use ip:port if address is null
+            address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
+        }
+
+        // accessToken
+        if (accessToken==null || accessToken.trim().length()==0) {
+            logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken.");
+        }
+
+        // start
+        embedServer = new EmbedServer();
+        embedServer.start(address, port, appname, accessToken);
+    }
+
+    private void stopEmbedServer() {
+        // stop provider factory
+        if (embedServer != null) {
+            try {
+                embedServer.stop();
+            } catch (Exception e) {
+                logger.error(e.getMessage(), e);
+            }
+        }
+    }
+
+
+    // ---------------------- job handler repository ----------------------
+    private static ConcurrentMap<String, IJobHandler> jobHandlerRepository = new ConcurrentHashMap<String, IJobHandler>();
+    public static IJobHandler loadJobHandler(String name){
+        return jobHandlerRepository.get(name);
+    }
+    public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
+        logger.info(">>>>>>>>>>> xxl-job register jobhandler success, name:{}, jobHandler:{}", name, jobHandler);
+        return jobHandlerRepository.put(name, jobHandler);
+    }
+    protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod){
+        if (xxlJob == null) {
+            return;
+        }
+
+        String name = xxlJob.value();
+        //make and simplify the variables since they'll be called several times later
+        Class<?> clazz = bean.getClass();
+        String methodName = executeMethod.getName();
+        if (name.trim().length() == 0) {
+            throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + clazz + "#" + methodName + "] .");
+        }
+        if (loadJobHandler(name) != null) {
+            throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
+        }
+
+        // execute method
+        /*if (!(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(String.class))) {
+            throw new RuntimeException("xxl-job method-jobhandler param-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +
+                    "The correct method format like \" public ReturnT<String> execute(String param) \" .");
+        }
+        if (!method.getReturnType().isAssignableFrom(ReturnT.class)) {
+            throw new RuntimeException("xxl-job method-jobhandler return-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +
+                    "The correct method format like \" public ReturnT<String> execute(String param) \" .");
+        }*/
+
+        executeMethod.setAccessible(true);
+
+        // init and destroy
+        Method initMethod = null;
+        Method destroyMethod = null;
+
+        if (xxlJob.init().trim().length() > 0) {
+            try {
+                initMethod = clazz.getDeclaredMethod(xxlJob.init());
+                initMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
+            }
+        }
+        if (xxlJob.destroy().trim().length() > 0) {
+            try {
+                destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());
+                destroyMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
+            }
+        }
+
+        // registry jobhandler
+        registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
+
+    }
+
+
+    // ---------------------- job thread repository ----------------------
+    private static ConcurrentMap<Integer, JobThread> jobThreadRepository = new ConcurrentHashMap<Integer, JobThread>();
+    public static JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason){
+        JobThread newJobThread = new JobThread(jobId, handler);
+        newJobThread.start();
+        logger.info(">>>>>>>>>>> xxl-job regist JobThread success, jobId:{}, handler:{}", new Object[]{jobId, handler});
+
+        JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread);	// putIfAbsent | oh my god, map's put method return the old value!!!
+        if (oldJobThread != null) {
+            oldJobThread.toStop(removeOldReason);
+            oldJobThread.interrupt();
+        }
+
+        return newJobThread;
+    }
+    public static JobThread removeJobThread(int jobId, String removeOldReason){
+        JobThread oldJobThread = jobThreadRepository.remove(jobId);
+        if (oldJobThread != null) {
+            oldJobThread.toStop(removeOldReason);
+            oldJobThread.interrupt();
+
+            return oldJobThread;
+        }
+        return null;
+    }
+    public static JobThread loadJobThread(int jobId){
+        JobThread jobThread = jobThreadRepository.get(jobId);
+        return jobThread;
+    }
+
+}

+ 75 - 0
xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSimpleExecutor.java

@@ -0,0 +1,75 @@
+package com.xxl.job.core.executor.impl;
+
+import com.xxl.job.core.executor.XxlJobExecutor;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import com.xxl.job.core.handler.impl.MethodJobHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * xxl-job executor (for frameless)
+ *
+ * @author xuxueli 2020-11-05
+ */
+public class XxlJobSimpleExecutor extends XxlJobExecutor {
+    private static final Logger logger = LoggerFactory.getLogger(XxlJobSimpleExecutor.class);
+
+
+    private List<Object> xxlJobBeanList = new ArrayList<>();
+    public List<Object> getXxlJobBeanList() {
+        return xxlJobBeanList;
+    }
+    public void setXxlJobBeanList(List<Object> xxlJobBeanList) {
+        this.xxlJobBeanList = xxlJobBeanList;
+    }
+
+
+    @Override
+    public void start() {
+
+        // init JobHandler Repository (for method)
+        initJobHandlerMethodRepository(xxlJobBeanList);
+
+        // super start
+        try {
+            super.start();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+    }
+
+
+    private void initJobHandlerMethodRepository(List<Object> xxlJobBeanList) {
+        if (xxlJobBeanList==null || xxlJobBeanList.size()==0) {
+            return;
+        }
+
+        // init job handler from method
+        for (Object bean: xxlJobBeanList) {
+            // method
+            Method[] methods = bean.getClass().getDeclaredMethods();
+            if (methods.length == 0) {
+                continue;
+            }
+            for (Method executeMethod : methods) {
+                XxlJob xxlJob = executeMethod.getAnnotation(XxlJob.class);
+                // registry
+                registJobHandler(xxlJob, bean, executeMethod);
+            }
+
+        }
+
+    }
+
+}

+ 126 - 0
xxl-job-core/src/main/java/com/xxl/job/core/executor/impl/XxlJobSpringExecutor.java

@@ -0,0 +1,126 @@
+package com.xxl.job.core.executor.impl;
+
+import com.xxl.job.core.executor.XxlJobExecutor;
+import com.xxl.job.core.glue.GlueFactory;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import com.xxl.job.core.handler.impl.MethodJobHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.MethodIntrospector;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+
+/**
+ * xxl-job executor (for spring)
+ *
+ * @author xuxueli 2018-11-01 09:24:52
+ */
+public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
+    private static final Logger logger = LoggerFactory.getLogger(XxlJobSpringExecutor.class);
+
+
+    // start
+    @Override
+    public void afterSingletonsInstantiated() {
+
+        // init JobHandler Repository
+        /*initJobHandlerRepository(applicationContext);*/
+
+        // init JobHandler Repository (for method)
+        initJobHandlerMethodRepository(applicationContext);
+
+        // refresh GlueFactory
+        GlueFactory.refreshInstance(1);
+
+        // super start
+        try {
+            super.start();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // destroy
+    @Override
+    public void destroy() {
+        super.destroy();
+    }
+
+
+    /*private void initJobHandlerRepository(ApplicationContext applicationContext) {
+        if (applicationContext == null) {
+            return;
+        }
+
+        // init job handler action
+        Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(JobHandler.class);
+
+        if (serviceBeanMap != null && serviceBeanMap.size() > 0) {
+            for (Object serviceBean : serviceBeanMap.values()) {
+                if (serviceBean instanceof IJobHandler) {
+                    String name = serviceBean.getClass().getAnnotation(JobHandler.class).value();
+                    IJobHandler handler = (IJobHandler) serviceBean;
+                    if (loadJobHandler(name) != null) {
+                        throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
+                    }
+                    registJobHandler(name, handler);
+                }
+            }
+        }
+    }*/
+
+    private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
+        if (applicationContext == null) {
+            return;
+        }
+        // init job handler from method
+        String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
+        for (String beanDefinitionName : beanDefinitionNames) {
+            Object bean = applicationContext.getBean(beanDefinitionName);
+
+            Map<Method, XxlJob> annotatedMethods = null;   // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
+            try {
+                annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
+                        new MethodIntrospector.MetadataLookup<XxlJob>() {
+                            @Override
+                            public XxlJob inspect(Method method) {
+                                return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
+                            }
+                        });
+            } catch (Throwable ex) {
+                logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
+            }
+            if (annotatedMethods==null || annotatedMethods.isEmpty()) {
+                continue;
+            }
+
+            for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
+                Method executeMethod = methodXxlJobEntry.getKey();
+                XxlJob xxlJob = methodXxlJobEntry.getValue();
+                // regist
+                registJobHandler(xxlJob, bean, executeMethod);
+            }
+        }
+    }
+
+    // ---------------------- applicationContext ----------------------
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        XxlJobSpringExecutor.applicationContext = applicationContext;
+    }
+
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+
+}

+ 90 - 0
xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueFactory.java

@@ -0,0 +1,90 @@
+package com.xxl.job.core.glue;
+
+import com.xxl.job.core.glue.impl.SpringGlueFactory;
+import com.xxl.job.core.handler.IJobHandler;
+import groovy.lang.GroovyClassLoader;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * glue factory, product class/object by name
+ *
+ * @author xuxueli 2016-1-2 20:02:27
+ */
+public class GlueFactory {
+
+
+	private static GlueFactory glueFactory = new GlueFactory();
+	public static GlueFactory getInstance(){
+		return glueFactory;
+	}
+	public static void refreshInstance(int type){
+		if (type == 0) {
+			glueFactory = new GlueFactory();
+		} else if (type == 1) {
+			glueFactory = new SpringGlueFactory();
+		}
+	}
+
+
+	/**
+	 * groovy class loader
+	 */
+	private GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
+	private ConcurrentMap<String, Class<?>> CLASS_CACHE = new ConcurrentHashMap<>();
+
+	/**
+	 * load new instance, prototype
+	 *
+	 * @param codeSource
+	 * @return
+	 * @throws Exception
+	 */
+	public IJobHandler loadNewInstance(String codeSource) throws Exception{
+		if (codeSource!=null && codeSource.trim().length()>0) {
+			Class<?> clazz = getCodeSourceClass(codeSource);
+			if (clazz != null) {
+				Object instance = clazz.newInstance();
+				if (instance!=null) {
+					if (instance instanceof IJobHandler) {
+						this.injectService(instance);
+						return (IJobHandler) instance;
+					} else {
+						throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, "
+								+ "cannot convert from instance["+ instance.getClass() +"] to IJobHandler");
+					}
+				}
+			}
+		}
+		throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, instance is null");
+	}
+	private Class<?> getCodeSourceClass(String codeSource){
+		try {
+			// md5
+			byte[] md5 = MessageDigest.getInstance("MD5").digest(codeSource.getBytes());
+			String md5Str = new BigInteger(1, md5).toString(16);
+
+			Class<?> clazz = CLASS_CACHE.get(md5Str);
+			if(clazz == null){
+				clazz = groovyClassLoader.parseClass(codeSource);
+				CLASS_CACHE.putIfAbsent(md5Str, clazz);
+			}
+			return clazz;
+		} catch (Exception e) {
+			return groovyClassLoader.parseClass(codeSource);
+		}
+	}
+
+	/**
+	 * inject service of bean field
+	 *
+	 * @param instance
+	 */
+	public void injectService(Object instance) {
+		// do something
+	}
+
+}

+ 53 - 0
xxl-job-core/src/main/java/com/xxl/job/core/glue/GlueTypeEnum.java

@@ -0,0 +1,53 @@
+package com.xxl.job.core.glue;
+
+/**
+ * Created by xuxueli on 17/4/26.
+ */
+public enum GlueTypeEnum {
+
+    BEAN("BEAN", false, null, null),
+    GLUE_GROOVY("GLUE(Java)", false, null, null),
+    GLUE_SHELL("GLUE(Shell)", true, "bash", ".sh"),
+    GLUE_PYTHON("GLUE(Python)", true, "python", ".py"),
+    GLUE_PHP("GLUE(PHP)", true, "php", ".php"),
+    GLUE_NODEJS("GLUE(Nodejs)", true, "node", ".js"),
+    GLUE_POWERSHELL("GLUE(PowerShell)", true, "powershell", ".ps1");
+
+    private String desc;
+    private boolean isScript;
+    private String cmd;
+    private String suffix;
+
+    private GlueTypeEnum(String desc, boolean isScript, String cmd, String suffix) {
+        this.desc = desc;
+        this.isScript = isScript;
+        this.cmd = cmd;
+        this.suffix = suffix;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public boolean isScript() {
+        return isScript;
+    }
+
+    public String getCmd() {
+        return cmd;
+    }
+
+    public String getSuffix() {
+        return suffix;
+    }
+
+    public static GlueTypeEnum match(String name){
+        for (GlueTypeEnum item: GlueTypeEnum.values()) {
+            if (item.name().equals(name)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+}

+ 80 - 0
xxl-job-core/src/main/java/com/xxl/job/core/glue/impl/SpringGlueFactory.java

@@ -0,0 +1,80 @@
+package com.xxl.job.core.glue.impl;
+
+import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
+import com.xxl.job.core.glue.GlueFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.AnnotationUtils;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * @author xuxueli 2018-11-01
+ */
+public class SpringGlueFactory extends GlueFactory {
+    private static Logger logger = LoggerFactory.getLogger(SpringGlueFactory.class);
+
+
+    /**
+     * inject action of spring
+     * @param instance
+     */
+    @Override
+    public void injectService(Object instance){
+        if (instance==null) {
+            return;
+        }
+
+        if (XxlJobSpringExecutor.getApplicationContext() == null) {
+            return;
+        }
+
+        Field[] fields = instance.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            if (Modifier.isStatic(field.getModifiers())) {
+                continue;
+            }
+
+            Object fieldBean = null;
+            // with bean-id, bean could be found by both @Resource and @Autowired, or bean could only be found by @Autowired
+
+            if (AnnotationUtils.getAnnotation(field, Resource.class) != null) {
+                try {
+                    Resource resource = AnnotationUtils.getAnnotation(field, Resource.class);
+                    if (resource.name()!=null && resource.name().length()>0){
+                        fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(resource.name());
+                    } else {
+                        fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getName());
+                    }
+                } catch (Exception e) {
+                }
+                if (fieldBean==null ) {
+                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType());
+                }
+            } else if (AnnotationUtils.getAnnotation(field, Autowired.class) != null) {
+                Qualifier qualifier = AnnotationUtils.getAnnotation(field, Qualifier.class);
+                if (qualifier!=null && qualifier.value()!=null && qualifier.value().length()>0) {
+                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(qualifier.value());
+                } else {
+                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType());
+                }
+            }
+
+            if (fieldBean!=null) {
+                field.setAccessible(true);
+                try {
+                    field.set(instance, fieldBean);
+                } catch (IllegalArgumentException e) {
+                    logger.error(e.getMessage(), e);
+                } catch (IllegalAccessException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+}

+ 38 - 0
xxl-job-core/src/main/java/com/xxl/job/core/handler/IJobHandler.java

@@ -0,0 +1,38 @@
+package com.xxl.job.core.handler;
+
+/**
+ * job handler
+ *
+ * @author xuxueli 2015-12-19 19:06:38
+ */
+public abstract class IJobHandler {
+
+
+	/**
+	 * execute handler, invoked when executor receives a scheduling request
+	 *
+	 * @throws Exception
+	 */
+	public abstract void execute() throws Exception;
+
+
+	/*@Deprecated
+	public abstract ReturnT<String> execute(String param) throws Exception;*/
+
+	/**
+	 * init handler, invoked when JobThread init
+	 */
+	public void init() throws Exception {
+		// do something
+	}
+
+
+	/**
+	 * destroy handler, invoked when JobThread destroy
+	 */
+	public void destroy() throws Exception {
+		// do something
+	}
+
+
+}

+ 24 - 0
xxl-job-core/src/main/java/com/xxl/job/core/handler/annotation/JobHandler.java

@@ -0,0 +1,24 @@
+//package com.xxl.job.core.handler.annotation;
+//
+//import java.lang.annotation.ElementType;
+//import java.lang.annotation.Inherited;
+//import java.lang.annotation.Retention;
+//import java.lang.annotation.RetentionPolicy;
+//import java.lang.annotation.Target;
+//
+///**
+// * annotation for job handler
+// *
+// * will be replaced by {@link com.xxl.job.core.handler.annotation.XxlJob}
+// *
+// * @author 2016-5-17 21:06:49
+// */
+//@Target({ElementType.TYPE})
+//@Retention(RetentionPolicy.RUNTIME)
+//@Inherited
+//@Deprecated
+//public @interface JobHandler {
+//
+//    String value();
+//
+//}

+ 30 - 0
xxl-job-core/src/main/java/com/xxl/job/core/handler/annotation/XxlJob.java

@@ -0,0 +1,30 @@
+package com.xxl.job.core.handler.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * annotation for method jobhandler
+ *
+ * @author xuxueli 2019-12-11 20:50:13
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface XxlJob {
+
+    /**
+     * jobhandler name
+     */
+    String value();
+
+    /**
+     * init handler, invoked when JobThread init
+     */
+    String init() default "";
+
+    /**
+     * destroy handler, invoked when JobThread destroy
+     */
+    String destroy() default "";
+
+}

+ 38 - 0
xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/GlueJobHandler.java

@@ -0,0 +1,38 @@
+package com.xxl.job.core.handler.impl;
+
+import com.xxl.job.core.context.XxlJobHelper;
+import com.xxl.job.core.handler.IJobHandler;
+
+/**
+ * glue job handler
+ *
+ * @author xuxueli 2016-5-19 21:05:45
+ */
+public class GlueJobHandler extends IJobHandler {
+
+	private long glueUpdatetime;
+	private IJobHandler jobHandler;
+	public GlueJobHandler(IJobHandler jobHandler, long glueUpdatetime) {
+		this.jobHandler = jobHandler;
+		this.glueUpdatetime = glueUpdatetime;
+	}
+	public long getGlueUpdatetime() {
+		return glueUpdatetime;
+	}
+
+	@Override
+	public void execute() throws Exception {
+		XxlJobHelper.log("----------- glue.version:"+ glueUpdatetime +" -----------");
+		jobHandler.execute();
+	}
+
+	@Override
+	public void init() throws Exception {
+		this.jobHandler.init();
+	}
+
+	@Override
+	public void destroy() throws Exception {
+		this.jobHandler.destroy();
+	}
+}

+ 53 - 0
xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/MethodJobHandler.java

@@ -0,0 +1,53 @@
+package com.xxl.job.core.handler.impl;
+
+import com.xxl.job.core.handler.IJobHandler;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author xuxueli 2019-12-11 21:12:18
+ */
+public class MethodJobHandler extends IJobHandler {
+
+    private final Object target;
+    private final Method method;
+    private Method initMethod;
+    private Method destroyMethod;
+
+    public MethodJobHandler(Object target, Method method, Method initMethod, Method destroyMethod) {
+        this.target = target;
+        this.method = method;
+
+        this.initMethod = initMethod;
+        this.destroyMethod = destroyMethod;
+    }
+
+    @Override
+    public void execute() throws Exception {
+        Class<?>[] paramTypes = method.getParameterTypes();
+        if (paramTypes.length > 0) {
+            method.invoke(target, new Object[paramTypes.length]);       // method-param can not be primitive-types
+        } else {
+            method.invoke(target);
+        }
+    }
+
+    @Override
+    public void init() throws Exception {
+        if(initMethod != null) {
+            initMethod.invoke(target);
+        }
+    }
+
+    @Override
+    public void destroy() throws Exception {
+        if(destroyMethod != null) {
+            destroyMethod.invoke(target);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return super.toString()+"["+ target.getClass() + "#" + method.getName() +"]";
+    }
+}

+ 93 - 0
xxl-job-core/src/main/java/com/xxl/job/core/handler/impl/ScriptJobHandler.java

@@ -0,0 +1,93 @@
+package com.xxl.job.core.handler.impl;
+
+import com.xxl.job.core.context.XxlJobContext;
+import com.xxl.job.core.context.XxlJobHelper;
+import com.xxl.job.core.glue.GlueTypeEnum;
+import com.xxl.job.core.handler.IJobHandler;
+import com.xxl.job.core.log.XxlJobFileAppender;
+import com.xxl.job.core.util.ScriptUtil;
+
+import java.io.File;
+
+/**
+ * Created by xuxueli on 17/4/27.
+ */
+public class ScriptJobHandler extends IJobHandler {
+
+    private int jobId;
+    private long glueUpdatetime;
+    private String gluesource;
+    private GlueTypeEnum glueType;
+
+    public ScriptJobHandler(int jobId, long glueUpdatetime, String gluesource, GlueTypeEnum glueType){
+        this.jobId = jobId;
+        this.glueUpdatetime = glueUpdatetime;
+        this.gluesource = gluesource;
+        this.glueType = glueType;
+
+        // clean old script file
+        File glueSrcPath = new File(XxlJobFileAppender.getGlueSrcPath());
+        if (glueSrcPath.exists()) {
+            File[] glueSrcFileList = glueSrcPath.listFiles();
+            if (glueSrcFileList!=null && glueSrcFileList.length>0) {
+                for (File glueSrcFileItem : glueSrcFileList) {
+                    if (glueSrcFileItem.getName().startsWith(String.valueOf(jobId)+"_")) {
+                        glueSrcFileItem.delete();
+                    }
+                }
+            }
+        }
+
+    }
+
+    public long getGlueUpdatetime() {
+        return glueUpdatetime;
+    }
+
+    @Override
+    public void execute() throws Exception {
+
+        if (!glueType.isScript()) {
+            XxlJobHelper.handleFail("glueType["+ glueType +"] invalid.");
+            return;
+        }
+
+        // cmd
+        String cmd = glueType.getCmd();
+
+        // make script file
+        String scriptFileName = XxlJobFileAppender.getGlueSrcPath()
+                .concat(File.separator)
+                .concat(String.valueOf(jobId))
+                .concat("_")
+                .concat(String.valueOf(glueUpdatetime))
+                .concat(glueType.getSuffix());
+        File scriptFile = new File(scriptFileName);
+        if (!scriptFile.exists()) {
+            ScriptUtil.markScriptFile(scriptFileName, gluesource);
+        }
+
+        // log file
+        String logFileName = XxlJobContext.getXxlJobContext().getJobLogFileName();
+
+        // script params:0=param、1=分片序号、2=分片总数
+        String[] scriptParams = new String[3];
+        scriptParams[0] = XxlJobHelper.getJobParam();
+        scriptParams[1] = String.valueOf(XxlJobContext.getXxlJobContext().getShardIndex());
+        scriptParams[2] = String.valueOf(XxlJobContext.getXxlJobContext().getShardTotal());
+
+        // invoke
+        XxlJobHelper.log("----------- script file:"+ scriptFileName +" -----------");
+        int exitValue = ScriptUtil.execToFile(cmd, scriptFileName, logFileName, scriptParams);
+
+        if (exitValue == 0) {
+            XxlJobHelper.handleSuccess();
+            return;
+        } else {
+            XxlJobHelper.handleFail("script exit value("+exitValue+") is failed");
+            return ;
+        }
+
+    }
+
+}

+ 220 - 0
xxl-job-core/src/main/java/com/xxl/job/core/log/XxlJobFileAppender.java

@@ -0,0 +1,220 @@
+package com.xxl.job.core.log;
+
+import com.xxl.job.core.biz.model.LogResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * store trigger log in each log-file
+ * @author xuxueli 2016-3-12 19:25:12
+ */
+public class XxlJobFileAppender {
+	private static Logger logger = LoggerFactory.getLogger(XxlJobFileAppender.class);
+
+	/**
+	 * log base path
+	 *
+	 * strut like:
+	 * 	---/
+	 * 	---/gluesource/
+	 * 	---/gluesource/10_1514171108000.js
+	 * 	---/gluesource/10_1514171108000.js
+	 * 	---/2017-12-25/
+	 * 	---/2017-12-25/639.log
+	 * 	---/2017-12-25/821.log
+	 *
+	 */
+	private static String logBasePath = "/data/applogs/xxl-job/jobhandler";
+	private static String glueSrcPath = logBasePath.concat("/gluesource");
+	public static void initLogPath(String logPath){
+		// init
+		if (logPath!=null && logPath.trim().length()>0) {
+			logBasePath = logPath;
+		}
+		// mk base dir
+		File logPathDir = new File(logBasePath);
+		if (!logPathDir.exists()) {
+			logPathDir.mkdirs();
+		}
+		logBasePath = logPathDir.getPath();
+
+		// mk glue dir
+		File glueBaseDir = new File(logPathDir, "gluesource");
+		if (!glueBaseDir.exists()) {
+			glueBaseDir.mkdirs();
+		}
+		glueSrcPath = glueBaseDir.getPath();
+	}
+	public static String getLogPath() {
+		return logBasePath;
+	}
+	public static String getGlueSrcPath() {
+		return glueSrcPath;
+	}
+
+	/**
+	 * log filename, like "logPath/yyyy-MM-dd/9999.log"
+	 *
+	 * @param triggerDate
+	 * @param logId
+	 * @return
+	 */
+	public static String makeLogFileName(Date triggerDate, long logId) {
+
+		// filePath/yyyy-MM-dd
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");	// avoid concurrent problem, can not be static
+		File logFilePath = new File(getLogPath(), sdf.format(triggerDate));
+		if (!logFilePath.exists()) {
+			logFilePath.mkdir();
+		}
+
+		// filePath/yyyy-MM-dd/9999.log
+		String logFileName = logFilePath.getPath()
+				.concat(File.separator)
+				.concat(String.valueOf(logId))
+				.concat(".log");
+		return logFileName;
+	}
+
+	/**
+	 * append log
+	 *
+	 * @param logFileName
+	 * @param appendLog
+	 */
+	public static void appendLog(String logFileName, String appendLog) {
+
+		// log file
+		if (logFileName==null || logFileName.trim().length()==0) {
+			return;
+		}
+		File logFile = new File(logFileName);
+
+		if (!logFile.exists()) {
+			try {
+				logFile.createNewFile();
+			} catch (IOException e) {
+				logger.error(e.getMessage(), e);
+				return;
+			}
+		}
+
+		// log
+		if (appendLog == null) {
+			appendLog = "";
+		}
+		appendLog += "\r\n";
+		
+		// append file content
+		FileOutputStream fos = null;
+		try {
+			fos = new FileOutputStream(logFile, true);
+			fos.write(appendLog.getBytes("utf-8"));
+			fos.flush();
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+		} finally {
+			if (fos != null) {
+				try {
+					fos.close();
+				} catch (IOException e) {
+					logger.error(e.getMessage(), e);
+				}
+			}
+		}
+		
+	}
+
+	/**
+	 * support read log-file
+	 *
+	 * @param logFileName
+	 * @return log content
+	 */
+	public static LogResult readLog(String logFileName, int fromLineNum){
+
+		// valid log file
+		if (logFileName==null || logFileName.trim().length()==0) {
+            return new LogResult(fromLineNum, 0, "readLog fail, logFile not found", true);
+		}
+		File logFile = new File(logFileName);
+
+		if (!logFile.exists()) {
+            return new LogResult(fromLineNum, 0, "readLog fail, logFile not exists", true);
+		}
+
+		// read file
+		StringBuffer logContentBuffer = new StringBuffer();
+		int toLineNum = 0;
+		LineNumberReader reader = null;
+		try {
+			//reader = new LineNumberReader(new FileReader(logFile));
+			reader = new LineNumberReader(new InputStreamReader(new FileInputStream(logFile), "utf-8"));
+			String line = null;
+
+			while ((line = reader.readLine())!=null) {
+				toLineNum = reader.getLineNumber();		// [from, to], start as 1
+				if (toLineNum >= fromLineNum) {
+					logContentBuffer.append(line).append("\n");
+				}
+			}
+		} catch (IOException e) {
+			logger.error(e.getMessage(), e);
+		} finally {
+			if (reader != null) {
+				try {
+					reader.close();
+				} catch (IOException e) {
+					logger.error(e.getMessage(), e);
+				}
+			}
+		}
+
+		// result
+		LogResult logResult = new LogResult(fromLineNum, toLineNum, logContentBuffer.toString(), false);
+		return logResult;
+
+		/*
+        // it will return the number of characters actually skipped
+        reader.skip(Long.MAX_VALUE);
+        int maxLineNum = reader.getLineNumber();
+        maxLineNum++;	// 最大行号
+        */
+	}
+
+	/**
+	 * read log data
+	 * @param logFile
+	 * @return log line content
+	 */
+	public static String readLines(File logFile){
+		BufferedReader reader = null;
+		try {
+			reader = new BufferedReader(new InputStreamReader(new FileInputStream(logFile), "utf-8"));
+			if (reader != null) {
+				StringBuilder sb = new StringBuilder();
+				String line = null;
+				while ((line = reader.readLine()) != null) {
+					sb.append(line).append("\n");
+				}
+				return sb.toString();
+			}
+		} catch (IOException e) {
+			logger.error(e.getMessage(), e);
+		} finally {
+			if (reader != null) {
+				try {
+					reader.close();
+				} catch (IOException e) {
+					logger.error(e.getMessage(), e);
+				}
+			}
+		}
+		return null;
+	}
+
+}

+ 266 - 0
xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java

@@ -0,0 +1,266 @@
+package com.xxl.job.core.server;
+
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.biz.impl.ExecutorBizImpl;
+import com.xxl.job.core.biz.model.*;
+import com.xxl.job.core.thread.ExecutorRegistryThread;
+import com.xxl.job.core.util.GsonTool;
+import com.xxl.job.core.util.ThrowableUtil;
+import com.xxl.job.core.util.XxlJobRemotingUtil;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.*;
+import io.netty.handler.timeout.IdleStateEvent;
+import io.netty.handler.timeout.IdleStateHandler;
+import io.netty.util.CharsetUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.*;
+
+/**
+ * Copy from : https://github.com/xuxueli/xxl-rpc
+ *
+ * @author xuxueli 2020-04-11 21:25
+ */
+public class EmbedServer {
+    private static final Logger logger = LoggerFactory.getLogger(EmbedServer.class);
+
+    private ExecutorBiz executorBiz;
+    private Thread thread;
+
+    public void start(final String address, final int port, final String appname, final String accessToken) {
+        executorBiz = new ExecutorBizImpl();
+        thread = new Thread(new Runnable() {
+
+            @Override
+            public void run() {
+
+                // param
+                EventLoopGroup bossGroup = new NioEventLoopGroup();
+                EventLoopGroup workerGroup = new NioEventLoopGroup();
+                ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
+                        0,
+                        200,
+                        60L,
+                        TimeUnit.SECONDS,
+                        new LinkedBlockingQueue<Runnable>(2000),
+                        new ThreadFactory() {
+                            @Override
+                            public Thread newThread(Runnable r) {
+                                return new Thread(r, "xxl-job, EmbedServer bizThreadPool-" + r.hashCode());
+                            }
+                        },
+                        new RejectedExecutionHandler() {
+                            @Override
+                            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+                                throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
+                            }
+                        });
+
+
+                try {
+                    // start server
+                    ServerBootstrap bootstrap = new ServerBootstrap();
+                    bootstrap.group(bossGroup, workerGroup)
+                            .channel(NioServerSocketChannel.class)
+                            .childHandler(new ChannelInitializer<SocketChannel>() {
+                                @Override
+                                public void initChannel(SocketChannel channel) throws Exception {
+                                    channel.pipeline()
+                                            .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))  // beat 3N, close if idle
+                                            .addLast(new HttpServerCodec())
+                                            .addLast(new HttpObjectAggregator(5 * 1024 * 1024))  // merge request & reponse to FULL
+                                            .addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
+                                }
+                            })
+                            .childOption(ChannelOption.SO_KEEPALIVE, true);
+
+                    // bind
+                    ChannelFuture future = bootstrap.bind(port).sync();
+
+                    logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);
+
+                    // start registry
+                    startRegistry(appname, address);
+
+                    // wait util stop
+                    future.channel().closeFuture().sync();
+
+                } catch (InterruptedException e) {
+                    if (e instanceof InterruptedException) {
+                        logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
+                    } else {
+                        logger.error(">>>>>>>>>>> xxl-job remoting server error.", e);
+                    }
+                } finally {
+                    // stop
+                    try {
+                        workerGroup.shutdownGracefully();
+                        bossGroup.shutdownGracefully();
+                    } catch (Exception e) {
+                        logger.error(e.getMessage(), e);
+                    }
+                }
+
+            }
+
+        });
+        thread.setDaemon(true);	// daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
+        thread.start();
+    }
+
+    public void stop() throws Exception {
+        // destroy server thread
+        if (thread!=null && thread.isAlive()) {
+            thread.interrupt();
+        }
+
+        // stop registry
+        stopRegistry();
+        logger.info(">>>>>>>>>>> xxl-job remoting server destroy success.");
+    }
+
+
+    // ---------------------- registry ----------------------
+
+    /**
+     * netty_http
+     *
+     * Copy from : https://github.com/xuxueli/xxl-rpc
+     *
+     * @author xuxueli 2015-11-24 22:25:15
+     */
+    public static class EmbedHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
+        private static final Logger logger = LoggerFactory.getLogger(EmbedHttpServerHandler.class);
+
+        private ExecutorBiz executorBiz;
+        private String accessToken;
+        private ThreadPoolExecutor bizThreadPool;
+        public EmbedHttpServerHandler(ExecutorBiz executorBiz, String accessToken, ThreadPoolExecutor bizThreadPool) {
+            this.executorBiz = executorBiz;
+            this.accessToken = accessToken;
+            this.bizThreadPool = bizThreadPool;
+        }
+
+        @Override
+        protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
+
+            // request parse
+            //final byte[] requestBytes = ByteBufUtil.getBytes(msg.content());    // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8);
+            String requestData = msg.content().toString(CharsetUtil.UTF_8);
+            String uri = msg.uri();
+            HttpMethod httpMethod = msg.method();
+            boolean keepAlive = HttpUtil.isKeepAlive(msg);
+            String accessTokenReq = msg.headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN);
+
+            // invoke
+            bizThreadPool.execute(new Runnable() {
+                @Override
+                public void run() {
+                    // do invoke
+                    Object responseObj = process(httpMethod, uri, requestData, accessTokenReq);
+
+                    // to json
+                    String responseJson = GsonTool.toJson(responseObj);
+
+                    // write response
+                    writeResponse(ctx, keepAlive, responseJson);
+                }
+            });
+        }
+
+        private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {
+
+            // valid
+            if (HttpMethod.POST != httpMethod) {
+                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 (accessToken!=null
+                    && accessToken.trim().length()>0
+                    && !accessToken.equals(accessTokenReq)) {
+                return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
+            }
+
+            // services mapping
+            try {
+                if ("/beat".equals(uri)) {
+                    return executorBiz.beat();
+                } else if ("/idleBeat".equals(uri)) {
+                    IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
+                    return executorBiz.idleBeat(idleBeatParam);
+                } else if ("/run".equals(uri)) {
+                    TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
+                    return executorBiz.run(triggerParam);
+                } else if ("/kill".equals(uri)) {
+                    KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
+                    return executorBiz.kill(killParam);
+                } else if ("/log".equals(uri)) {
+                    LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
+                    return executorBiz.log(logParam);
+                } else {
+                    return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
+                }
+            } catch (Exception e) {
+                logger.error(e.getMessage(), e);
+                return new ReturnT<String>(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.toString(e));
+            }
+        }
+
+        /**
+         * write response
+         */
+        private void writeResponse(ChannelHandlerContext ctx, boolean keepAlive, String responseJson) {
+            // write response
+            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(responseJson, CharsetUtil.UTF_8));   //  Unpooled.wrappedBuffer(responseJson)
+            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");       // HttpHeaderValues.TEXT_PLAIN.toString()
+            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
+            if (keepAlive) {
+                response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
+            }
+            ctx.writeAndFlush(response);
+        }
+
+        @Override
+        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+            ctx.flush();
+        }
+
+        @Override
+        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+            logger.error(">>>>>>>>>>> xxl-job provider netty_http server caught exception", cause);
+            ctx.close();
+        }
+
+        @Override
+        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+            if (evt instanceof IdleStateEvent) {
+                ctx.channel().close();      // beat 3N, close if idle
+                logger.debug(">>>>>>>>>>> xxl-job provider netty_http server close an idle channel.");
+            } else {
+                super.userEventTriggered(ctx, evt);
+            }
+        }
+    }
+
+    // ---------------------- registry ----------------------
+
+    public void startRegistry(final String appname, final String address) {
+        // start registry
+        ExecutorRegistryThread.getInstance().start(appname, address);
+    }
+
+    public void stopRegistry() {
+        // stop registry
+        ExecutorRegistryThread.getInstance().toStop();
+    }
+
+
+}

+ 129 - 0
xxl-job-core/src/main/java/com/xxl/job/core/thread/ExecutorRegistryThread.java

@@ -0,0 +1,129 @@
+package com.xxl.job.core.thread;
+
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.model.RegistryParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.enums.RegistryConfig;
+import com.xxl.job.core.executor.XxlJobExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by xuxueli on 17/3/2.
+ */
+public class ExecutorRegistryThread {
+    private static Logger logger = LoggerFactory.getLogger(ExecutorRegistryThread.class);
+
+    private static ExecutorRegistryThread instance = new ExecutorRegistryThread();
+    public static ExecutorRegistryThread getInstance(){
+        return instance;
+    }
+
+    private Thread registryThread;
+    private volatile boolean toStop = false;
+    public void start(final String appname, final String address){
+
+        // valid
+        if (appname==null || appname.trim().length()==0) {
+            logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, appname is null.");
+            return;
+        }
+        if (XxlJobExecutor.getAdminBizList() == null) {
+            logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, adminAddresses is null.");
+            return;
+        }
+
+        registryThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+
+                // registry
+                while (!toStop) {
+                    try {
+                        RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
+                        for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
+                            try {
+                                ReturnT<String> registryResult = adminBiz.registry(registryParam);
+                                if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
+                                    registryResult = ReturnT.SUCCESS;
+                                    logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
+                                    break;
+                                } else {
+                                    logger.info(">>>>>>>>>>> xxl-job registry fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
+                                }
+                            } catch (Exception e) {
+                                logger.info(">>>>>>>>>>> xxl-job registry error, registryParam:{}", registryParam, e);
+                            }
+
+                        }
+                    } catch (Exception e) {
+                        if (!toStop) {
+                            logger.error(e.getMessage(), e);
+                        }
+
+                    }
+
+                    try {
+                        if (!toStop) {
+                            TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
+                        }
+                    } catch (InterruptedException e) {
+                        if (!toStop) {
+                            logger.warn(">>>>>>>>>>> xxl-job, executor registry thread interrupted, error msg:{}", e.getMessage());
+                        }
+                    }
+                }
+
+                // registry remove
+                try {
+                    RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
+                    for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
+                        try {
+                            ReturnT<String> registryResult = adminBiz.registryRemove(registryParam);
+                            if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
+                                registryResult = ReturnT.SUCCESS;
+                                logger.info(">>>>>>>>>>> xxl-job registry-remove success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
+                                break;
+                            } else {
+                                logger.info(">>>>>>>>>>> xxl-job registry-remove fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
+                            }
+                        } catch (Exception e) {
+                            if (!toStop) {
+                                logger.info(">>>>>>>>>>> xxl-job registry-remove error, registryParam:{}", registryParam, e);
+                            }
+
+                        }
+
+                    }
+                } catch (Exception e) {
+                    if (!toStop) {
+                        logger.error(e.getMessage(), e);
+                    }
+                }
+                logger.info(">>>>>>>>>>> xxl-job, executor registry thread destroy.");
+
+            }
+        });
+        registryThread.setDaemon(true);
+        registryThread.setName("xxl-job, executor ExecutorRegistryThread");
+        registryThread.start();
+    }
+
+    public void toStop() {
+        toStop = true;
+
+        // interrupt and wait
+        if (registryThread != null) {
+            registryThread.interrupt();
+            try {
+                registryThread.join();
+            } catch (InterruptedException e) {
+                logger.error(e.getMessage(), e);
+            }
+        }
+
+    }
+
+}

+ 124 - 0
xxl-job-core/src/main/java/com/xxl/job/core/thread/JobLogFileCleanThread.java

@@ -0,0 +1,124 @@
+package com.xxl.job.core.thread;
+
+import com.xxl.job.core.log.XxlJobFileAppender;
+import com.xxl.job.core.util.FileUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * job file clean thread
+ *
+ * @author xuxueli 2017-12-29 16:23:43
+ */
+public class JobLogFileCleanThread {
+    private static Logger logger = LoggerFactory.getLogger(JobLogFileCleanThread.class);
+
+    private static JobLogFileCleanThread instance = new JobLogFileCleanThread();
+    public static JobLogFileCleanThread getInstance(){
+        return instance;
+    }
+
+    private Thread localThread;
+    private volatile boolean toStop = false;
+    public void start(final long logRetentionDays){
+
+        // limit min value
+        if (logRetentionDays < 3 ) {
+            return;
+        }
+
+        localThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (!toStop) {
+                    try {
+                        // clean log dir, over logRetentionDays
+                        File[] childDirs = new File(XxlJobFileAppender.getLogPath()).listFiles();
+                        if (childDirs!=null && childDirs.length>0) {
+
+                            // today
+                            Calendar todayCal = Calendar.getInstance();
+                            todayCal.set(Calendar.HOUR_OF_DAY,0);
+                            todayCal.set(Calendar.MINUTE,0);
+                            todayCal.set(Calendar.SECOND,0);
+                            todayCal.set(Calendar.MILLISECOND,0);
+
+                            Date todayDate = todayCal.getTime();
+
+                            for (File childFile: childDirs) {
+
+                                // valid
+                                if (!childFile.isDirectory()) {
+                                    continue;
+                                }
+                                if (childFile.getName().indexOf("-") == -1) {
+                                    continue;
+                                }
+
+                                // file create date
+                                Date logFileCreateDate = null;
+                                try {
+                                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+                                    logFileCreateDate = simpleDateFormat.parse(childFile.getName());
+                                } catch (ParseException e) {
+                                    logger.error(e.getMessage(), e);
+                                }
+                                if (logFileCreateDate == null) {
+                                    continue;
+                                }
+
+                                if ((todayDate.getTime()-logFileCreateDate.getTime()) >= logRetentionDays * (24 * 60 * 60 * 1000) ) {
+                                    FileUtil.deleteRecursively(childFile);
+                                }
+
+                            }
+                        }
+
+                    } catch (Exception e) {
+                        if (!toStop) {
+                            logger.error(e.getMessage(), e);
+                        }
+
+                    }
+
+                    try {
+                        TimeUnit.DAYS.sleep(1);
+                    } catch (InterruptedException e) {
+                        if (!toStop) {
+                            logger.error(e.getMessage(), e);
+                        }
+                    }
+                }
+                logger.info(">>>>>>>>>>> xxl-job, executor JobLogFileCleanThread thread destroy.");
+
+            }
+        });
+        localThread.setDaemon(true);
+        localThread.setName("xxl-job, executor JobLogFileCleanThread");
+        localThread.start();
+    }
+
+    public void toStop() {
+        toStop = true;
+
+        if (localThread == null) {
+            return;
+        }
+
+        // interrupt and wait
+        localThread.interrupt();
+        try {
+            localThread.join();
+        } catch (InterruptedException e) {
+            logger.error(e.getMessage(), e);
+        }
+    }
+
+}

+ 252 - 0
xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java

@@ -0,0 +1,252 @@
+package com.xxl.job.core.thread;
+
+import com.xxl.job.core.biz.model.HandleCallbackParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.biz.model.TriggerParam;
+import com.xxl.job.core.context.XxlJobContext;
+import com.xxl.job.core.context.XxlJobHelper;
+import com.xxl.job.core.executor.XxlJobExecutor;
+import com.xxl.job.core.handler.IJobHandler;
+import com.xxl.job.core.log.XxlJobFileAppender;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.*;
+
+
+/**
+ * handler thread
+ * @author xuxueli 2016-1-16 19:52:47
+ */
+public class JobThread extends Thread{
+	private static Logger logger = LoggerFactory.getLogger(JobThread.class);
+
+	private int jobId;
+	private IJobHandler handler;
+	private LinkedBlockingQueue<TriggerParam> triggerQueue;
+	private Set<Long> triggerLogIdSet;		// avoid repeat trigger for the same TRIGGER_LOG_ID
+
+	private volatile boolean toStop = false;
+	private String stopReason;
+
+    private boolean running = false;    // if running job
+	private int idleTimes = 0;			// idel times
+
+
+	public JobThread(int jobId, IJobHandler handler) {
+		this.jobId = jobId;
+		this.handler = handler;
+		this.triggerQueue = new LinkedBlockingQueue<TriggerParam>();
+		this.triggerLogIdSet = Collections.synchronizedSet(new HashSet<Long>());
+
+		// assign job thread name
+		this.setName("xxl-job, JobThread-"+jobId+"-"+System.currentTimeMillis());
+	}
+	public IJobHandler getHandler() {
+		return handler;
+	}
+
+    /**
+     * new trigger to queue
+     *
+     * @param triggerParam
+     * @return
+     */
+	public ReturnT<String> pushTriggerQueue(TriggerParam triggerParam) {
+		// avoid repeat
+		if (triggerLogIdSet.contains(triggerParam.getLogId())) {
+			logger.info(">>>>>>>>>>> repeate trigger job, logId:{}", triggerParam.getLogId());
+			return new ReturnT<String>(ReturnT.FAIL_CODE, "repeate trigger job, logId:" + triggerParam.getLogId());
+		}
+
+		triggerLogIdSet.add(triggerParam.getLogId());
+		triggerQueue.add(triggerParam);
+        return ReturnT.SUCCESS;
+	}
+
+    /**
+     * kill job thread
+     *
+     * @param stopReason
+     */
+	public void toStop(String stopReason) {
+		/**
+		 * Thread.interrupt只支持终止线程的阻塞状态(wait、join、sleep),
+		 * 在阻塞出抛出InterruptedException异常,但是并不会终止运行的线程本身;
+		 * 所以需要注意,此处彻底销毁本线程,需要通过共享变量方式;
+		 */
+		this.toStop = true;
+		this.stopReason = stopReason;
+	}
+
+    /**
+     * is running job
+     * @return
+     */
+    public boolean isRunningOrHasQueue() {
+        return running || triggerQueue.size()>0;
+    }
+
+    @Override
+	public void run() {
+
+    	// init
+    	try {
+			handler.init();
+		} catch (Throwable e) {
+    		logger.error(e.getMessage(), e);
+		}
+
+		// execute
+		while(!toStop){
+			running = false;
+			idleTimes++;
+
+            TriggerParam triggerParam = null;
+            try {
+				// to check toStop signal, we need cycle, so wo cannot use queue.take(), instand of poll(timeout)
+				triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
+				if (triggerParam!=null) {
+					running = true;
+					idleTimes = 0;
+					triggerLogIdSet.remove(triggerParam.getLogId());
+
+					// log filename, like "logPath/yyyy-MM-dd/9999.log"
+					String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTime()), triggerParam.getLogId());
+					XxlJobContext xxlJobContext = new XxlJobContext(
+							triggerParam.getJobId(),
+							triggerParam.getExecutorParams(),
+							logFileName,
+							triggerParam.getBroadcastIndex(),
+							triggerParam.getBroadcastTotal());
+
+					// init job context
+					XxlJobContext.setXxlJobContext(xxlJobContext);
+
+					// execute
+					XxlJobHelper.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + xxlJobContext.getJobParam());
+
+					if (triggerParam.getExecutorTimeout() > 0) {
+						// limit timeout
+						Thread futureThread = null;
+						try {
+							FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
+								@Override
+								public Boolean call() throws Exception {
+
+									// init job context
+									XxlJobContext.setXxlJobContext(xxlJobContext);
+
+									handler.execute();
+									return true;
+								}
+							});
+							futureThread = new Thread(futureTask);
+							futureThread.start();
+
+							Boolean tempResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS);
+						} catch (TimeoutException e) {
+
+							XxlJobHelper.log("<br>----------- xxl-job job execute timeout");
+							XxlJobHelper.log(e);
+
+							// handle result
+							XxlJobHelper.handleTimeout("job execute timeout ");
+						} finally {
+							futureThread.interrupt();
+						}
+					} else {
+						// just execute
+						handler.execute();
+					}
+
+					// valid execute handle data
+					if (XxlJobContext.getXxlJobContext().getHandleCode() <= 0) {
+						XxlJobHelper.handleFail("job handle result lost.");
+					} else {
+						String tempHandleMsg = XxlJobContext.getXxlJobContext().getHandleMsg();
+						tempHandleMsg = (tempHandleMsg!=null&&tempHandleMsg.length()>50000)
+								?tempHandleMsg.substring(0, 50000).concat("...")
+								:tempHandleMsg;
+						XxlJobContext.getXxlJobContext().setHandleMsg(tempHandleMsg);
+					}
+					XxlJobHelper.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- Result: handleCode="
+							+ XxlJobContext.getXxlJobContext().getHandleCode()
+							+ ", handleMsg = "
+							+ XxlJobContext.getXxlJobContext().getHandleMsg()
+					);
+
+				} else {
+					if (idleTimes > 30) {
+						if(triggerQueue.size() == 0) {	// avoid concurrent trigger causes jobId-lost
+							XxlJobExecutor.removeJobThread(jobId, "excutor idel times over limit.");
+						}
+					}
+				}
+			} catch (Throwable e) {
+				if (toStop) {
+					XxlJobHelper.log("<br>----------- JobThread toStop, stopReason:" + stopReason);
+				}
+
+				// handle result
+				StringWriter stringWriter = new StringWriter();
+				e.printStackTrace(new PrintWriter(stringWriter));
+				String errorMsg = stringWriter.toString();
+
+				XxlJobHelper.handleFail(errorMsg);
+
+				XxlJobHelper.log("<br>----------- JobThread Exception:" + errorMsg + "<br>----------- xxl-job job execute end(error) -----------");
+			} finally {
+                if(triggerParam != null) {
+                    // callback handler info
+                    if (!toStop) {
+                        // commonm
+                        TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
+                        		triggerParam.getLogId(),
+								triggerParam.getLogDateTime(),
+								XxlJobContext.getXxlJobContext().getHandleCode(),
+								XxlJobContext.getXxlJobContext().getHandleMsg() )
+						);
+                    } else {
+                        // is killed
+                        TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
+                        		triggerParam.getLogId(),
+								triggerParam.getLogDateTime(),
+								XxlJobContext.HANDLE_CODE_FAIL,
+								stopReason + " [job running, killed]" )
+						);
+                    }
+                }
+            }
+        }
+
+		// callback trigger request in queue
+		while(triggerQueue !=null && triggerQueue.size()>0){
+			TriggerParam triggerParam = triggerQueue.poll();
+			if (triggerParam!=null) {
+				// is killed
+				TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
+						triggerParam.getLogId(),
+						triggerParam.getLogDateTime(),
+						XxlJobContext.HANDLE_CODE_FAIL,
+						stopReason + " [job not executed, in the job queue, killed.]")
+				);
+			}
+		}
+
+		// destroy
+		try {
+			handler.destroy();
+		} catch (Throwable e) {
+			logger.error(e.getMessage(), e);
+		}
+
+		logger.info(">>>>>>>>>>> xxl-job JobThread stoped, hashCode:{}", Thread.currentThread());
+	}
+}

+ 260 - 0
xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java

@@ -0,0 +1,260 @@
+package com.xxl.job.core.thread;
+
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.model.HandleCallbackParam;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.context.XxlJobContext;
+import com.xxl.job.core.context.XxlJobHelper;
+import com.xxl.job.core.enums.RegistryConfig;
+import com.xxl.job.core.executor.XxlJobExecutor;
+import com.xxl.job.core.log.XxlJobFileAppender;
+import com.xxl.job.core.util.FileUtil;
+import com.xxl.job.core.util.JdkSerializeTool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by xuxueli on 16/7/22.
+ */
+public class TriggerCallbackThread {
+    private static Logger logger = LoggerFactory.getLogger(TriggerCallbackThread.class);
+
+    private static TriggerCallbackThread instance = new TriggerCallbackThread();
+    public static TriggerCallbackThread getInstance(){
+        return instance;
+    }
+
+    /**
+     * job results callback queue
+     */
+    private LinkedBlockingQueue<HandleCallbackParam> callBackQueue = new LinkedBlockingQueue<HandleCallbackParam>();
+    public static void pushCallBack(HandleCallbackParam callback){
+        getInstance().callBackQueue.add(callback);
+        logger.debug(">>>>>>>>>>> xxl-job, push callback request, logId:{}", callback.getLogId());
+    }
+
+    /**
+     * callback thread
+     */
+    private Thread triggerCallbackThread;
+    private Thread triggerRetryCallbackThread;
+    private volatile boolean toStop = false;
+    public void start() {
+
+        // valid
+        if (XxlJobExecutor.getAdminBizList() == null) {
+            logger.warn(">>>>>>>>>>> xxl-job, executor callback config fail, adminAddresses is null.");
+            return;
+        }
+
+        // callback
+        triggerCallbackThread = new Thread(new Runnable() {
+
+            @Override
+            public void run() {
+
+                // normal callback
+                while(!toStop){
+                    try {
+                        HandleCallbackParam callback = getInstance().callBackQueue.take();
+                        if (callback != null) {
+
+                            // callback list param
+                            List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
+                            int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
+                            callbackParamList.add(callback);
+
+                            // callback, will retry if error
+                            if (callbackParamList!=null && callbackParamList.size()>0) {
+                                doCallback(callbackParamList);
+                            }
+                        }
+                    } catch (Exception e) {
+                        if (!toStop) {
+                            logger.error(e.getMessage(), e);
+                        }
+                    }
+                }
+
+                // last callback
+                try {
+                    List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
+                    int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
+                    if (callbackParamList!=null && callbackParamList.size()>0) {
+                        doCallback(callbackParamList);
+                    }
+                } catch (Exception e) {
+                    if (!toStop) {
+                        logger.error(e.getMessage(), e);
+                    }
+                }
+                logger.info(">>>>>>>>>>> xxl-job, executor callback thread destroy.");
+
+            }
+        });
+        triggerCallbackThread.setDaemon(true);
+        triggerCallbackThread.setName("xxl-job, executor TriggerCallbackThread");
+        triggerCallbackThread.start();
+
+
+        // retry
+        triggerRetryCallbackThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while(!toStop){
+                    try {
+                        retryFailCallbackFile();
+                    } catch (Exception e) {
+                        if (!toStop) {
+                            logger.error(e.getMessage(), e);
+                        }
+
+                    }
+                    try {
+                        TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
+                    } catch (InterruptedException e) {
+                        if (!toStop) {
+                            logger.error(e.getMessage(), e);
+                        }
+                    }
+                }
+                logger.info(">>>>>>>>>>> xxl-job, executor retry callback thread destroy.");
+            }
+        });
+        triggerRetryCallbackThread.setDaemon(true);
+        triggerRetryCallbackThread.start();
+
+    }
+    public void toStop(){
+        toStop = true;
+        // stop callback, interrupt and wait
+        if (triggerCallbackThread != null) {    // support empty admin address
+            triggerCallbackThread.interrupt();
+            try {
+                triggerCallbackThread.join();
+            } catch (InterruptedException e) {
+                logger.error(e.getMessage(), e);
+            }
+        }
+
+        // stop retry, interrupt and wait
+        if (triggerRetryCallbackThread != null) {
+            triggerRetryCallbackThread.interrupt();
+            try {
+                triggerRetryCallbackThread.join();
+            } catch (InterruptedException e) {
+                logger.error(e.getMessage(), e);
+            }
+        }
+
+    }
+
+    /**
+     * do callback, will retry if error
+     * @param callbackParamList
+     */
+    private void doCallback(List<HandleCallbackParam> callbackParamList){
+        boolean callbackRet = false;
+        // callback, will retry if error
+        for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
+            try {
+                ReturnT<String> callbackResult = adminBiz.callback(callbackParamList);
+                if (callbackResult!=null && ReturnT.SUCCESS_CODE == callbackResult.getCode()) {
+                    callbackLog(callbackParamList, "<br>----------- xxl-job job callback finish.");
+                    callbackRet = true;
+                    break;
+                } else {
+                    callbackLog(callbackParamList, "<br>----------- xxl-job job callback fail, callbackResult:" + callbackResult);
+                }
+            } catch (Exception e) {
+                callbackLog(callbackParamList, "<br>----------- xxl-job job callback error, errorMsg:" + e.getMessage());
+            }
+        }
+        if (!callbackRet) {
+            appendFailCallbackFile(callbackParamList);
+        }
+    }
+
+    /**
+     * callback log
+     */
+    private void callbackLog(List<HandleCallbackParam> callbackParamList, String logContent){
+        for (HandleCallbackParam callbackParam: callbackParamList) {
+            String logFileName = XxlJobFileAppender.makeLogFileName(new Date(callbackParam.getLogDateTim()), callbackParam.getLogId());
+            XxlJobContext.setXxlJobContext(new XxlJobContext(
+                    -1,
+                    null,
+                    logFileName,
+                    -1,
+                    -1));
+            XxlJobHelper.log(logContent);
+        }
+    }
+
+
+    // ---------------------- fail-callback file ----------------------
+
+    private static String failCallbackFilePath = XxlJobFileAppender.getLogPath().concat(File.separator).concat("callbacklog").concat(File.separator);
+    private static String failCallbackFileName = failCallbackFilePath.concat("xxl-job-callback-{x}").concat(".log");
+
+    private void appendFailCallbackFile(List<HandleCallbackParam> callbackParamList){
+        // valid
+        if (callbackParamList==null || callbackParamList.size()==0) {
+            return;
+        }
+
+        // append file
+        byte[] callbackParamList_bytes = JdkSerializeTool.serialize(callbackParamList);
+
+        File callbackLogFile = new File(failCallbackFileName.replace("{x}", String.valueOf(System.currentTimeMillis())));
+        if (callbackLogFile.exists()) {
+            for (int i = 0; i < 100; i++) {
+                callbackLogFile = new File(failCallbackFileName.replace("{x}", String.valueOf(System.currentTimeMillis()).concat("-").concat(String.valueOf(i)) ));
+                if (!callbackLogFile.exists()) {
+                    break;
+                }
+            }
+        }
+        FileUtil.writeFileContent(callbackLogFile, callbackParamList_bytes);
+    }
+
+    private void retryFailCallbackFile(){
+
+        // valid
+        File callbackLogPath = new File(failCallbackFilePath);
+        if (!callbackLogPath.exists()) {
+            return;
+        }
+        if (callbackLogPath.isFile()) {
+            callbackLogPath.delete();
+        }
+        if (!(callbackLogPath.isDirectory() && callbackLogPath.list()!=null && callbackLogPath.list().length>0)) {
+            return;
+        }
+
+        // load and clear file, retry
+        for (File callbaclLogFile: callbackLogPath.listFiles()) {
+            byte[] callbackParamList_bytes = FileUtil.readFileContent(callbaclLogFile);
+
+            // avoid empty file
+            if(callbackParamList_bytes == null || callbackParamList_bytes.length < 1){
+                callbaclLogFile.delete();
+                continue;
+            }
+
+            List<HandleCallbackParam> callbackParamList = (List<HandleCallbackParam>) JdkSerializeTool.deserialize(callbackParamList_bytes, List.class);
+
+            callbaclLogFile.delete();
+            doCallback(callbackParamList);
+        }
+
+    }
+
+}

+ 156 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/DateUtil.java

@@ -0,0 +1,156 @@
+package com.xxl.job.core.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * date util
+ *
+ * @author xuxueli 2018-08-19 01:24:11
+ */
+public class DateUtil {
+
+    // ---------------------- format parse ----------------------
+    private static Logger logger = LoggerFactory.getLogger(DateUtil.class);
+
+    private static final String DATE_FORMAT = "yyyy-MM-dd";
+    private static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    private static final ThreadLocal<Map<String, DateFormat>> dateFormatThreadLocal = new ThreadLocal<Map<String, DateFormat>>();
+    private static DateFormat getDateFormat(String pattern) {
+        if (pattern==null || pattern.trim().length()==0) {
+            throw new IllegalArgumentException("pattern cannot be empty.");
+        }
+
+        Map<String, DateFormat> dateFormatMap = dateFormatThreadLocal.get();
+        if(dateFormatMap!=null && dateFormatMap.containsKey(pattern)){
+            return dateFormatMap.get(pattern);
+        }
+
+        synchronized (dateFormatThreadLocal) {
+            if (dateFormatMap == null) {
+                dateFormatMap = new HashMap<String, DateFormat>();
+            }
+            dateFormatMap.put(pattern, new SimpleDateFormat(pattern));
+            dateFormatThreadLocal.set(dateFormatMap);
+        }
+
+        return dateFormatMap.get(pattern);
+    }
+
+    /**
+     * format datetime. like "yyyy-MM-dd"
+     *
+     * @param date
+     * @return
+     * @throws ParseException
+     */
+    public static String formatDate(Date date) {
+        return format(date, DATE_FORMAT);
+    }
+
+    /**
+     * format date. like "yyyy-MM-dd HH:mm:ss"
+     *
+     * @param date
+     * @return
+     * @throws ParseException
+     */
+    public static String formatDateTime(Date date) {
+        return format(date, DATETIME_FORMAT);
+    }
+
+    /**
+     * format date
+     *
+     * @param date
+     * @param patten
+     * @return
+     * @throws ParseException
+     */
+    public static String format(Date date, String patten) {
+        return getDateFormat(patten).format(date);
+    }
+
+    /**
+     * parse date string, like "yyyy-MM-dd HH:mm:s"
+     *
+     * @param dateString
+     * @return
+     * @throws ParseException
+     */
+    public static Date parseDate(String dateString){
+        return parse(dateString, DATE_FORMAT);
+    }
+
+    /**
+     * parse datetime string, like "yyyy-MM-dd HH:mm:ss"
+     *
+     * @param dateString
+     * @return
+     * @throws ParseException
+     */
+    public static Date parseDateTime(String dateString) {
+        return parse(dateString, DATETIME_FORMAT);
+    }
+
+    /**
+     * parse date
+     *
+     * @param dateString
+     * @param pattern
+     * @return
+     * @throws ParseException
+     */
+    public static Date parse(String dateString, String pattern) {
+        try {
+            Date date = getDateFormat(pattern).parse(dateString);
+            return date;
+        } catch (Exception e) {
+            logger.warn("parse date error, dateString = {}, pattern={}; errorMsg = {}", dateString, pattern, e.getMessage());
+            return null;
+        }
+    }
+
+
+    // ---------------------- add date ----------------------
+
+    public static Date addYears(final Date date, final int amount) {
+        return add(date, Calendar.YEAR, amount);
+    }
+
+    public static Date addMonths(final Date date, final int amount) {
+        return add(date, Calendar.MONTH, amount);
+    }
+
+    public static Date addDays(final Date date, final int amount) {
+        return add(date, Calendar.DAY_OF_MONTH, amount);
+    }
+
+    public static Date addHours(final Date date, final int amount) {
+        return add(date, Calendar.HOUR_OF_DAY, amount);
+    }
+
+    public static Date addMinutes(final Date date, final int amount) {
+        return add(date, Calendar.MINUTE, amount);
+    }
+
+    private static Date add(final Date date, final int calendarField, final int amount) {
+        if (date == null) {
+            return null;
+        }
+        final Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(calendarField, amount);
+        return c.getTime();
+    }
+
+}

+ 181 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/FileUtil.java

@@ -0,0 +1,181 @@
+package com.xxl.job.core.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * file tool
+ *
+ * @author xuxueli 2017-12-29 17:56:48
+ */
+public class FileUtil {
+    private static Logger logger = LoggerFactory.getLogger(FileUtil.class);
+
+
+    /**
+     * delete recursively
+     *
+     * @param root
+     * @return
+     */
+    public static boolean deleteRecursively(File root) {
+        if (root != null && root.exists()) {
+            if (root.isDirectory()) {
+                File[] children = root.listFiles();
+                if (children != null) {
+                    for (File child : children) {
+                        deleteRecursively(child);
+                    }
+                }
+            }
+            return root.delete();
+        }
+        return false;
+    }
+
+
+    public static void deleteFile(String fileName) {
+        // file
+        File file = new File(fileName);
+        if (file.exists()) {
+            file.delete();
+        }
+    }
+
+
+    public static void writeFileContent(File file, byte[] data) {
+
+        // file
+        if (!file.exists()) {
+            file.getParentFile().mkdirs();
+        }
+
+        // append file content
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(file);
+            fos.write(data);
+            fos.flush();
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+
+    }
+
+    public static byte[] readFileContent(File file) {
+        Long filelength = file.length();
+        byte[] filecontent = new byte[filelength.intValue()];
+
+        FileInputStream in = null;
+        try {
+            in = new FileInputStream(file);
+            in.read(filecontent);
+            in.close();
+
+            return filecontent;
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+            return null;
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+
+    /*public static void appendFileLine(String fileName, String content) {
+
+        // file
+        File file = new File(fileName);
+        if (!file.exists()) {
+            try {
+                file.createNewFile();
+            } catch (IOException e) {
+                logger.error(e.getMessage(), e);
+                return;
+            }
+        }
+
+        // content
+        if (content == null) {
+            content = "";
+        }
+        content += "\r\n";
+
+        // append file content
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(file, true);
+            fos.write(content.getBytes("utf-8"));
+            fos.flush();
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+
+    }
+
+    public static List<String> loadFileLines(String fileName){
+
+        List<String> result = new ArrayList<>();
+
+        // valid log file
+        File file = new File(fileName);
+        if (!file.exists()) {
+            return result;
+        }
+
+        // read file
+        StringBuffer logContentBuffer = new StringBuffer();
+        int toLineNum = 0;
+        LineNumberReader reader = null;
+        try {
+            //reader = new LineNumberReader(new FileReader(logFile));
+            reader = new LineNumberReader(new InputStreamReader(new FileInputStream(file), "utf-8"));
+            String line = null;
+            while ((line = reader.readLine())!=null) {
+                if (line!=null && line.trim().length()>0) {
+                    result.add(line);
+                }
+            }
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        }
+
+        return result;
+    }*/
+
+}

+ 88 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/GsonTool.java

@@ -0,0 +1,88 @@
+package com.xxl.job.core.util;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * @author xuxueli 2020-04-11 20:56:31
+ */
+public class GsonTool {
+
+    private static Gson gson = null;
+    static {
+            gson= new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
+    }
+
+    /**
+     * Object 转成 json
+     *
+     * @param src
+     * @return String
+     */
+    public static String toJson(Object src) {
+        return gson.toJson(src);
+    }
+
+    /**
+     * json 转成 特定的cls的Object
+     *
+     * @param json
+     * @param classOfT
+     * @return
+     */
+    public static <T> T fromJson(String json, Class<T> classOfT) {
+        return gson.fromJson(json, classOfT);
+    }
+
+    /**
+     * json 转成 特定的 rawClass<classOfT> 的Object
+     *
+     * @param json
+     * @param classOfT
+     * @param argClassOfT
+     * @return
+     */
+    public static <T> T fromJson(String json, Class<T> classOfT, Class argClassOfT) {
+        Type type = new ParameterizedType4ReturnT(classOfT, new Class[]{argClassOfT});
+        return gson.fromJson(json, type);
+    }
+    public static class ParameterizedType4ReturnT implements ParameterizedType {
+        private final Class raw;
+        private final Type[] args;
+        public ParameterizedType4ReturnT(Class raw, Type[] args) {
+            this.raw = raw;
+            this.args = args != null ? args : new Type[0];
+        }
+        @Override
+        public Type[] getActualTypeArguments() {
+            return args;
+        }
+        @Override
+        public Type getRawType() {
+            return raw;
+        }
+        @Override
+        public Type getOwnerType() {return null;}
+    }
+
+    /**
+     * json 转成 特定的cls的list
+     *
+     * @param json
+     * @param classOfT
+     * @return
+     */
+    public static <T> List<T> fromJsonList(String json, Class<T> classOfT) {
+        return gson.fromJson(
+                json,
+                new TypeToken<List<T>>() {
+                }.getType()
+        );
+    }
+
+}

+ 203 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/IpUtil.java

@@ -0,0 +1,203 @@
+package com.xxl.job.core.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import java.util.regex.Pattern;
+
+/**
+ * ip tool
+ *
+ * @author xuxueli 2016-5-22 11:38:05
+ */
+public class IpUtil {
+    private static final Logger logger = LoggerFactory.getLogger(IpUtil.class);
+
+    private static final String ANYHOST_VALUE = "0.0.0.0";
+    private static final String LOCALHOST_VALUE = "127.0.0.1";
+    private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
+
+
+
+    private static volatile InetAddress LOCAL_ADDRESS = null;
+
+    // ---------------------- valid ----------------------
+
+    private static InetAddress toValidAddress(InetAddress address) {
+        if (address instanceof Inet6Address) {
+            Inet6Address v6Address = (Inet6Address) address;
+            if (isPreferIPV6Address()) {
+                return normalizeV6Address(v6Address);
+            }
+        }
+        if (isValidV4Address(address)) {
+            return address;
+        }
+        return null;
+    }
+
+    private static boolean isPreferIPV6Address() {
+        return Boolean.getBoolean("java.net.preferIPv6Addresses");
+    }
+
+    /**
+     * valid Inet4Address
+     *
+     * @param address
+     * @return
+     */
+    private static boolean isValidV4Address(InetAddress address) {
+        if (address == null || address.isLoopbackAddress()) {
+            return false;
+        }
+        String name = address.getHostAddress();
+        boolean result = (name != null
+                && IP_PATTERN.matcher(name).matches()
+                && !ANYHOST_VALUE.equals(name)
+                && !LOCALHOST_VALUE.equals(name));
+        return result;
+    }
+
+
+    /**
+     * normalize the ipv6 Address, convert scope name to scope id.
+     * e.g.
+     * convert
+     * fe80:0:0:0:894:aeec:f37d:23e1%en0
+     * to
+     * fe80:0:0:0:894:aeec:f37d:23e1%5
+     * <p>
+     * The %5 after ipv6 address is called scope id.
+     * see java doc of {@link Inet6Address} for more details.
+     *
+     * @param address the input address
+     * @return the normalized address, with scope id converted to int
+     */
+    private static InetAddress normalizeV6Address(Inet6Address address) {
+        String addr = address.getHostAddress();
+        int i = addr.lastIndexOf('%');
+        if (i > 0) {
+            try {
+                return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId());
+            } catch (UnknownHostException e) {
+                // ignore
+                logger.debug("Unknown IPV6 address: ", e);
+            }
+        }
+        return address;
+    }
+
+    // ---------------------- find ip ----------------------
+
+
+    private static InetAddress getLocalAddress0() {
+        InetAddress localAddress = null;
+        try {
+            localAddress = InetAddress.getLocalHost();
+            InetAddress addressItem = toValidAddress(localAddress);
+            if (addressItem != null) {
+                return addressItem;
+            }
+        } catch (Throwable e) {
+            logger.error(e.getMessage(), e);
+        }
+
+        try {
+            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+            if (null == interfaces) {
+                return localAddress;
+            }
+            while (interfaces.hasMoreElements()) {
+                try {
+                    NetworkInterface network = interfaces.nextElement();
+                    if (network.isLoopback() || network.isVirtual() || !network.isUp()) {
+                        continue;
+                    }
+                    Enumeration<InetAddress> addresses = network.getInetAddresses();
+                    while (addresses.hasMoreElements()) {
+                        try {
+                            InetAddress addressItem = toValidAddress(addresses.nextElement());
+                            if (addressItem != null) {
+                                try {
+                                    if(addressItem.isReachable(100)){
+                                        return addressItem;
+                                    }
+                                } catch (IOException e) {
+                                    // ignore
+                                }
+                            }
+                        } catch (Throwable e) {
+                            logger.error(e.getMessage(), e);
+                        }
+                    }
+                } catch (Throwable e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+        } catch (Throwable e) {
+            logger.error(e.getMessage(), e);
+        }
+        return localAddress;
+    }
+
+
+    // ---------------------- tool ----------------------
+
+    /**
+     * Find first valid IP from local network card
+     *
+     * @return first valid local IP
+     */
+    public static InetAddress getLocalAddress() {
+        if (LOCAL_ADDRESS != null) {
+            return LOCAL_ADDRESS;
+        }
+        InetAddress localAddress = getLocalAddress0();
+        LOCAL_ADDRESS = localAddress;
+        return localAddress;
+    }
+
+    /**
+     * get ip address
+     *
+     * @return String
+     */
+    public static String getIp(){
+        return getLocalAddress().getHostAddress();
+    }
+
+    /**
+     * get ip:port
+     *
+     * @param port
+     * @return String
+     */
+    public static String getIpPort(int port){
+        String ip = getIp();
+        return getIpPort(ip, port);
+    }
+
+    public static String getIpPort(String ip, int port){
+        if (ip==null) {
+            return null;
+        }
+        return ip.concat(":").concat(String.valueOf(port));
+    }
+
+    public static Object[] parseIpPort(String address){
+        String[] array = address.split(":");
+
+        String host = array[0];
+        int port = Integer.parseInt(array[1]);
+
+        return new Object[]{host, port};
+    }
+
+
+}

+ 73 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/JdkSerializeTool.java

@@ -0,0 +1,73 @@
+package com.xxl.job.core.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+
+/**
+ * @author xuxueli 2020-04-12 0:14:00
+ */
+public class JdkSerializeTool {
+    private static Logger logger = LoggerFactory.getLogger(JdkSerializeTool.class);
+
+
+    // ------------------------ serialize and unserialize ------------------------
+
+    /**
+     * 将对象-->byte[] (由于jedis中不支持直接存储object所以转换成byte[]存入)
+     *
+     * @param object
+     * @return
+     */
+    public static byte[] serialize(Object object) {
+        ObjectOutputStream oos = null;
+        ByteArrayOutputStream baos = null;
+        try {
+            // 序列化
+            baos = new ByteArrayOutputStream();
+            oos = new ObjectOutputStream(baos);
+            oos.writeObject(object);
+            byte[] bytes = baos.toByteArray();
+            return bytes;
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            try {
+                oos.close();
+                baos.close();
+            } catch (IOException e) {
+                logger.error(e.getMessage(), e);
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * 将byte[] -->Object
+     *
+     * @param bytes
+     * @return
+     */
+    public static  <T> Object deserialize(byte[] bytes, Class<T> clazz) {
+        ByteArrayInputStream bais = null;
+        try {
+            // 反序列化
+            bais = new ByteArrayInputStream(bytes);
+            ObjectInputStream ois = new ObjectInputStream(bais);
+            return ois.readObject();
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            try {
+                bais.close();
+            } catch (IOException e) {
+                logger.error(e.getMessage(), e);
+            }
+        }
+        return null;
+    }
+
+
+}

+ 70 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/NetUtil.java

@@ -0,0 +1,70 @@
+package com.xxl.job.core.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+/**
+ * net util
+ *
+ * @author xuxueli 2017-11-29 17:00:25
+ */
+public class NetUtil {
+    private static Logger logger = LoggerFactory.getLogger(NetUtil.class);
+
+    /**
+     * find avaliable port
+     *
+     * @param defaultPort
+     * @return
+     */
+    public static int findAvailablePort(int defaultPort) {
+        int portTmp = defaultPort;
+        while (portTmp < 65535) {
+            if (!isPortUsed(portTmp)) {
+                return portTmp;
+            } else {
+                portTmp++;
+            }
+        }
+        portTmp = defaultPort--;
+        while (portTmp > 0) {
+            if (!isPortUsed(portTmp)) {
+                return portTmp;
+            } else {
+                portTmp--;
+            }
+        }
+        throw new RuntimeException("no available port.");
+    }
+
+    /**
+     * check port used
+     *
+     * @param port
+     * @return
+     */
+    public static boolean isPortUsed(int port) {
+        boolean used = false;
+        ServerSocket serverSocket = null;
+        try {
+            serverSocket = new ServerSocket(port);
+            used = false;
+        } catch (IOException e) {
+            logger.info(">>>>>>>>>>> xxl-job, port[{}] is in use.", port);
+            used = true;
+        } finally {
+            if (serverSocket != null) {
+                try {
+                    serverSocket.close();
+                } catch (IOException e) {
+                    logger.info("");
+                }
+            }
+        }
+        return used;
+    }
+
+}

+ 228 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/ScriptUtil.java

@@ -0,0 +1,228 @@
+package com.xxl.job.core.util;
+
+import com.xxl.job.core.context.XxlJobHelper;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *  1、内嵌编译器如"PythonInterpreter"无法引用扩展包,因此推荐使用java调用控制台进程方式"Runtime.getRuntime().exec()"来运行脚本(shell或python);
+ *  2、因为通过java调用控制台进程方式实现,需要保证目标机器PATH路径正确配置对应编译器;
+ *  3、暂时脚本执行日志只能在脚本执行结束后一次性获取,无法保证实时性;因此为确保日志实时性,可改为将脚本打印的日志存储在指定的日志文件上;
+ *  4、python 异常输出优先级高于标准输出,体现在Log文件中,因此推荐通过logging方式打日志保持和异常信息一致;否则用prinf日志顺序会错乱
+ *
+ * Created by xuxueli on 17/2/25.
+ */
+public class ScriptUtil {
+
+    /**
+     * make script file
+     *
+     * @param scriptFileName
+     * @param content
+     * @throws IOException
+     */
+    public static void markScriptFile(String scriptFileName, String content) throws IOException {
+        // make file,   filePath/gluesource/666-123456789.py
+        FileOutputStream fileOutputStream = null;
+        try {
+            fileOutputStream = new FileOutputStream(scriptFileName);
+            fileOutputStream.write(content.getBytes("UTF-8"));
+            fileOutputStream.close();
+        } catch (Exception e) {
+            throw e;
+        }finally{
+            if(fileOutputStream != null){
+                fileOutputStream.close();
+            }
+        }
+    }
+
+    /**
+     * 脚本执行,日志文件实时输出
+     *
+     * @param command
+     * @param scriptFile
+     * @param logFile
+     * @param params
+     * @return
+     * @throws IOException
+     */
+    public static int execToFile(String command, String scriptFile, String logFile, String... params) throws IOException {
+
+        FileOutputStream fileOutputStream = null;
+        Thread inputThread = null;
+        Thread errThread = null;
+        try {
+            // file
+            fileOutputStream = new FileOutputStream(logFile, true);
+
+            // command
+            List<String> cmdarray = new ArrayList<>();
+            cmdarray.add(command);
+            cmdarray.add(scriptFile);
+            if (params!=null && params.length>0) {
+                for (String param:params) {
+                    cmdarray.add(param);
+                }
+            }
+            String[] cmdarrayFinal = cmdarray.toArray(new String[cmdarray.size()]);
+
+            // process-exec
+            final Process process = Runtime.getRuntime().exec(cmdarrayFinal);
+
+            // log-thread
+            final FileOutputStream finalFileOutputStream = fileOutputStream;
+            inputThread = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        copy(process.getInputStream(), finalFileOutputStream, new byte[1024]);
+                    } catch (IOException e) {
+                        XxlJobHelper.log(e);
+                    }
+                }
+            });
+            errThread = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        copy(process.getErrorStream(), finalFileOutputStream, new byte[1024]);
+                    } catch (IOException e) {
+                        XxlJobHelper.log(e);
+                    }
+                }
+            });
+            inputThread.start();
+            errThread.start();
+
+            // process-wait
+            int exitValue = process.waitFor();      // exit code: 0=success, 1=error
+
+            // log-thread join
+            inputThread.join();
+            errThread.join();
+
+            return exitValue;
+        } catch (Exception e) {
+            XxlJobHelper.log(e);
+            return -1;
+        } finally {
+            if (fileOutputStream != null) {
+                try {
+                    fileOutputStream.close();
+                } catch (IOException e) {
+                    XxlJobHelper.log(e);
+                }
+
+            }
+            if (inputThread != null && inputThread.isAlive()) {
+                inputThread.interrupt();
+            }
+            if (errThread != null && errThread.isAlive()) {
+                errThread.interrupt();
+            }
+        }
+    }
+
+    /**
+     * 数据流Copy(Input自动关闭,Output不处理)
+     *
+     * @param inputStream
+     * @param outputStream
+     * @param buffer
+     * @return
+     * @throws IOException
+     */
+    private static long copy(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws IOException {
+        try {
+            long total = 0;
+            for (;;) {
+                int res = inputStream.read(buffer);
+                if (res == -1) {
+                    break;
+                }
+                if (res > 0) {
+                    total += res;
+                    if (outputStream != null) {
+                        outputStream.write(buffer, 0, res);
+                    }
+                }
+            }
+            outputStream.flush();
+            //out = null;
+            inputStream.close();
+            inputStream = null;
+            return total;
+        } finally {
+            if (inputStream != null) {
+                inputStream.close();
+            }
+        }
+    }
+
+    /**
+     * 脚本执行,日志文件实时输出
+     *
+     * 优点:支持将目标数据实时输出到指定日志文件中去
+     * 缺点:
+     *      标准输出和错误输出优先级固定,可能和脚本中顺序不一致
+     *      Java无法实时获取
+     *
+     *      <!-- commons-exec -->
+     * 		<dependency>
+     * 			<groupId>org.apache.commons</groupId>
+     * 			<artifactId>commons-exec</artifactId>
+     * 			<version>${commons-exec.version}</version>
+     * 		</dependency>
+     *
+     * @param command
+     * @param scriptFile
+     * @param logFile
+     * @param params
+     * @return
+     * @throws IOException
+     */
+    /*public static int execToFileB(String command, String scriptFile, String logFile, String... params) throws IOException {
+        // 标准输出:print (null if watchdog timeout)
+        // 错误输出:logging + 异常 (still exists if watchdog timeout)
+        // 标准输入
+
+        FileOutputStream fileOutputStream = null;   //
+        try {
+            fileOutputStream = new FileOutputStream(logFile, true);
+            PumpStreamHandler streamHandler = new PumpStreamHandler(fileOutputStream, fileOutputStream, null);
+
+            // command
+            CommandLine commandline = new CommandLine(command);
+            commandline.addArgument(scriptFile);
+            if (params!=null && params.length>0) {
+                commandline.addArguments(params);
+            }
+
+            // exec
+            DefaultExecutor exec = new DefaultExecutor();
+            exec.setExitValues(null);
+            exec.setStreamHandler(streamHandler);
+            int exitValue = exec.execute(commandline);  // exit code: 0=success, 1=error
+            return exitValue;
+        } catch (Exception e) {
+            XxlJobLogger.log(e);
+            return -1;
+        } finally {
+            if (fileOutputStream != null) {
+                try {
+                    fileOutputStream.close();
+                } catch (IOException e) {
+                    XxlJobLogger.log(e);
+                }
+
+            }
+        }
+    }*/
+
+}

+ 46 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/ShardingUtil.java

@@ -0,0 +1,46 @@
+//package com.xxl.job.core.util;
+//
+///**
+// * sharding vo
+// * @author xuxueli 2017-07-25 21:26:38
+// */
+//public class ShardingUtil {
+//
+//    private static InheritableThreadLocal<ShardingVO> contextHolder = new InheritableThreadLocal<ShardingVO>();
+//
+//    public static class ShardingVO {
+//
+//        private int index;  // sharding index
+//        private int total;  // sharding total
+//
+//        public ShardingVO(int index, int total) {
+//            this.index = index;
+//            this.total = total;
+//        }
+//
+//        public int getIndex() {
+//            return index;
+//        }
+//
+//        public void setIndex(int index) {
+//            this.index = index;
+//        }
+//
+//        public int getTotal() {
+//            return total;
+//        }
+//
+//        public void setTotal(int total) {
+//            this.total = total;
+//        }
+//    }
+//
+//    public static void setShardingVo(ShardingVO shardingVo){
+//        contextHolder.set(shardingVo);
+//    }
+//
+//    public static ShardingVO getShardingVo(){
+//        return contextHolder.get();
+//    }
+//
+//}

+ 24 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/ThrowableUtil.java

@@ -0,0 +1,24 @@
+package com.xxl.job.core.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * @author xuxueli 2018-10-20 20:07:26
+ */
+public class ThrowableUtil {
+
+    /**
+     * parse error to string
+     *
+     * @param e
+     * @return
+     */
+    public static String toString(Throwable e) {
+        StringWriter stringWriter = new StringWriter();
+        e.printStackTrace(new PrintWriter(stringWriter));
+        String errorMsg = stringWriter.toString();
+        return errorMsg;
+    }
+
+}

+ 159 - 0
xxl-job-core/src/main/java/com/xxl/job/core/util/XxlJobRemotingUtil.java

@@ -0,0 +1,159 @@
+package com.xxl.job.core.util;
+
+import com.xxl.job.core.biz.model.ReturnT;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.*;
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+/**
+ * @author xuxueli 2018-11-25 00:55:31
+ */
+public class XxlJobRemotingUtil {
+    private static Logger logger = LoggerFactory.getLogger(XxlJobRemotingUtil.class);
+    public static final String XXL_JOB_ACCESS_TOKEN = "XXL-JOB-ACCESS-TOKEN";
+
+
+    // trust-https start
+    private static void trustAllHosts(HttpsURLConnection connection) {
+        try {
+            SSLContext sc = SSLContext.getInstance("TLS");
+            sc.init(null, trustAllCerts, new java.security.SecureRandom());
+            SSLSocketFactory newFactory = sc.getSocketFactory();
+
+            connection.setSSLSocketFactory(newFactory);
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        }
+        connection.setHostnameVerifier(new HostnameVerifier() {
+            @Override
+            public boolean verify(String hostname, SSLSession session) {
+                return true;
+            }
+        });
+    }
+    private static final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+        @Override
+        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+            return new java.security.cert.X509Certificate[]{};
+        }
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+        }
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+        }
+    }};
+    // trust-https end
+
+
+    /**
+     * post
+     *
+     * @param url
+     * @param accessToken
+     * @param timeout
+     * @param requestObj
+     * @param returnTargClassOfT
+     * @return
+     */
+    public static ReturnT postBody(String url, String accessToken, int timeout, Object requestObj, Class returnTargClassOfT) {
+        HttpURLConnection connection = null;
+        BufferedReader bufferedReader = null;
+        try {
+            // connection
+            URL realUrl = new URL(url);
+            connection = (HttpURLConnection) realUrl.openConnection();
+
+            // trust-https
+            boolean useHttps = url.startsWith("https");
+            if (useHttps) {
+                HttpsURLConnection https = (HttpsURLConnection) connection;
+                trustAllHosts(https);
+            }
+
+            // connection setting
+            connection.setRequestMethod("POST");
+            connection.setDoOutput(true);
+            connection.setDoInput(true);
+            connection.setUseCaches(false);
+            connection.setReadTimeout(timeout * 1000);
+            connection.setConnectTimeout(3 * 1000);
+            connection.setRequestProperty("connection", "Keep-Alive");
+            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
+            connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
+
+            if(accessToken!=null && accessToken.trim().length()>0){
+                connection.setRequestProperty(XXL_JOB_ACCESS_TOKEN, accessToken);
+            }
+
+            // do connection
+            connection.connect();
+
+            // write requestBody
+            if (requestObj != null) {
+                String requestBody = GsonTool.toJson(requestObj);
+
+                DataOutputStream dataOutputStream = new DataOutputStream(connection.getOutputStream());
+                dataOutputStream.write(requestBody.getBytes("UTF-8"));
+                dataOutputStream.flush();
+                dataOutputStream.close();
+            }
+
+            /*byte[] requestBodyBytes = requestBody.getBytes("UTF-8");
+            connection.setRequestProperty("Content-Length", String.valueOf(requestBodyBytes.length));
+            OutputStream outwritestream = connection.getOutputStream();
+            outwritestream.write(requestBodyBytes);
+            outwritestream.flush();
+            outwritestream.close();*/
+
+            // valid StatusCode
+            int statusCode = connection.getResponseCode();
+            if (statusCode != 200) {
+                return new ReturnT<String>(ReturnT.FAIL_CODE, "xxl-job remoting fail, StatusCode("+ statusCode +") invalid. for url : " + url);
+            }
+
+            // result
+            bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
+            StringBuilder result = new StringBuilder();
+            String line;
+            while ((line = bufferedReader.readLine()) != null) {
+                result.append(line);
+            }
+            String resultJson = result.toString();
+
+            // parse returnT
+            try {
+                ReturnT returnT = GsonTool.fromJson(resultJson, ReturnT.class, returnTargClassOfT);
+                return returnT;
+            } catch (Exception e) {
+                logger.error("xxl-job remoting (url="+url+") response content invalid("+ resultJson +").", e);
+                return new ReturnT<String>(ReturnT.FAIL_CODE, "xxl-job remoting (url="+url+") response content invalid("+ resultJson +").");
+            }
+
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+            return new ReturnT<String>(ReturnT.FAIL_CODE, "xxl-job remoting error("+ e.getMessage() +"), for url : " + url);
+        } finally {
+            try {
+                if (bufferedReader != null) {
+                    bufferedReader.close();
+                }
+                if (connection != null) {
+                    connection.disconnect();
+                }
+            } catch (Exception e2) {
+                logger.error(e2.getMessage(), e2);
+            }
+        }
+    }
+
+}