wanghongzhi 3 týždňov pred
rodič
commit
a3b9396091
100 zmenil súbory, kde vykonal 10776 pridanie a 0 odobranie
  1. 191 0
      LICENSE
  2. 50 0
      easydo-common/pom.xml
  3. 10 0
      easydo-common/src/main/java/easydo/technology/AutoIncrementModel.java
  4. 30 0
      easydo-common/src/main/java/easydo/technology/annotation/AnonymousAccess.java
  5. 47 0
      easydo-common/src/main/java/easydo/technology/annotation/DataPermission.java
  6. 28 0
      easydo-common/src/main/java/easydo/technology/annotation/NotTableField.java
  7. 88 0
      easydo-common/src/main/java/easydo/technology/annotation/Query.java
  8. 28 0
      easydo-common/src/main/java/easydo/technology/annotation/TableKey.java
  9. 92 0
      easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousDeleteMapping.java
  10. 91 0
      easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousGetMapping.java
  11. 92 0
      easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousPatchMapping.java
  12. 92 0
      easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousPostMapping.java
  13. 92 0
      easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousPutMapping.java
  14. 27 0
      easydo-common/src/main/java/easydo/technology/base/CommonDto.java
  15. 38 0
      easydo-common/src/main/java/easydo/technology/base/CommonEntity.java
  16. 11 0
      easydo-common/src/main/java/easydo/technology/base/CommonMapper.java
  17. 7 0
      easydo-common/src/main/java/easydo/technology/base/CommonService.java
  18. 28 0
      easydo-common/src/main/java/easydo/technology/base/PageInfo.java
  19. 177 0
      easydo-common/src/main/java/easydo/technology/base/QueryHelpMybatisPlus.java
  20. 9 0
      easydo-common/src/main/java/easydo/technology/base/impl/CommonServiceImpl.java
  21. 47 0
      easydo-common/src/main/java/easydo/technology/config/AuditorConfig.java
  22. 39 0
      easydo-common/src/main/java/easydo/technology/config/ElPermissionConfig.java
  23. 37 0
      easydo-common/src/main/java/easydo/technology/config/MybatisPlusConfig.java
  24. 53 0
      easydo-common/src/main/java/easydo/technology/config/MybatisPlusFillHandler.java
  25. 219 0
      easydo-common/src/main/java/easydo/technology/config/RedisConfig.java
  26. 38 0
      easydo-common/src/main/java/easydo/technology/config/RsaProperties.java
  27. 98 0
      easydo-common/src/main/java/easydo/technology/exception/BadConfigurationException.java
  28. 40 0
      easydo-common/src/main/java/easydo/technology/exception/BadRequestException.java
  29. 34 0
      easydo-common/src/main/java/easydo/technology/exception/EntityExistException.java
  30. 34 0
      easydo-common/src/main/java/easydo/technology/exception/EntityNotFoundException.java
  31. 52 0
      easydo-common/src/main/java/easydo/technology/exception/handler/ApiError.java
  32. 115 0
      easydo-common/src/main/java/easydo/technology/exception/handler/GlobalExceptionHandler.java
  33. 56 0
      easydo-common/src/main/java/easydo/technology/utils/CacheKey.java
  34. 43 0
      easydo-common/src/main/java/easydo/technology/utils/CallBack.java
  35. 46 0
      easydo-common/src/main/java/easydo/technology/utils/ConvertUtil.java
  36. 47 0
      easydo-common/src/main/java/easydo/technology/utils/ElAdminConstant.java
  37. 100 0
      easydo-common/src/main/java/easydo/technology/utils/EncryptUtils.java
  38. 407 0
      easydo-common/src/main/java/easydo/technology/utils/HttpUtil.java
  39. 237 0
      easydo-common/src/main/java/easydo/technology/utils/LocalDateUtil.java
  40. 41 0
      easydo-common/src/main/java/easydo/technology/utils/MD5Util.java
  41. 114 0
      easydo-common/src/main/java/easydo/technology/utils/MapUtil.java
  42. 9 0
      easydo-common/src/main/java/easydo/technology/utils/MyUtil.java
  43. 87 0
      easydo-common/src/main/java/easydo/technology/utils/PageUtil.java
  44. 714 0
      easydo-common/src/main/java/easydo/technology/utils/RedisUtils.java
  45. 33 0
      easydo-common/src/main/java/easydo/technology/utils/RequestHolder.java
  46. 720 0
      easydo-common/src/main/java/easydo/technology/utils/ResultSetUtil.java
  47. 180 0
      easydo-common/src/main/java/easydo/technology/utils/RsaUtils.java
  48. 101 0
      easydo-common/src/main/java/easydo/technology/utils/SecurityUtils.java
  49. 145 0
      easydo-common/src/main/java/easydo/technology/utils/SpringContextHolder.java
  50. 449 0
      easydo-common/src/main/java/easydo/technology/utils/StringUtil.java
  51. 268 0
      easydo-common/src/main/java/easydo/technology/utils/StringUtils.java
  52. 37 0
      easydo-common/src/main/java/easydo/technology/utils/ThrowableUtil.java
  53. 69 0
      easydo-common/src/main/java/easydo/technology/utils/TranslatorUtil.java
  54. 46 0
      easydo-common/src/main/java/easydo/technology/utils/ValidationUtil.java
  55. 50 0
      easydo-common/src/main/java/easydo/technology/utils/enums/CodeBiEnum.java
  56. 46 0
      easydo-common/src/main/java/easydo/technology/utils/enums/CodeEnum.java
  57. 53 0
      easydo-common/src/main/java/easydo/technology/utils/enums/DataScopeEnum.java
  58. 43 0
      easydo-common/src/main/java/easydo/technology/utils/enums/MenuType.java
  59. 74 0
      easydo-common/src/main/java/easydo/technology/utils/enums/RequestMethodEnum.java
  60. 27 0
      easydo-core/pom.xml
  61. 996 0
      easydo-core/src/main/java/easydo/technology/components/JdbcClient.java
  62. 47 0
      easydo-core/src/main/java/easydo/technology/enums/RedisKeyEnum.java
  63. 22 0
      easydo-logging/pom.xml
  64. 42 0
      easydo-logging/src/main/java/easydo/technology/annotation/Log.java
  65. 45 0
      easydo-logging/src/main/java/easydo/technology/annotation/type/LogActionType.java
  66. 65 0
      easydo-logging/src/main/java/easydo/technology/domain/Log.java
  67. 15 0
      easydo-logging/src/main/java/easydo/technology/mapper/LogMapper.java
  68. 106 0
      easydo-logging/src/main/java/easydo/technology/rest/LogController.java
  69. 94 0
      easydo-logging/src/main/java/easydo/technology/service/LogService.java
  70. 47 0
      easydo-logging/src/main/java/easydo/technology/service/dto/LogErrorDTO.java
  71. 40 0
      easydo-logging/src/main/java/easydo/technology/service/dto/LogQueryCriteria.java
  72. 31 0
      easydo-logging/src/main/java/easydo/technology/service/dto/LogQueryParam.java
  73. 41 0
      easydo-logging/src/main/java/easydo/technology/service/dto/LogSmallDTO.java
  74. 181 0
      easydo-logging/src/main/java/easydo/technology/service/impl/LogServiceImpl.java
  75. 118 0
      easydo-mes/pom.xml
  76. 26 0
      easydo-mes/src/main/java/easydo/technology/MesApplication.java
  77. 33 0
      easydo-mes/src/main/java/easydo/technology/config/AsyncConfig.java
  78. 30 0
      easydo-mes/src/main/java/easydo/technology/config/MinioConfig.java
  79. 26 0
      easydo-mes/src/main/java/easydo/technology/config/WebMvcConfig.java
  80. 35 0
      easydo-mes/src/main/java/easydo/technology/config/exception/BizExceptionHandler.java
  81. 11 0
      easydo-mes/src/main/java/easydo/technology/controller/TestController.java
  82. 152 0
      easydo-mes/src/main/java/easydo/technology/enums/MESEnum.java
  83. 35 0
      easydo-mes/src/main/java/easydo/technology/exception/BizException.java
  84. 42 0
      easydo-mes/src/main/java/easydo/technology/listener/OpsTaskListener.java
  85. 43 0
      easydo-mes/src/main/java/easydo/technology/service/AsyncService.java
  86. 129 0
      easydo-mes/src/main/java/easydo/technology/service/MinioService.java
  87. 72 0
      easydo-mes/src/main/java/easydo/technology/util/DataListener.java
  88. 291 0
      easydo-mes/src/main/java/easydo/technology/util/DateUtils.java
  89. 112 0
      easydo-mes/src/main/java/easydo/technology/util/ExcelUtil.java
  90. 374 0
      easydo-mes/src/main/java/easydo/technology/util/HttpsUtil.java
  91. 786 0
      easydo-mes/src/main/java/easydo/technology/util/WeatherUtil.java
  92. 8 0
      easydo-mes/src/main/resources/banner.txt
  93. 104 0
      easydo-mes/src/main/resources/config/application-owntest.yml
  94. 108 0
      easydo-mes/src/main/resources/config/application-prod.yml
  95. 82 0
      easydo-mes/src/main/resources/config/application.yml
  96. BIN
      easydo-mes/src/main/resources/ip2region/ip2region.db
  97. 21 0
      easydo-mes/src/main/resources/log4j.properties
  98. 45 0
      easydo-mes/src/main/resources/logback.xml
  99. 45 0
      easydo-system/pom.xml
  100. 0 0
      easydo-system/src/main/java/easydo/technology/system/domain/Dept.java

+ 191 - 0
LICENSE

@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "{}" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright 2019-2020 Zheng Jie
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 50 - 0
easydo-common/pom.xml

@@ -0,0 +1,50 @@
+<?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">
+    <parent>
+        <groupId>easydo.technology</groupId>
+        <artifactId>easydo</artifactId>
+        <version>v20220507</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <properties>
+        <hutool.version>5.3.4</hutool.version>
+    </properties>
+
+    <artifactId>easydo-common</artifactId>
+    <name>公共模块</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+
+        <!--为HttpUtil使用-->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.10</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+            <version>1.7.2</version>
+        </dependency>
+        <dependency>
+            <groupId>eu.bitwalker</groupId>
+            <artifactId>UserAgentUtils</artifactId>
+            <version>1.21</version>
+        </dependency>
+        <!-- fastjson -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.72</version>
+        </dependency>
+    </dependencies>
+</project>

+ 10 - 0
easydo-common/src/main/java/easydo/technology/AutoIncrementModel.java

@@ -0,0 +1,10 @@
+package easydo.technology;
+
+import lombok.Data;
+
+@Data
+public class AutoIncrementModel {
+    private Boolean isAutoIncrement = false;
+    private String columnName;
+    private String fieldName;
+}

+ 30 - 0
easydo-common/src/main/java/easydo/technology/annotation/AnonymousAccess.java

@@ -0,0 +1,30 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @author jacky
+ *  用于标记匿名访问方法
+ */
+@Inherited
+@Documented
+@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnonymousAccess {
+
+}

+ 47 - 0
easydo-common/src/main/java/easydo/technology/annotation/DataPermission.java

@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ *   用于判断是否过滤数据权限
+ *   1、如果没有用到 @OneToOne 这种关联关系,只需要填写 fieldName [参考:DeptQueryCriteria.class]
+ *   2、如果用到了 @OneToOne ,fieldName 和 joinName 都需要填写,拿UserQueryCriteria.class举例:
+ *   应该是 @DataPermission(joinName = "dept", fieldName = "id")
+ * </p>
+ * @author Zheng Jie
+ * @website https://el-admin.vip
+ * @date 2020-05-07
+ **/
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DataPermission {
+
+    /**
+     * Entity 中的字段名称
+     */
+    String fieldName() default "";
+
+    /**
+     * Entity 中与部门关联的字段名称
+     */
+    String joinName() default "";
+}

+ 28 - 0
easydo-common/src/main/java/easydo/technology/annotation/NotTableField.java

@@ -0,0 +1,28 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NotTableField {
+
+}
+

+ 88 - 0
easydo-common/src/main/java/easydo/technology/annotation/Query.java

@@ -0,0 +1,88 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-6-4 13:52:30
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Query {
+
+    // Dong ZhaoYang 2017/8/7 基本对象的属性名
+    String propName() default "";
+    // Dong ZhaoYang 2017/8/7 查询方式
+    Type type() default Type.EQUAL;
+
+    /**
+     * 连接查询的属性名,如User类中的dept
+     */
+    String joinName() default "";
+
+    /**
+     * 默认左连接
+     */
+    Join join() default Join.LEFT;
+
+    /**
+     * 多字段模糊搜索,仅支持String类型字段,多个用逗号隔开, 如@Query(blurry = "email,username")
+     */
+    String blurry() default "";
+
+    enum Type {
+        // jie 2019/6/4 相等
+        EQUAL
+        // Dong ZhaoYang 2017/8/7 大于等于
+        , GREATER_THAN
+        // Dong ZhaoYang 2017/8/7 小于等于
+        , LESS_THAN
+        // Dong ZhaoYang 2017/8/7 中模糊查询
+        , INNER_LIKE
+        // Dong ZhaoYang 2017/8/7 左模糊查询
+        , LEFT_LIKE
+        // Dong ZhaoYang 2017/8/7 右模糊查询
+        , RIGHT_LIKE
+        // Dong ZhaoYang 2017/8/7 小于
+        , LESS_THAN_NQ
+        // jie 2019/6/4 包含
+        , IN
+        // 不等于
+        ,NOT_EQUAL
+        // between
+        ,BETWEEN
+        // 不为空
+        ,NOT_NULL
+        // 为空
+        ,IS_NULL
+    }
+
+    /**
+     * @author Zheng Jie
+     * 适用于简单连接查询,复杂的请自定义该注解,或者使用sql查询
+     */
+    enum Join {
+        /** jie 2019-6-4 13:18:30 */
+        LEFT, RIGHT, INNER
+    }
+
+}
+

+ 28 - 0
easydo-common/src/main/java/easydo/technology/annotation/TableKey.java

@@ -0,0 +1,28 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TableKey {
+
+}
+

+ 92 - 0
easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousDeleteMapping.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package easydo.technology.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import easydo.technology.annotation.AnonymousAccess;
+
+/**
+ * Annotation for mapping HTTP {@code DELETE} requests onto specific handler
+ * methods.
+ * 支持匿名访问  DeleteMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousPatchMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.DELETE)
+public @interface AnonymousDeleteMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 91 - 0
easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousGetMapping.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package easydo.technology.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import easydo.technology.annotation.AnonymousAccess;
+
+/**
+ * Annotation for mapping HTTP {@code GET} requests onto specific handler
+ * methods.
+ * <p>
+ * 支持匿名访问   GetMapping
+ *
+ * @author liaojinlong
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.GET)
+public @interface AnonymousGetMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     *
+     * @since 4.3.5
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 92 - 0
easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousPatchMapping.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package easydo.technology.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import easydo.technology.annotation.AnonymousAccess;
+
+/**
+ * Annotation for mapping HTTP {@code PATCH} requests onto specific handler
+ * methods.
+ * * 支持匿名访问    PatchMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousDeleteMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.PATCH)
+public @interface AnonymousPatchMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 92 - 0
easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousPostMapping.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package easydo.technology.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import easydo.technology.annotation.AnonymousAccess;
+
+/**
+ * Annotation for mapping HTTP {@code POST} requests onto specific handler
+ * methods.
+ * 支持匿名访问 PostMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousDeleteMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.POST)
+public @interface AnonymousPostMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 92 - 0
easydo-common/src/main/java/easydo/technology/annotation/rest/AnonymousPutMapping.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package easydo.technology.annotation.rest;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import easydo.technology.annotation.AnonymousAccess;
+
+/**
+ * Annotation for mapping HTTP {@code PUT} requests onto specific handler
+ * methods.
+ * * 支持匿名访问  PutMapping
+ *
+ * @author liaojinlong
+ * @see AnonymousGetMapping
+ * @see AnonymousPostMapping
+ * @see AnonymousPutMapping
+ * @see AnonymousDeleteMapping
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.PUT)
+public @interface AnonymousPutMapping {
+
+    /**
+     * Alias for {@link RequestMapping#name}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String name() default "";
+
+    /**
+     * Alias for {@link RequestMapping#value}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] value() default {};
+
+    /**
+     * Alias for {@link RequestMapping#path}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] path() default {};
+
+    /**
+     * Alias for {@link RequestMapping#params}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] params() default {};
+
+    /**
+     * Alias for {@link RequestMapping#headers}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] headers() default {};
+
+    /**
+     * Alias for {@link RequestMapping#consumes}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] consumes() default {};
+
+    /**
+     * Alias for {@link RequestMapping#produces}.
+     */
+    @AliasFor(annotation = RequestMapping.class)
+    String[] produces() default {};
+
+}

+ 27 - 0
easydo-common/src/main/java/easydo/technology/base/CommonDto.java

@@ -0,0 +1,27 @@
+package easydo.technology.base;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 浅析VO、DTO、DO、PO的概念、区别和用处
+ * https://www.cnblogs.com/qixuejia/p/4390086.html
+ *
+ * Created by jinjin on 2020-09-22.
+ */
+@Getter
+@Setter
+public abstract class CommonDto implements Serializable {
+
+    private String createBy;
+
+    private String updateBy;
+
+    private Date createTime;
+
+    private Date updateTime;
+
+}

+ 38 - 0
easydo-common/src/main/java/easydo/technology/base/CommonEntity.java

@@ -0,0 +1,38 @@
+package easydo.technology.base;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ *
+ * Created by jinjin on 2020-09-22.
+ */
+@Getter
+@Setter
+public abstract class CommonEntity implements Serializable {
+
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /* 分组校验 */
+    public @interface Create {
+    }
+
+    /* 分组校验 */
+    public @interface Update {
+    }
+}

+ 11 - 0
easydo-common/src/main/java/easydo/technology/base/CommonMapper.java

@@ -0,0 +1,11 @@
+package easydo.technology.base;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @author Jinjin
+ * @date 20200922
+ */
+public interface CommonMapper<T> extends BaseMapper<T> {
+
+}

+ 7 - 0
easydo-common/src/main/java/easydo/technology/base/CommonService.java

@@ -0,0 +1,7 @@
+package easydo.technology.base;
+
+/**
+ * Created by jinjin on 2020-09-22.
+ */
+public interface CommonService<T> {
+}

+ 28 - 0
easydo-common/src/main/java/easydo/technology/base/PageInfo.java

@@ -0,0 +1,28 @@
+package easydo.technology.base;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Created by jinjin on 2020-09-22.
+ */
+@Data
+@Builder
+@Accessors(chain = true)
+public class PageInfo<T> implements Serializable {
+    private long totalElements;
+
+    private List<T> content;
+
+    public PageInfo(long totalElements, List<T> content) {
+        this.totalElements = totalElements;
+        this.content = content;
+    }
+
+    public PageInfo() {
+    }
+}

+ 177 - 0
easydo-common/src/main/java/easydo/technology/base/QueryHelpMybatisPlus.java

@@ -0,0 +1,177 @@
+package easydo.technology.base;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import easydo.technology.annotation.DataPermission;
+import easydo.technology.annotation.Query;
+import easydo.technology.utils.SecurityUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-6-4 14:59:48
+ */
+@Slf4j
+public class QueryHelpMybatisPlus {
+
+    // TODO DataPermission
+    @SuppressWarnings("unchecked")
+    public static <R, Q> QueryWrapper<R> getPredicate(Q query) {
+        QueryWrapper<R> queryWrapper = new QueryWrapper<>();
+        if (query == null) {
+            return queryWrapper;
+        }
+
+        // 数据权限验证
+        DataPermission permission = query.getClass().getAnnotation(DataPermission.class);
+        if (permission != null) {
+            // 获取数据权限
+            List<Long> dataScopes = SecurityUtils.getCurrentUserDataScope();
+            if (dataScopes != null && !dataScopes.isEmpty()) {
+                if (StringUtils.isNotBlank(permission.joinName()) && StringUtils.isNotBlank(permission.fieldName())) {
+                    // Join join = root.join(permission.joinName(), JoinType.LEFT);
+                    // list.add(getExpression(permission.fieldName(),join, root).in(dataScopes));
+                    throw new RuntimeException("未实现");
+                } else if (StringUtils.isBlank(permission.joinName())
+                        && StringUtils.isNotBlank(permission.fieldName())) {
+                    // list.add(getExpression(permission.fieldName(),null, root).in(dataScopes));
+                    queryWrapper.in(permission.fieldName(), dataScopes);
+                }
+            }
+        }
+
+        try {
+            List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
+            for (Field field : fields) {
+                boolean accessible = field.isAccessible();
+                field.setAccessible(true);
+                Query q = field.getAnnotation(Query.class);
+                if (q != null) {
+                    String propName = q.propName();
+                    String blurry = q.blurry();
+                    String attributeName = propName == null || propName.trim().isEmpty() ? field.getName() : propName;
+                    attributeName = humpToUnderline(attributeName);
+                    // Class<?> fieldType = field.getType();
+                    Object val = field.get(query);
+                    if (val == null || "".equals(val)) {
+                        continue;
+                    }
+                    // 模糊多字段
+                    if (blurry != null && !blurry.trim().isEmpty()) {
+                        String[] blurrys = blurry.split(",");
+                        // queryWrapper.or();
+                        queryWrapper.and(wrapper -> {
+                            for (String blurry1 : blurrys) {
+                                String column = humpToUnderline(blurry1);
+                                // if(i!=0){
+                                wrapper.or();
+                                // }
+                                wrapper.like(column, val.toString());
+                            }
+                        });
+                        continue;
+                    }
+                    String finalAttributeName = attributeName;
+                    switch (q.type()) {
+                        case EQUAL:
+                            // queryWrapper.and(wrapper -> wrapper.eq(finalAttributeName, val));
+                            queryWrapper.eq(attributeName, val);
+                            break;
+                        case GREATER_THAN:
+                            queryWrapper.ge(finalAttributeName, val);
+                            break;
+                        case LESS_THAN:
+                            queryWrapper.le(finalAttributeName, val);
+                            break;
+                        case LESS_THAN_NQ:
+                            queryWrapper.lt(finalAttributeName, val);
+                            break;
+                        case INNER_LIKE:
+                            queryWrapper.like(finalAttributeName, val);
+                            break;
+                        case LEFT_LIKE:
+                            queryWrapper.likeLeft(finalAttributeName, val);
+                            break;
+                        case RIGHT_LIKE:
+                            queryWrapper.likeRight(finalAttributeName, val);
+                            break;
+                        case IN:
+                            Collection<Long> _longs = (Collection<Long>) val;
+                            if (_longs != null && !_longs.isEmpty()) {
+                                queryWrapper.in(finalAttributeName, _longs);
+                            }
+                            break;
+                        case NOT_EQUAL:
+                            queryWrapper.ne(finalAttributeName, val);
+                            break;
+                        case NOT_NULL:
+                            queryWrapper.isNotNull(finalAttributeName);
+                            break;
+                        case IS_NULL:
+                            queryWrapper.isNull(finalAttributeName);
+                            break;
+                        case BETWEEN:
+                            List<Object> between = new ArrayList<>((List<Object>) val);
+                            queryWrapper.between(finalAttributeName, between.get(0), between.get(1));
+                            break;
+                        default:
+                            break;
+                    }
+                }
+                field.setAccessible(accessible);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return queryWrapper;
+    }
+
+    public static List<Field> getAllFields(Class<?> clazz, List<Field> fields) {
+        if (clazz != null) {
+            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+            getAllFields(clazz.getSuperclass(), fields);
+        }
+        return fields;
+    }
+
+    /***
+     * 驼峰命名转为下划线命名
+     *
+     * @param para
+     *             驼峰命名的字符串
+     */
+
+    public static String humpToUnderline(String para) {
+        StringBuilder sb = new StringBuilder(para);
+        int temp = 0;
+        if (!para.contains("_")) {
+            for (int i = 0; i < para.length(); i++) {
+                if (Character.isUpperCase(para.charAt(i))) {
+                    sb.insert(i + temp, "_");
+                    temp += 1;
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+    // public static void main(String[] args) {
+    // QueryWrapper<Paging> query = new QueryWrapper<Paging>();
+    // //query.or();
+    // query.or(wrapper -> wrapper.eq("store_id", 1).or().eq("store_id", 2));
+    // //query.like("a",1);
+    // //query.or();
+    // //query.like("b",2);
+    // //query.and(wrapper->wrapper.eq("c",1));
+    // query.eq("1", 1);
+    //
+    // System.out.println(query.getSqlSegment());
+    // }
+}

+ 9 - 0
easydo-common/src/main/java/easydo/technology/base/impl/CommonServiceImpl.java

@@ -0,0 +1,9 @@
+package easydo.technology.base.impl;
+
+import easydo.technology.base.CommonService;
+
+/**
+ * Created by jinjin on 2020-09-22.
+ */
+public abstract class CommonServiceImpl<T> implements CommonService<T> {
+}

+ 47 - 0
easydo-common/src/main/java/easydo/technology/config/AuditorConfig.java

@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.config;
+
+import java.util.Optional;
+
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.stereotype.Component;
+
+import easydo.technology.utils.SecurityUtils;
+
+/**
+ * @description  : 设置审计
+ * @author  : Dong ZhaoYang
+ * @date : 2019/10/28
+ */
+@Component("auditorAware")
+public class AuditorConfig implements AuditorAware<String> {
+
+    /**
+     * 返回操作员标志信息
+     *
+     * @return /
+     */
+    @Override
+    public Optional<String> getCurrentAuditor() {
+        try {
+            // 这里应根据实际业务情况获取具体信息
+            return Optional.of(SecurityUtils.getCurrentUsername());
+        }catch (Exception ignored){}
+        // 用户定时任务,或者无Token调用的情况
+        return Optional.of("System");
+    }
+}

+ 39 - 0
easydo-common/src/main/java/easydo/technology/config/ElPermissionConfig.java

@@ -0,0 +1,39 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.config;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.stereotype.Service;
+
+import easydo.technology.utils.SecurityUtils;
+
+/**
+ * @author Zheng Jie
+ */
+@Service(value = "el")
+public class ElPermissionConfig {
+
+    public Boolean check(String ...permissions){
+        // 获取当前用户的所有权限
+        List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
+        // 判断当前用户的所有权限是否包含接口上定义的权限
+        return elPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(elPermissions::contains);
+    }
+}

+ 37 - 0
easydo-common/src/main/java/easydo/technology/config/MybatisPlusConfig.java

@@ -0,0 +1,37 @@
+package easydo.technology.config;
+
+import com.baomidou.mybatisplus.core.config.GlobalConfig;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 配置文件
+ * Created by jinjin on 2020-09-21.
+ */
+@Configuration
+@MapperScan(basePackages ={"easydo.technology.**.mapper"})
+public class MybatisPlusConfig {
+    @Bean
+    public MybatisPlusInterceptor paginationInterceptor() {
+        MybatisPlusInterceptor paginationInterceptor = new MybatisPlusInterceptor();
+        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
+        // paginationInterceptor.setOverflow(false);
+        // 设置最大单页限制数量,默认 500 条,-1 不受限制
+        // paginationInterceptor.setLimit(500);
+        // 开启 count 的 join 优化,只针对部分 left join
+        //paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
+        PaginationInnerInterceptor page = new PaginationInnerInterceptor();
+        paginationInterceptor.addInnerInterceptor(page);
+        return paginationInterceptor;
+    }
+
+    @Bean
+    public GlobalConfig globalConfig() {
+        GlobalConfig globalConfig = new GlobalConfig();
+        globalConfig.setMetaObjectHandler(new MybatisPlusFillHandler());
+        return globalConfig;
+    }
+}

+ 53 - 0
easydo-common/src/main/java/easydo/technology/config/MybatisPlusFillHandler.java

@@ -0,0 +1,53 @@
+package easydo.technology.config;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.context.annotation.Configuration;
+
+import easydo.technology.utils.SecurityUtils;
+
+/**
+ * Created by jinjin on 2020-09-21.
+ */
+@Configuration
+public class MybatisPlusFillHandler implements MetaObjectHandler{
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        Date currentTime = new Date();
+        if (metaObject.hasSetter("createTime")) {
+            setFieldValByName("createTime", currentTime, metaObject);
+        }
+        if (metaObject.hasSetter("createBy")) {
+            setFieldValByName("createBy", getUsername(), metaObject);
+        }
+        if (metaObject.hasSetter("updateTime")) {
+            setFieldValByName("updateTime", currentTime, metaObject);
+        }
+        if (metaObject.hasSetter("updateBy")) {
+            setFieldValByName("updateBy", getUsername(), metaObject);
+        }
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        Date currentTime = new Date();
+        if (metaObject.hasSetter("updateTime")) {
+            setFieldValByName("updateTime", currentTime, metaObject);
+        }
+        if (metaObject.hasSetter("updateBy")) {
+            setFieldValByName("updateBy", getUsername(), metaObject);
+        }
+    }
+
+    private String getUsername() {
+        try {
+            return SecurityUtils.getCurrentUsername();
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+}

+ 219 - 0
easydo-common/src/main/java/easydo/technology/config/RedisConfig.java

@@ -0,0 +1,219 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.config;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+
+import easydo.technology.utils.StringUtil;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.Cache;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.CacheErrorHandler;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+import easydo.technology.utils.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@Slf4j
+@Configuration
+@EnableCaching
+@ConditionalOnClass(RedisOperations.class)
+@EnableConfigurationProperties(RedisProperties.class)
+public class RedisConfig extends CachingConfigurerSupport {
+
+    /**
+     * 设置 redis 数据默认过期时间,默认2小时
+     * 设置@cacheable 序列化方式
+     */
+    @Bean
+    public RedisCacheConfiguration redisCacheConfiguration() {
+        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
+        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
+        configuration = configuration
+                .serializeValuesWith(
+                        RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
+                .entryTtl(Duration.ofHours(2));
+        return configuration;
+    }
+
+    @SuppressWarnings("all")
+    @Bean(name = "redisTemplate")
+    @ConditionalOnMissingBean(name = "redisTemplate")
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        // 序列化
+        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
+        // value值的序列化采用fastJsonRedisSerializer
+        template.setValueSerializer(fastJsonRedisSerializer);
+        template.setHashValueSerializer(fastJsonRedisSerializer);
+        // 全局开启AutoType,这里方便开发,使用全局的方式
+        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
+        // 建议使用这种方式,小范围指定白名单
+        // ParserConfig.getGlobalInstance().addAccept("com.shanshoufu.domain");
+        // key的序列化采用StringRedisSerializer
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setConnectionFactory(redisConnectionFactory);
+        return template;
+    }
+
+    /**
+     * 自定义缓存key生成策略,默认将使用该策略
+     */
+    @Bean
+    @Override
+    public KeyGenerator keyGenerator() {
+        return (target, method, params) -> {
+            Map<String, Object> container = new HashMap<>(3);
+            Class<?> targetClassClass = target.getClass();
+            // 类地址
+            container.put("class", targetClassClass.toGenericString());
+            // 方法名称
+            container.put("methodName", method.getName());
+            // 包名称
+            container.put("package", targetClassClass.getPackage());
+            // 参数列表
+            for (int i = 0; i < params.length; i++) {
+                container.put(String.valueOf(i), params[i]);
+            }
+            // 转为JSON字符串
+            String jsonString = JSON.toJSONString(container);
+            // 做SHA256 Hash计算,得到一个SHA256摘要作为Key
+            return DigestUtils.sha256Hex(jsonString);
+        };
+    }
+
+    @Bean
+    @Override
+    public CacheErrorHandler errorHandler() {
+        // 异常处理,当Redis发生异常时,打印日志,但是程序正常走
+        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
+        return new CacheErrorHandler() {
+            @Override
+            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
+                log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
+            }
+
+            @Override
+            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
+                log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
+            }
+
+            @Override
+            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
+                log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
+            }
+
+            @Override
+            public void handleCacheClearError(RuntimeException e, Cache cache) {
+                log.error("Redis occur handleCacheClearError:", e);
+            }
+        };
+    }
+
+}
+
+/**
+ * Value 序列化
+ *
+ * @author /
+ * @param <T>
+ */
+class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
+
+    private final Class<T> clazz;
+
+    FastJsonRedisSerializer(Class<T> clazz) {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) {
+        if (t == null) {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) {
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+        String str = new String(bytes, StandardCharsets.UTF_8);
+        return JSON.parseObject(str, clazz);
+    }
+
+}
+
+/**
+ * 重写序列化器
+ *
+ * @author /
+ */
+class StringRedisSerializer implements RedisSerializer<Object> {
+
+    private final Charset charset;
+
+    StringRedisSerializer() {
+        this(StandardCharsets.UTF_8);
+    }
+
+    private StringRedisSerializer(Charset charset) {
+        this.charset = charset;
+    }
+
+    @Override
+    public String deserialize(byte[] bytes) {
+        return (bytes == null ? null : new String(bytes, charset));
+    }
+
+    @Override
+    public byte[] serialize(Object object) {
+        String string = JSON.toJSONString(object);
+        if (StringUtil.isEmpty(string)) {
+            return null;
+        }
+        string = string.replace("\"", "");
+        return string.getBytes(charset);
+    }
+}

+ 38 - 0
easydo-common/src/main/java/easydo/technology/config/RsaProperties.java

@@ -0,0 +1,38 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.config;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Zheng Jie
+ * @website https://el-admin.vip
+ * @description
+ * @date 2020-05-18
+ **/
+@Data
+@Component
+public class RsaProperties {
+
+    public static String privateKey;
+
+    @Value("${rsa.private_key}")
+    public void setPrivateKey(String privateKey) {
+        RsaProperties.privateKey = privateKey;
+    }
+}

+ 98 - 0
easydo-common/src/main/java/easydo/technology/exception/BadConfigurationException.java

@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package easydo.technology.exception;
+
+/**
+ * 统一关于错误配置信息 异常
+ *
+ * @author: liaojinlong
+ * @date: 2020/6/10 18:06
+ */
+public class BadConfigurationException extends RuntimeException {
+    /**
+     * Constructs a new runtime exception with {@code null} as its
+     * detail message.  The cause is not initialized, and may subsequently be
+     * initialized by a call to {@link #initCause}.
+     */
+    public BadConfigurationException() {
+        super();
+    }
+
+    /**
+     * Constructs a new runtime exception with the specified detail message.
+     * The cause is not initialized, and may subsequently be initialized by a
+     * call to {@link #initCause}.
+     *
+     * @param message the detail message. The detail message is saved for
+     *                later retrieval by the {@link #getMessage()} method.
+     */
+    public BadConfigurationException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new runtime exception with the specified detail message and
+     * cause.  <p>Note that the detail message associated with
+     * {@code cause} is <i>not</i> automatically incorporated in
+     * this runtime exception's detail message.
+     *
+     * @param message the detail message (which is saved for later retrieval
+     *                by the {@link #getMessage()} method).
+     * @param cause   the cause (which is saved for later retrieval by the
+     *                {@link #getCause()} method).  (A {@code null} value is
+     *                permitted, and indicates that the cause is nonexistent or
+     *                unknown.)
+     * @since 1.4
+     */
+    public BadConfigurationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new runtime exception with the specified cause and a
+     * detail message of {@code (cause==null ? null : cause.toString())}
+     * (which typically contains the class and detail message of
+     * {@code cause}).  This constructor is useful for runtime exceptions
+     * that are little more than wrappers for other throwables.
+     *
+     * @param cause the cause (which is saved for later retrieval by the
+     *              {@link #getCause()} method).  (A {@code null} value is
+     *              permitted, and indicates that the cause is nonexistent or
+     *              unknown.)
+     * @since 1.4
+     */
+    public BadConfigurationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a new runtime exception with the specified detail
+     * message, cause, suppression enabled or disabled, and writable
+     * stack trace enabled or disabled.
+     *
+     * @param message            the detail message.
+     * @param cause              the cause.  (A {@code null} value is permitted,
+     *                           and indicates that the cause is nonexistent or unknown.)
+     * @param enableSuppression  whether or not suppression is enabled
+     *                           or disabled
+     * @param writableStackTrace whether or not the stack trace should
+     *                           be writable
+     * @since 1.7
+     */
+    protected BadConfigurationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 40 - 0
easydo-common/src/main/java/easydo/technology/exception/BadRequestException.java

@@ -0,0 +1,40 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ * 统一异常处理
+ */
+@Getter
+public class BadRequestException extends RuntimeException{
+
+    private Integer status = BAD_REQUEST.value();
+
+    public BadRequestException(String msg){
+        super(msg);
+    }
+
+    public BadRequestException(HttpStatus status,String msg){
+        super(msg);
+        this.status = status.value();
+    }
+}

+ 34 - 0
easydo-common/src/main/java/easydo/technology/exception/EntityExistException.java

@@ -0,0 +1,34 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public class EntityExistException extends RuntimeException {
+
+    public EntityExistException(Class<?> clazz, String field, String val) {
+        super(EntityExistException.generateMessage(clazz.getSimpleName(), field, val));
+    }
+
+    private static String generateMessage(String entity, String field, String val) {
+        return StringUtils.capitalize(entity)
+                + " with " + field + " " + val + " existed";
+    }
+}

+ 34 - 0
easydo-common/src/main/java/easydo/technology/exception/EntityNotFoundException.java

@@ -0,0 +1,34 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.exception;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public class EntityNotFoundException extends RuntimeException {
+
+    public EntityNotFoundException(Class<?> clazz, String field, String val) {
+        super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), field, val));
+    }
+
+    private static String generateMessage(String entity, String field, String val) {
+        return StringUtils.capitalize(entity)
+                + " with " + field + " " + val + " does not exist";
+    }
+}

+ 52 - 0
easydo-common/src/main/java/easydo/technology/exception/handler/ApiError.java

@@ -0,0 +1,52 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.exception.handler;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+@Data
+class ApiError {
+
+    private Integer status = 400;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime timestamp;
+    private String message;
+
+    private ApiError() {
+        timestamp = LocalDateTime.now();
+    }
+
+    public static ApiError error(String message){   
+        ApiError apiError = new ApiError();
+        apiError.setMessage(message);
+        return apiError;
+    }
+
+    public static ApiError error(Integer status, String message){
+        ApiError apiError = new ApiError();
+        apiError.setStatus(status);
+        apiError.setMessage(message);
+        return apiError;
+    }
+}
+
+

+ 115 - 0
easydo-common/src/main/java/easydo/technology/exception/handler/GlobalExceptionHandler.java

@@ -0,0 +1,115 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.exception.handler;
+
+import static org.springframework.http.HttpStatus.NOT_FOUND;
+
+import java.util.Objects;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import easydo.technology.exception.BadRequestException;
+import easydo.technology.exception.EntityExistException;
+import easydo.technology.exception.EntityNotFoundException;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 处理所有不可知的异常
+     */
+    @ExceptionHandler(Throwable.class)
+    public ResponseEntity<ApiError> handleException(Throwable e) {
+        // 打印堆栈信息
+        // log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ApiError.error(e.getMessage()));
+    }
+
+    /**
+     * BadCredentialsException
+     */
+    @ExceptionHandler(BadCredentialsException.class)
+    public ResponseEntity<ApiError> badCredentialsException(BadCredentialsException e) {
+        // 打印堆栈信息
+        String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage();
+        log.error(message);
+        return buildResponseEntity(ApiError.error(message));
+    }
+
+    /**
+     * 处理自定义异常
+     */
+    @ExceptionHandler(value = BadRequestException.class)
+    public ResponseEntity<ApiError> badRequestException(BadRequestException e) {
+        // 打印堆栈信息
+        // log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ApiError.error(e.getStatus(), e.getMessage()));
+    }
+
+    /**
+     * 处理 EntityExist
+     */
+    @ExceptionHandler(value = EntityExistException.class)
+    public ResponseEntity<ApiError> entityExistException(EntityExistException e) {
+        // 打印堆栈信息
+        // log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ApiError.error(e.getMessage()));
+    }
+
+    /**
+     * 处理 EntityNotFound
+     */
+    @ExceptionHandler(value = EntityNotFoundException.class)
+    public ResponseEntity<ApiError> entityNotFoundException(EntityNotFoundException e) {
+        // 打印堆栈信息
+        // log.error(ThrowableUtil.getStackTrace(e));
+        return buildResponseEntity(ApiError.error(NOT_FOUND.value(), e.getMessage()));
+    }
+
+    /**
+     * 处理所有接口数据验证异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        // 打印堆栈信息
+        // log.error(ThrowableUtil.getStackTrace(e));
+        String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");
+        String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
+        String msg = "不能为空";
+        if (msg.equals(message)) {
+            message = str[1] + ":" + message;
+        }
+        return buildResponseEntity(ApiError.error(message));
+    }
+
+    /**
+     * 统一返回
+     */
+    private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {
+        return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus()));
+    }
+}

+ 56 - 0
easydo-common/src/main/java/easydo/technology/utils/CacheKey.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package easydo.technology.utils;
+
+/**
+ * @author: liaojinlong
+ * @date: 2020/6/11 15:49
+ * @apiNote: 关于缓存的Key集合
+ */
+public interface CacheKey {
+
+    /**
+     * 内置 用户、岗位、应用、菜单、角色 相关key
+     */
+    String USER_MODIFY_TIME_KEY = "user:modify:time:key:";
+    String APP_MODIFY_TIME_KEY = "app:modify:time:key:";
+    String JOB_MODIFY_TIME_KEY = "job:modify:time:key:";
+    String MENU_MODIFY_TIME_KEY = "menu:modify:time:key:";
+    String ROLE_MODIFY_TIME_KEY = "role:modify:time:key:";
+    String DEPT_MODIFY_TIME_KEY = "dept:modify:time:key:";
+
+    /**
+     * 用户
+     */
+    String USER_ID = "user::id:";
+    String USER_NAME = "user::username:";
+    /**
+     * 数据
+     */
+    String DATE_USER = "data::user:";
+    /**
+     * 菜单
+     */
+    String MENU_USER = "menu::user:";
+    /**
+     * 角色授权
+     */
+    String ROLE_AUTH = "role::auth:";
+    /**
+     * 角色信息
+     */
+    String ROLE_ID = "role::id:";
+}

+ 43 - 0
easydo-common/src/main/java/easydo/technology/utils/CallBack.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019-2020  the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package easydo.technology.utils;
+
+/**
+ * @author: liaojinlong
+ * @date: 2020/6/9 17:02
+ * @since: 1.0
+ * @see {@link SpringContextHolder}
+ * 针对某些初始化方法,在SpringContextHolder 初始化前时,<br>
+ * 可提交一个 提交回调任务。<br>
+ * 在SpringContextHolder 初始化后,进行回调使用
+ */
+
+public interface CallBack {
+    /**
+     * 回调执行方法
+     */
+    void executor();
+
+    /**
+     * 本回调任务名称
+     * @return /
+     */
+    default String getCallBackName() {
+        return Thread.currentThread().getId() + ":" + this.getClass().getName();
+    }
+}
+

+ 46 - 0
easydo-common/src/main/java/easydo/technology/utils/ConvertUtil.java

@@ -0,0 +1,46 @@
+package easydo.technology.utils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+import easydo.technology.base.PageInfo;
+
+/**
+ * 转换
+ * Created by jinjin on 2020-09-22.
+ */
+public class ConvertUtil {
+    public static <T, S> T convert(final S s, Class<T> clz) {
+        return s == null ? null : JSONObject.parseObject(JSONObject.toJSONString(s), clz);
+    }
+
+    public static <T, S> List<T> convertList(List<S> s, Class<T> clz) {
+        List<T> res = new ArrayList<>();
+        for (S si : s)
+            res.add(JSONObject.parseObject(JSONObject.toJSONString(si), clz));
+        return res;
+    }
+
+    public static <T, S> Set<T> convertSet(Set<S> s, Class<T> clz) {
+        Set<T> res = new HashSet<>();
+        for (S si : s)
+            res.add(JSONObject.parseObject(JSONObject.toJSONString(si), clz));
+        return res;
+    }
+
+    public static <T, S> PageInfo<T> convertPage(IPage<S> page, Class<T> clz) {
+        if (page == null) {
+            return null;
+        }
+        PageInfo<T> pageInfo = new PageInfo<>();
+        pageInfo.setTotalElements(page.getTotal());
+        pageInfo.setContent(convertList(page.getRecords(), clz));
+        return pageInfo;
+    }
+
+}

+ 47 - 0
easydo-common/src/main/java/easydo/technology/utils/ElAdminConstant.java

@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+/**
+ * 常用静态常量
+ *
+ * @author Zheng Jie
+ * @date 2018-12-26
+ */
+public class ElAdminConstant {
+
+    /**
+     * 用于IP定位转换
+     */
+    public static final String REGION = "内网IP|内网IP";
+    /**
+     * win 系统
+     */
+    public static final String WIN = "win";
+
+    /**
+     * mac 系统
+     */
+    public static final String MAC = "mac";
+
+    /**
+     * 常用接口
+     */
+    public static class Url {
+        // IP归属地查询
+        public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true";
+    }
+}

+ 100 - 0
easydo-common/src/main/java/easydo/technology/utils/EncryptUtils.java

@@ -0,0 +1,100 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 加密
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+
+public class EncryptUtils {
+
+    private static final String STR_PARAM = "Passw0rd";
+
+    private static Cipher cipher;
+
+    private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));
+
+    private static DESKeySpec getDesKeySpec(String source) throws Exception {
+        if (source == null || source.length() == 0){
+            return null;
+        }
+        cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        String strKey = "Passw0rd";
+        return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * 对称加密
+     */
+    public static String desEncrypt(String source) throws Exception {
+        DESKeySpec desKeySpec = getDesKeySpec(source);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
+        return byte2hex(
+                cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();
+    }
+
+    /**
+     * 对称解密
+     */
+    public static String desDecrypt(String source) throws Exception {
+        byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8));
+        DESKeySpec desKeySpec = getDesKeySpec(source);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, IV);
+        byte[] retByte = cipher.doFinal(src);
+        return new String(retByte);
+    }
+
+    private static String byte2hex(byte[] inStr) {
+        String stmp;
+        StringBuilder out = new StringBuilder(inStr.length * 2);
+        for (byte b : inStr) {
+            stmp = Integer.toHexString(b & 0xFF);
+            if (stmp.length() == 1) {
+                // 如果是0至F的单位字符串,则添加0
+                out.append("0").append(stmp);
+            } else {
+                out.append(stmp);
+            }
+        }
+        return out.toString();
+    }
+
+    private static byte[] hex2byte(byte[] b) {
+        int size = 2;
+        if ((b.length % size) != 0){
+            throw new IllegalArgumentException("长度不是偶数");
+        }
+        byte[] b2 = new byte[b.length / 2];
+        for (int n = 0; n < b.length; n += size) {
+            String item = new String(b, n, 2);
+            b2[n / 2] = (byte) Integer.parseInt(item, 16);
+        }
+        return b2;
+    }
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 407 - 0
easydo-common/src/main/java/easydo/technology/utils/HttpUtil.java


+ 237 - 0
easydo-common/src/main/java/easydo/technology/utils/LocalDateUtil.java

@@ -0,0 +1,237 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package easydo.technology.utils;
+
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAdjusters;
+import java.util.Date;
+
+/**
+ * @author: liaojinlong
+ * @date: 2020/6/11 16:28
+ * @apiNote: JDK 8  新日期类 格式化与字符串转换 工具类
+ */
+public class LocalDateUtil {
+
+    public static final DateTimeFormatter DFY_MD_HMS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    public static final DateTimeFormatter DFY_MD = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    public static final DateTimeFormatter DFY_MD_2 = DateTimeFormatter.ofPattern("yyyyMMdd");
+
+    /**
+     * LocalDateTime 转时间戳
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static Long getTimeStamp(LocalDateTime localDateTime) {
+        return localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond();
+    }
+
+    /**
+     * 时间戳转LocalDateTime
+     *
+     * @param timeStamp /
+     * @return /
+     */
+    public static LocalDateTime fromTimeStamp(Long timeStamp) {
+        return LocalDateTime.ofEpochSecond(timeStamp, 0, OffsetDateTime.now().getOffset());
+    }
+
+    /**
+     * LocalDateTime 转 Date
+     * Jdk8 后 不推荐使用 {@link Date} Date
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static Date toDate(LocalDateTime localDateTime) {
+        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * LocalDate 转 Date
+     * Jdk8 后 不推荐使用 {@link Date} Date
+     *
+     * @param localDate /
+     * @return /
+     */
+    public static Date toDate(LocalDate localDate) {
+        return toDate(localDate.atTime(LocalTime.now(ZoneId.systemDefault())));
+    }
+
+
+    /**
+     * Date转 LocalDateTime
+     * Jdk8 后 不推荐使用 {@link Date} Date
+     *
+     * @param date /
+     * @return /
+     */
+    public static LocalDateTime toLocalDateTime(Date date) {
+        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
+    }
+
+    /**
+     * 日期 格式化
+     *
+     * @param localDateTime /
+     * @param patten        /
+     * @return /
+     */
+    public static String localDateTimeFormat(LocalDateTime localDateTime, String patten) {
+        DateTimeFormatter df = DateTimeFormatter.ofPattern(patten);
+        return df.format(localDateTime);
+    }
+
+    public static String localDateTimeFormat(LocalDate localDate, String patten) {
+        DateTimeFormatter df = DateTimeFormatter.ofPattern(patten);
+        return df.format(localDate);
+    }
+
+    /**
+     * 日期 格式化
+     *
+     * @param localDateTime /
+     * @param df            /
+     * @return /
+     */
+    public static String localDateTimeFormat(LocalDateTime localDateTime, DateTimeFormatter df) {
+        return df.format(localDateTime);
+    }
+
+    /**
+     * 日期格式化 yyyy-MM-dd HH:mm:ss
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static String localDateTimeFormatyMdHms(LocalDateTime localDateTime) {
+        return DFY_MD_HMS.format(localDateTime);
+    }
+
+    /**
+     * 日期格式化 yyyy-MM-dd
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static String localDateTimeFormatyMd(LocalDateTime localDateTime) {
+        return DFY_MD.format(localDateTime);
+    }
+
+    /**
+     * 日期格式化 yyyyMMdd
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static String localDateTimeFormatyMd2(LocalDateTime localDateTime) {
+        return DFY_MD_2.format(localDateTime);
+    }
+
+    /**
+     * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, String pattern) {
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
+        return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));
+    }
+
+    /**
+     * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, DateTimeFormatter dateTimeFormatter) {
+        return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));
+    }
+
+    /**
+     * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd HH:mm:ss
+     *
+     * @param localDateTime /
+     * @return /
+     */
+    public static LocalDateTime parseLocalDateTimeFormatyMdHms(String localDateTime) {
+        return LocalDateTime.from(DFY_MD_HMS.parse(localDateTime));
+    }
+
+    public static LocalDateTime formatTimeStr(String dateStr) {
+        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        LocalDateTime localTime;
+        try {
+            if (dateStr.contains("-")) {
+                localTime = LocalDateTime.parse(dateStr, df);
+            } else {
+                localTime = new Date(Long.valueOf(dateStr)).toInstant().atOffset(ZoneOffset.of("+8")).toLocalDateTime();
+            }
+        } catch (Exception e) {
+            // 提取年月部分
+            String[] parts = dateStr.split("[- :]");
+            int year = Integer.parseInt(parts[0]);
+            int month = Integer.parseInt(parts[1]);
+
+            // 获取该月的最后一天
+            LocalDateTime lastDay = LocalDateTime.of(year, month, 1, 0, 0)
+                    .with(TemporalAdjusters.lastDayOfMonth());
+
+            // 设置时间部分
+            int hour = Integer.parseInt(parts[3]);
+            int minute = Integer.parseInt(parts[4]);
+            int second = Integer.parseInt(parts[5]);
+
+            return lastDay.withHour(hour).withMinute(minute).withSecond(second);
+        }
+
+        return localTime;
+    }
+
+    public static LocalDate formatDateStr(String dateStr) {
+        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDate localTime;
+        if (dateStr.contains("-")) {
+            localTime = LocalDate.parse(dateStr, df);
+        } else {
+            localTime = new Date(Long.valueOf(dateStr)).toInstant().atOffset(ZoneOffset.of("+8")).toLocalDate();
+        }
+        return localTime;
+    }
+
+    public static LocalDateTime replaceFormatTimeStr(String timeStr, String replaceStr) {
+        if (StringUtil.isEmpty(replaceStr)) {
+            return formatTimeStr(timeStr);
+        }
+        int paramLen = replaceStr.length();
+        String targetTimeStr = replaceStr + timeStr.substring(paramLen);
+        return formatTimeStr(targetTimeStr);
+    }
+
+    public static String replaceLocalDateTimeStr(String timeStr, String replaceStr) {
+        if (StringUtil.isEmpty(replaceStr) || StringUtil.isEmpty(timeStr)) {
+            return timeStr;
+        }
+        int paramLen = replaceStr.length();
+        String targetTimeStr = replaceStr + timeStr.substring(paramLen);
+        LocalDateTime dateTime = formatTimeStr(targetTimeStr);
+        return localDateTimeFormatyMdHms(dateTime);
+    }
+}

+ 41 - 0
easydo-common/src/main/java/easydo/technology/utils/MD5Util.java

@@ -0,0 +1,41 @@
+package easydo.technology.utils;
+
+
+import java.security.MessageDigest;
+
+public class MD5Util {
+    /**
+     * 32位MD5加密的大写字符串
+     *
+     * @param s 需要加密的字符串
+     * @return
+     */
+
+    public final static String MD5(String s) {
+        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+        try {
+            byte[] btInput = s.getBytes();
+            // 获得MD5摘要算法的 MessageDigest 对象
+            MessageDigest mdInst = MessageDigest.getInstance("MD5");
+            // 使用指定的字节更新摘要
+            mdInst.update(btInput);
+            // 获得密文
+            byte[] md = mdInst.digest();
+            // 把密文转换成十六进制的字符串形式
+            int j = md.length;
+            char str[] = new char[j * 2];
+            int k = 0;
+            for (int i = 0; i < j; i++) {
+                byte byte0 = md[i];
+                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
+                str[k++] = hexDigits[byte0 & 0xf];
+            }
+            return new String(str);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+}
+

+ 114 - 0
easydo-common/src/main/java/easydo/technology/utils/MapUtil.java

@@ -0,0 +1,114 @@
+package easydo.technology.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.annotation.TableField;
+import easydo.technology.annotation.NotTableField;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * map工具类
+ *
+ *
+ * @author whz 2020-12-25
+ */
+public class MapUtil {
+
+    /**
+     * map转List
+     */
+    public static <T> List<T> mapToList(Map<String, Object> map, Class<T> clazz, String key) {
+        List<T> t = null;
+        JSONObject jsonObject = mapToJson(map);
+        JSONArray array = jsonObject.getJSONArray(key);
+        t = array.toJavaList(clazz);
+        return t;
+    }
+
+    /**
+     * map转Json
+     */
+    public static JSONObject mapToJson(Map<String, Object> map) {
+        String data = JSON.toJSONString(map);
+        return JSON.parseObject(data);
+    }
+
+
+    /**
+     * map中取key对应的value
+     *
+     * @param map
+     * @param key
+     * @return
+     */
+    public static String mapToString(Map<String, Object> map, String key) {
+        JSONObject jsonObject = mapToJson(map);
+        return jsonObject.getString(key);
+    }
+
+    /**
+     * map中取类对象
+     *
+     * @param map
+     * @param clazz
+     * @param key
+     * @param <T>
+     * @return
+     */
+    public static <T> T mapToObject(Map<String, Object> map, Class<T> clazz, String key) {
+        T t = null;
+        JSONObject jsonObject = mapToJson(map);
+        JSONObject object = jsonObject.getJSONObject(key);
+        t = object.toJavaObject(clazz);
+        return t;
+    }
+
+    public static <T> T mapToObject(Map<String, Object> map, Class<T> clazz) {
+        T t = null;
+        JSONObject jsonObject = mapToJson(map);
+        t = jsonObject.toJavaObject(clazz);
+        return t;
+    }
+
+
+    /**
+     * 对象转化为Map
+     *
+     * @param obj
+     * @return
+     * @throws Exception
+     */
+    public static Map<String, Object> objectToMap(Object obj) throws Exception {
+        if (obj == null) {
+            return new HashMap<>();
+        }
+        Map<String, Object> map = new HashMap<>();
+
+        Field[] declaredFields = obj.getClass().getDeclaredFields();
+        for (Field field : declaredFields) {
+            if ("folders".equals(field.getName()) || "serialVersionUID".equals(field.getName())) {
+                continue;
+            }
+            if (field.isAnnotationPresent(NotTableField.class)) {
+                continue;
+            }
+            if (field.isAnnotationPresent(TableField.class)) {
+                if (!field.getAnnotation(TableField.class).exist()) {
+                    continue;
+                }
+            }
+            field.setAccessible(true);
+            if (StringUtil.isEmpty(field.get(obj))) {
+                continue;
+            }
+            map.put(field.getName(), field.get(obj));
+        }
+
+        return map;
+    }
+}

+ 9 - 0
easydo-common/src/main/java/easydo/technology/utils/MyUtil.java

@@ -0,0 +1,9 @@
+package easydo.technology.utils;
+
+/**
+ * 一些不好归类的常用工具类
+ * Created by jinjin on 2020-10-01.
+ */
+public class MyUtil {
+
+}

+ 87 - 0
easydo-common/src/main/java/easydo/technology/utils/PageUtil.java

@@ -0,0 +1,87 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+
+import java.util.*;
+
+/**
+ * 分页工具
+ * 
+ * @author Zheng Jie
+ * @date 2018-12-10
+ */
+public class PageUtil {
+
+    /**
+     * List 分页
+     */
+    public static <T> List<T> toPage(int page, int size, List<T> list) {
+        int fromIndex = page * size;
+        int toIndex = page * size + size;
+        if (fromIndex > list.size()) {
+            return new ArrayList<>();
+        } else if (toIndex >= list.size()) {
+            return list.subList(fromIndex, list.size());
+        } else {
+            return list.subList(fromIndex, toIndex);
+        }
+    }
+
+    /**
+     * Page 数据处理,预防redis反序列化报错
+     */
+    public static <T> Map<String, Object> toPage(Page<T> page) {
+        Map<String, Object> map = new LinkedHashMap<>(2);
+        map.put("content", page.getContent());
+        map.put("totalElements", page.getTotalElements());
+        return map;
+    }
+
+    /**
+     * 自定义分页
+     */
+    public static Map<String, Object> toPage(Object object, Object totalElements) {
+        Map<String, Object> map = new LinkedHashMap<>(2);
+        map.put("content", object);
+        map.put("totalElements", totalElements);
+        return map;
+    }
+
+    public static <T> IPage<T> toMybatisPage(Pageable pageable) {
+        return toMybatisPage(pageable, false);
+    }
+
+    public static <T> IPage<T> toMybatisPage(Pageable pageable, boolean ignoreOrderBy) {
+        com.baomidou.mybatisplus.extension.plugins.pagination.Page<T> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(
+                pageable.getPageNumber() + 1, pageable.getPageSize());
+        if (!ignoreOrderBy) {
+            for (Sort.Order order : pageable.getSort()) {
+                OrderItem orderItem = new OrderItem();
+                orderItem.setAsc(order.isAscending());
+                orderItem.setColumn(
+                        com.baomidou.mybatisplus.core.toolkit.StringUtils.camelToUnderline(order.getProperty()));
+                page.addOrder(orderItem);
+            }
+        }
+        return page;
+    }
+}

+ 714 - 0
easydo-common/src/main/java/easydo/technology/utils/RedisUtils.java

@@ -0,0 +1,714 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.Cursor;
+import org.springframework.data.redis.core.RedisConnectionUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author /
+ */
+@Component
+@SuppressWarnings({"unchecked", "all"})
+public class RedisUtils {
+    private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);
+    private RedisTemplate<Object, Object> redisTemplate;
+    @Value("${jwt.online-key}")
+    private String onlineKey;
+
+    public RedisUtils(RedisTemplate<Object, Object> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+
+    /**
+     * 指定缓存失效时间
+     *
+     * @param key  键
+     * @param time 时间(秒)
+     */
+    public boolean expire(String key, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 指定缓存失效时间
+     *
+     * @param key      键
+     * @param time     时间(秒)
+     * @param timeUnit 单位
+     */
+    public boolean expire(String key, long time, TimeUnit timeUnit) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, timeUnit);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 根据 key 获取过期时间
+     *
+     * @param key 键 不能为null
+     * @return 时间(秒) 返回0代表为永久有效
+     */
+    public long getExpire(Object key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 查找匹配key
+     *
+     * @param pattern key
+     * @return /
+     */
+    public List<String> scan(String pattern) {
+        ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
+        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+        Cursor<byte[]> cursor = rc.scan(options);
+        List<String> result = new ArrayList<>();
+        while (cursor.hasNext()) {
+            result.add(new String(cursor.next()));
+        }
+        try {
+            RedisConnectionUtils.releaseConnection(rc, factory);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return result;
+    }
+
+    /**
+     * 分页查询 key
+     *
+     * @param patternKey key
+     * @param page       页码
+     * @param size       每页数目
+     * @return /
+     */
+    public List<String> findKeysForPage(String patternKey, int page, int size) {
+        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
+        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+        Cursor<byte[]> cursor = rc.scan(options);
+        List<String> result = new ArrayList<>(size);
+        int tmpIndex = 0;
+        int fromIndex = page * size;
+        int toIndex = page * size + size;
+        while (cursor.hasNext()) {
+            if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
+                result.add(new String(cursor.next()));
+                tmpIndex++;
+                continue;
+            }
+            // 获取到满足条件的数据后,就可以退出了
+            if (tmpIndex >= toIndex) {
+                break;
+            }
+            tmpIndex++;
+            cursor.next();
+        }
+        try {
+            RedisConnectionUtils.releaseConnection(rc, factory);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return result;
+    }
+
+    /**
+     * 判断key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public boolean hasKey(String key) {
+        try {
+            return redisTemplate.hasKey(key);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 删除缓存
+     *
+     * @param key 可以传一个值 或多个
+     */
+    public void del(String... keys) {
+        if (keys != null && keys.length > 0) {
+            if (keys.length == 1) {
+                boolean result = redisTemplate.delete(keys[0]);
+                log.debug("--------------------------------------------");
+                log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString());
+                log.debug("--------------------------------------------");
+            } else {
+                Set<Object> keySet = new HashSet<>();
+                for (String key : keys) {
+                    keySet.addAll(redisTemplate.keys(key));
+                }
+                long count = redisTemplate.delete(keySet);
+                log.debug("--------------------------------------------");
+                log.debug("成功删除缓存:" + keySet.toString());
+                log.debug("缓存删除数量:" + count + "个");
+                log.debug("--------------------------------------------");
+            }
+        }
+    }
+
+    // ============================String=============================
+
+    /**
+     * 普通缓存获取
+     *
+     * @param key 键
+     * @return 值
+     */
+    public Object get(String key) {
+        return key == null ? null : redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 批量获取
+     *
+     * @param keys
+     * @return
+     */
+    // public List<Object> multiGet(List<String> keys) {
+    //     List list = redisTemplate.opsForValue().multiGet(Sets.newHashSet(keys));
+    //     List resultList = Lists.newArrayList();
+    //     Optional.ofNullable(list).ifPresent(e-> list.forEach(ele-> Optional.ofNullable(ele).ifPresent(resultList::add)));
+    //     return resultList;
+    // }
+
+    /**
+     * 普通缓存放入
+     *
+     * @param key   键
+     * @param value 值
+     * @return true成功 false失败
+     */
+    public boolean set(String key, Object value) {
+        try {
+            redisTemplate.opsForValue().set(key, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
+     * @return true成功 false 失败
+     */
+    public boolean set(String key, Object value, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     *
+     * @param key      键
+     * @param value    值
+     * @param time     时间
+     * @param timeUnit 类型
+     * @return true成功 false 失败
+     */
+    public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, timeUnit);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    // ================================Map=================================
+
+    /**
+     * HashGet
+     *
+     * @param key  键 不能为null
+     * @param item 项 不能为null
+     * @return 值
+     */
+    public Object hget(String key, String item) {
+        return redisTemplate.opsForHash().get(key, item);
+    }
+
+    /**
+     * 获取hashKey对应的所有键值
+     *
+     * @param key 键
+     * @return 对应的多个键值
+     */
+    public Map<Object, Object> hmget(String key) {
+        return redisTemplate.opsForHash().entries(key);
+
+    }
+
+    /**
+     * HashSet
+     *
+     * @param key 键
+     * @param map 对应多个键值
+     * @return true 成功 false 失败
+     */
+    public boolean hmset(String key, Map<String, Object> map) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * HashSet 并设置时间
+     *
+     * @param key  键
+     * @param map  对应多个键值
+     * @param time 时间(秒)
+     * @return true成功 false失败
+     */
+    public boolean hmset(String key, Map<String, Object> map, long time) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value, long time) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 删除hash表中的值
+     *
+     * @param key  键 不能为null
+     * @param item 项 可以使多个 不能为null
+     */
+    public void hdel(String key, Object... item) {
+        redisTemplate.opsForHash().delete(key, item);
+    }
+
+    /**
+     * 判断hash表中是否有该项的值
+     *
+     * @param key  键 不能为null
+     * @param item 项 不能为null
+     * @return true 存在 false不存在
+     */
+    public boolean hHasKey(String key, String item) {
+        return redisTemplate.opsForHash().hasKey(key, item);
+    }
+
+    /**
+     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要增加几(大于0)
+     * @return
+     */
+    public double hincr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, by);
+    }
+
+    /**
+     * hash递减
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要减少记(小于0)
+     * @return
+     */
+    public double hdecr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, -by);
+    }
+
+    // ============================set=============================
+
+    /**
+     * 根据key获取Set中的所有值
+     *
+     * @param key 键
+     * @return
+     */
+    public Set<Object> sGet(String key) {
+        try {
+            return redisTemplate.opsForSet().members(key);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 根据value从一个set中查询,是否存在
+     *
+     * @param key   键
+     * @param value 值
+     * @return true 存在 false不存在
+     */
+    public boolean sHasKey(String key, Object value) {
+        try {
+            return redisTemplate.opsForSet().isMember(key, value);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 将数据放入set缓存
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSet(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * 将set数据放入缓存
+     *
+     * @param key    键
+     * @param time   时间(秒)
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSetAndTime(String key, long time, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().add(key, values);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return count;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * 获取set缓存的长度
+     *
+     * @param key 键
+     * @return
+     */
+    public long sGetSetSize(String key) {
+        try {
+            return redisTemplate.opsForSet().size(key);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * 移除值为value的
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 移除的个数
+     */
+    public long setRemove(String key, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().remove(key, values);
+            return count;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    // ===============================list=================================
+
+    /**
+     * 获取list缓存的内容
+     *
+     * @param key   键
+     * @param start 开始
+     * @param end   结束 0 到 -1代表所有值
+     * @return
+     */
+    public List<Object> lGet(String key, long start, long end) {
+        try {
+            return redisTemplate.opsForList().range(key, start, end);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 获取list缓存的长度
+     *
+     * @param key 键
+     * @return
+     */
+    public long lGetListSize(String key) {
+        try {
+            return redisTemplate.opsForList().size(key);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * 通过索引 获取list中的值
+     *
+     * @param key   键
+     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
+     * @return
+     */
+    public Object lGetIndex(String key, long index) {
+        try {
+            return redisTemplate.opsForList().index(key, index);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @return
+     */
+    public boolean lSet(String key, Object value) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, Object value, long time) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value, long time) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 根据索引修改list中的某条数据
+     *
+     * @param key   键
+     * @param index 索引
+     * @param value 值
+     * @return /
+     */
+    public boolean lUpdateIndex(String key, long index, Object value) {
+        try {
+            redisTemplate.opsForList().set(key, index, value);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 移除N个值为value
+     *
+     * @param key   键
+     * @param count 移除多少个
+     * @param value 值
+     * @return 移除的个数
+     */
+    public long lRemove(String key, long count, Object value) {
+        try {
+            return redisTemplate.opsForList().remove(key, count, value);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return 0;
+        }
+    }
+
+    /**
+     * @param prefix 前缀
+     * @param ids    id
+     */
+    public void delByKeys(String prefix, Set<Long> ids) {
+        Set<Object> keys = new HashSet<>();
+        for (Long id : ids) {
+            keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString()));
+        }
+        long count = redisTemplate.delete(keys);
+        // 此处提示可自行删除
+        log.debug("--------------------------------------------");
+        log.debug("成功删除缓存:" + keys.toString());
+        log.debug("缓存删除数量:" + count + "个");
+        log.debug("--------------------------------------------");
+    }
+}

+ 33 - 0
easydo-common/src/main/java/easydo/technology/utils/RequestHolder.java

@@ -0,0 +1,33 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Objects;
+
+/**
+ * 获取 HttpServletRequest
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+public class RequestHolder {
+
+    public static HttpServletRequest getHttpServletRequest() {
+        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
+    }
+}

+ 720 - 0
easydo-common/src/main/java/easydo/technology/utils/ResultSetUtil.java

@@ -0,0 +1,720 @@
+package easydo.technology.utils;
+
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import easydo.technology.AutoIncrementModel;
+import easydo.technology.annotation.NotTableField;
+import easydo.technology.annotation.TableKey;
+
+import java.lang.reflect.Field;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ResultSetUtil {
+    private static Pattern linePattern = Pattern.compile("_(\\w)");
+    private static String suffixBegin = "_begin";
+    private static String suffixEnd = "_end";
+    private static String suffixLike = "_like";
+    private static String suffixNot = "_not";
+    private static String suffixIn = "_in";
+    private static String suffixNotIn = "_notin";
+    private static String suffixNull = "_null";
+    private static String suffixNotNull = "_notnull";
+    private static String suffixApplySql = "apply_sql";
+
+
+    /**
+     * 下划线转驼峰
+     */
+    public static String lineToHump(String str) {
+        str = str.toLowerCase();
+        Matcher matcher = linePattern.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        while (matcher.find()) {
+            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+
+
+    private static Pattern humpPattern = Pattern.compile("[A-Z]");
+
+    /**
+     * 驼峰转下划线,效率比上面高
+     */
+    public static String humpToLine(String str) {
+        Matcher matcher = humpPattern.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        while (matcher.find()) {
+            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
+        }
+        matcher.appendTail(sb);
+        if (sb.toString().startsWith("_")) {
+            sb.deleteCharAt(0);
+        }
+        return sb.toString();
+    }
+
+    public static List<Map<String, Object>> formatResultSetToListMap(ResultSet resultSet) throws SQLException {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        while (resultSet.next()) {
+            ResultSetMetaData metaData = resultSet.getMetaData();
+            int columnCount = metaData.getColumnCount();
+            Map<String, Object> resultMap = new HashMap<>();
+            for (int i = 1; i <= columnCount; i++) {
+                String key = lineToHump(metaData.getColumnName(i));
+                if (key.endsWith("Time") || key.endsWith("Date")) {
+                    resultMap.put(key, formatDateAndTime(key, resultSet.getObject(i)));
+                } else {
+                    resultMap.put(key, resultSet.getObject(i));
+                }
+            }
+            resultList.add(resultMap);
+        }
+        return resultList;
+    }
+
+    public static <T> List<T> formatResultSetToList(ResultSet resultSet, Class<T> clazz) throws SQLException {
+        List<T> resultList = new ArrayList<>();
+        while (resultSet.next()) {
+            ResultSetMetaData metaData = resultSet.getMetaData();
+            int columnCount = metaData.getColumnCount();
+            JSONObject jsonObject = new JSONObject();
+            for (int i = 1; i <= columnCount; i++) {
+                String key = lineToHump(metaData.getColumnName(i));
+                if (key.endsWith("Time") || key.endsWith("Date")) {
+                    jsonObject.put(key, formatDateAndTime(key, resultSet.getObject(i)));
+                } else {
+                    jsonObject.put(key, resultSet.getObject(i));
+                }
+
+            }
+            T t = jsonObject.toJavaObject(clazz);
+            resultList.add(t);
+        }
+        return resultList;
+    }
+
+    public static List<String> formatResultSetToArr(ResultSet resultSet) throws SQLException {
+        List<String> resultList = new ArrayList<>();
+        while (resultSet.next()) {
+            ResultSetMetaData metaData = resultSet.getMetaData();
+            String key = lineToHump(metaData.getColumnName(1));
+            if (key.endsWith("Time") || key.endsWith("Date")) {
+                Object object = formatDateAndTime(key, resultSet.getObject(1));
+                resultList.add(object == null ? "" : object.toString());
+            } else {
+                resultList.add(resultSet.getObject(1) == null ? "" : resultSet.getObject(1).toString());
+            }
+
+        }
+        return resultList;
+    }
+
+    public static <T> T formatResultSetToModel(ResultSet resultSet, Class<T> clazz) throws SQLException {
+        T t;
+        JSONObject jsonObject = new JSONObject();
+        if (resultSet.next()) {
+            ResultSetMetaData metaData = resultSet.getMetaData();
+            int columnCount = metaData.getColumnCount();
+            for (int i = 1; i <= columnCount; i++) {
+                String key = lineToHump(metaData.getColumnName(i));
+                if (key.endsWith("Time") || key.endsWith("Date")) {
+                    jsonObject.put(key, formatDateAndTime(key, resultSet.getObject(i)));
+                } else {
+                    jsonObject.put(key, resultSet.getObject(i));
+                }
+
+            }
+        }
+        t = jsonObject.toJavaObject(clazz);
+        return t;
+    }
+
+    private static Object formatDateAndTime(String key, Object object) {
+        if (key.endsWith("Time")) {
+            try {
+                String timeStr = object.toString();
+                timeStr = timeStr.replace("T", "").substring(0, 19);
+                return timeStr;
+            } catch (Exception e) {
+                return object;
+            }
+        } else if (key.endsWith("Date")) {
+            try {
+                String timeStr = object.toString();
+                timeStr = timeStr.substring(0, 10);
+                return timeStr;
+            } catch (Exception e) {
+                return object;
+            }
+        } else {
+            return object;
+        }
+    }
+
+    public static String formatSqlCondition(Map<String, Object> map) throws RuntimeException {
+        String orderLimitSql = formatPageSqlCondition(map);
+
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append(" where 1=1 ");
+        map.entrySet().stream()
+                .filter(entry -> StringUtil.isNotEmpty(entry.getKey()) && StringUtil.isNotEmpty(entry.getValue()))
+                .forEach(entry -> {
+                    String key = entry.getKey();
+                    Object value = entry.getValue();
+                    if (containsSqlInjection(value) && !"apply_sql".equals(key)) {
+                        throw new RuntimeException("非法参数");
+                    }
+                    String column = humpToLine(key);
+                    if (column.endsWith(suffixBegin)) {
+                        stringBuilder.append(" and ");
+                        //解决字段中存在begin和end的情况
+                        int lastIndex = column.lastIndexOf(suffixBegin);
+                        column = column.substring(0, lastIndex)
+                                + column.substring(lastIndex + suffixBegin.length());
+                        stringBuilder.append("`").append(column).append("`");
+                        stringBuilder.append(">=").append("'").append(value).append("'");
+                    } else if (column.endsWith(suffixEnd)) {
+                        stringBuilder.append(" and ");
+                        //解决字段中存在begin和end的情况
+                        int lastIndex = column.lastIndexOf(suffixEnd);
+                        column = column.substring(0, lastIndex)
+                                + column.substring(lastIndex + suffixEnd.length());
+                        stringBuilder.append("`").append(column).append("`");
+                        stringBuilder.append("<=").append("'").append(value).append("'");
+                    } else if (column.endsWith(suffixLike)) {
+                        stringBuilder.append(" and ");
+                        int lastIndex = column.lastIndexOf(suffixLike);
+                        column = column.substring(0, lastIndex)
+                                + column.substring(lastIndex + suffixLike.length());
+                        stringBuilder.append("`").append(column).append("`");
+                        stringBuilder.append(" like '%").append(value).append("%'");
+                    } else if (column.endsWith(suffixNot)) {
+                        stringBuilder.append(" and ");
+                        int lastIndex = column.lastIndexOf(suffixNot);
+                        column = column.substring(0, lastIndex)
+                                + column.substring(lastIndex + suffixNot.length());
+                        stringBuilder.append("`").append(column).append("`").append("!=");
+                        if ("true".equals(value.toString()) || "false".equals(value.toString())) {
+                            stringBuilder.append(value);
+                        } else {
+                            stringBuilder.append("'").append(value).append("'");
+                        }
+                    } else if (column.endsWith(suffixIn)) {
+                        stringBuilder.append(" and ");
+                        int lastIndex = column.lastIndexOf(suffixIn);
+                        column = column.substring(0, lastIndex)
+                                + column.substring(lastIndex + suffixIn.length());
+                        List<Object> list = (List<Object>) value;
+                        stringBuilder.append("`").append(column).append("`");
+                        stringBuilder.append(" in (");
+                        if (!list.isEmpty()) {
+                            list.forEach(str -> {
+                                stringBuilder.append("'").append(str).append("',");
+                            });
+                        } else {
+                            stringBuilder.append("'null',");
+                        }
+                        stringBuilder.deleteCharAt(stringBuilder.toString().length() - 1);
+                        stringBuilder.append(")");
+                    } else if (column.endsWith(suffixNotIn)) {
+                        stringBuilder.append(" and ");
+                        int lastIndex = column.lastIndexOf(suffixNotIn);
+                        column = column.substring(0, lastIndex)
+                                + column.substring(lastIndex + suffixNotIn.length());
+                        List<String> list = (List<String>) value;
+                        stringBuilder.append("`").append(column).append("`");
+                        stringBuilder.append(" not in (");
+                        if (!list.isEmpty()) {
+                            list.forEach(str -> {
+                                stringBuilder.append("'").append(str).append("',");
+                            });
+                        } else {
+                            stringBuilder.append("'null',");
+                        }
+                        stringBuilder.deleteCharAt(stringBuilder.toString().length() - 1);
+                        stringBuilder.append(")");
+                    } else if (column.endsWith(suffixNull)) {
+                        stringBuilder.append(" and ");
+                        int lastIndex = column.lastIndexOf(suffixNull);
+                        column = column.substring(0, lastIndex)
+                                + column.substring(lastIndex + suffixNull.length());
+                        stringBuilder.append("`").append(column).append("`").append(" is null");
+                    } else if (column.endsWith(suffixNotNull)) {
+                        stringBuilder.append(" and ");
+                        int lastIndex = column.lastIndexOf(suffixNotNull);
+                        column = column.substring(0, lastIndex)
+                                + column.substring(lastIndex + suffixNotNull.length());
+                        stringBuilder.append("`").append(column).append("`").append(" is not null");
+                    } else if (column.equals(suffixApplySql)) {
+                        stringBuilder.append(" and ");
+                        stringBuilder.append("(").append(value).append(")");
+                    } else if (column.startsWith("(") && column.endsWith(")")) {
+                        stringBuilder.append(" and ");
+                        column = key.substring(0, key.length() - 1);
+                        stringBuilder.append("`").append(column).append("`").append("=").append("'").append(value).append("')");
+                    } else {
+                        stringBuilder.append(" and ");
+                        stringBuilder.append("`").append(column).append("`").append("=");
+                        if ("true".equals(value.toString()) || "false".equals(value.toString())) {
+                            stringBuilder.append(value);
+                        } else {
+                            stringBuilder.append("'").append(value).append("'");
+                        }
+                    }
+                });
+        stringBuilder.append(orderLimitSql);
+        return stringBuilder.toString();
+    }
+
+    private static String formatPageSqlCondition(Map<String, Object> map) {
+        StringBuilder limitSql = new StringBuilder();
+        StringBuilder orderBySql = new StringBuilder();
+        StringBuilder groupBySql = new StringBuilder();
+        if (map.containsKey("current") & map.containsKey("size")) {
+            int current = (int) map.get("current");
+            int size = (int) map.get("size");
+            limitSql.append(" limit ").append((current - 1) * size).append(",").append(size);
+        }
+        if (map.containsKey("orderBy")) {
+            String orderBy = map.get("orderBy").toString();
+            String[] orderByArr = orderBy.split(",");
+            orderBySql.append(" order by ");
+            for (String name : orderByArr) {
+                String[] nameArr = name.split("_");
+                orderBySql.append("`").append(ResultSetUtil.humpToLine(nameArr[0])).append("` ").append(nameArr[1]).append(",");
+            }
+            orderBySql.deleteCharAt(orderBySql.toString().length() - 1);
+        }
+        if (map.containsKey("groupBy")) {
+            String groupBy = map.get("groupBy").toString();
+            String[] groupByArr = groupBy.split("-");
+            groupBySql.append(" group by ");
+            for (String name : groupByArr) {
+                groupBySql.append("`").append(ResultSetUtil.humpToLine(name)).append("` ").append(",");
+            }
+            groupBySql.deleteCharAt(groupBySql.toString().length() - 1);
+        }
+
+        map.remove("current");
+        map.remove("size");
+        map.remove("orderBy");
+        map.remove("groupBy");
+        map.remove("folders");
+        map.remove("serialVersionUID");
+        return orderBySql.toString() + groupBySql.toString() + limitSql.toString();
+    }
+
+    public static String formatSelectByIdSql(Object obj) throws Exception {
+        String tableName = obj.getClass().getSimpleName();
+        tableName = ResultSetUtil.humpToLine(tableName);
+
+        Field[] declaredFields = obj.getClass().getDeclaredFields();
+        Map<String, Object> map = new HashMap<>();
+        for (Field field : declaredFields) {
+            if (field.isAnnotationPresent(TableKey.class)) {
+                field.setAccessible(true);
+                map.put(field.getName(), field.get(obj));
+                break;
+            }
+            if (field.isAnnotationPresent(TableId.class)) {
+                field.setAccessible(true);
+                if (StringUtil.isEmpty(field.getAnnotation(TableId.class).value())) {
+                    map.put(field.getName(), field.get(obj));
+                } else {
+                    map.put(field.getAnnotation(TableId.class).value(), field.get(obj));
+                }
+                break;
+            }
+        }
+        if (map.isEmpty()) {
+            Field idField = obj.getClass().getDeclaredField("id");
+            idField.setAccessible(true);
+            map.put("id", idField.get(obj));
+        }
+
+        StringBuilder sql = new StringBuilder("select * from " + tableName + " where ");
+
+        for (String key : map.keySet()) {
+            sql.append(humpToLine(key)).append(" = '").append(map.get(key)).append("'");
+        }
+
+        return sql.toString();
+    }
+
+    /*
+     * 此方法不设计到查某一个具体的主键有几条数据,
+     * 因此不需要添加TableId的注解逻辑
+     * */
+    public static String formatSelectCountSql(Object obj) throws Exception {
+        String tableName = obj.getClass().getSimpleName();
+        tableName = ResultSetUtil.humpToLine(tableName);
+
+        StringBuilder stringBuffer = new StringBuilder();
+        stringBuffer.append(" where 1=1 ");
+        Field[] declaredFields = obj.getClass().getDeclaredFields();
+        for (Field field : declaredFields) {
+            if ("folders".equals(field.getName()) || "serialVersionUID".equals(field.getName())) {
+                continue;
+            }
+            if (field.isAnnotationPresent(NotTableField.class)) {
+                continue;
+            }
+            if (field.isAnnotationPresent(TableField.class)) {
+                if (!field.getAnnotation(TableField.class).exist()) {
+                    continue;
+                }
+            }
+            field.setAccessible(true);
+            if (StringUtil.isEmpty(field.get(obj))) {
+                continue;
+            }
+            String column = humpToLine(field.getName());
+            stringBuffer.append(" and `").append(column).append("`=");
+            if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
+                stringBuffer.append(field.get(obj));
+            } else {
+                stringBuffer.append("'").append(field.get(obj)).append("'");
+            }
+        }
+
+        return "select count(1) from " + tableName + stringBuffer.toString();
+    }
+
+    public static String formatSelectCountSql(Class clazz, Map<String, Object> map) throws Exception {
+        String tableName = clazz.getSimpleName();
+        tableName = ResultSetUtil.humpToLine(tableName);
+
+        return "select count(1) from " + tableName + formatSqlCondition(map);
+    }
+
+    /*
+     * 此方法不设计到getById,
+     * 因此不需要添加TableId的注解逻辑
+     * */
+    public static String formatSelectSql(Object obj) throws Exception {
+        String tableName = obj.getClass().getSimpleName();
+        tableName = ResultSetUtil.humpToLine(tableName);
+
+        StringBuilder stringBuffer = new StringBuilder();
+        stringBuffer.append(" where 1=1 ");
+        Field[] declaredFields = obj.getClass().getDeclaredFields();
+        for (Field field : declaredFields) {
+            if ("folders".equals(field.getName()) || "serialVersionUID".equals(field.getName())) {
+                continue;
+            }
+            if (field.isAnnotationPresent(NotTableField.class)) {
+                continue;
+            }
+            if (field.isAnnotationPresent(TableField.class)) {
+                if (!field.getAnnotation(TableField.class).exist()) {
+                    continue;
+                }
+            }
+            field.setAccessible(true);
+            if (StringUtil.isEmpty(field.get(obj))) {
+                continue;
+            }
+            String column = humpToLine(field.getName());
+            stringBuffer.append(" and `").append(column).append("`=");
+            if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
+                stringBuffer.append(field.get(obj));
+            } else {
+                stringBuffer.append("'").append(field.get(obj)).append("'");
+            }
+        }
+        if (stringBuffer.toString().endsWith("1=1 ")) {
+            throw new Exception("关键参数缺失");
+        }
+        return "select * from " + tableName + stringBuffer.toString() + " limit 1";
+    }
+
+    public static String formatSelectSql(Map<String, Object> map, Class clazz) throws Exception {
+        String tableName = clazz.getSimpleName();
+        tableName = ResultSetUtil.humpToLine(tableName);
+        String condition = formatSqlCondition(map);
+        if (condition.endsWith("1=1 ")) {
+            throw new Exception("关键参数缺失");
+        }
+        return "select * from " + tableName + condition + " limit 1";
+    }
+
+    public static String formatSelectSql(Map<String, Object> map, String sql) throws Exception {
+        String condition = formatSqlCondition(map);
+        return sql + condition;
+    }
+
+    public static String formatInsertSql(Object obj) throws Exception {
+        String tableName = obj.getClass().getSimpleName();
+        tableName = ResultSetUtil.humpToLine(tableName);
+
+        StringBuilder stringBuffer = new StringBuilder();
+        stringBuffer.append("insert into ").append(tableName);
+        StringBuilder stringBufferBe = new StringBuilder();
+        StringBuilder stringBufferAf = new StringBuilder();
+
+        Field[] declaredFields = obj.getClass().getDeclaredFields();
+        for (Field field : declaredFields) {
+            field.setAccessible(true);
+            if ("folders".equals(field.getName()) || "serialVersionUID".equals(field.getName())) {
+                continue;
+            }
+            if (field.isAnnotationPresent(NotTableField.class)) {
+                continue;
+            }
+            if (field.isAnnotationPresent(TableField.class)) {
+                if (!field.getAnnotation(TableField.class).exist()) {
+                    continue;
+                }
+                //解决字段不是根据驼峰与下划线的转换规则的情况
+                if (StringUtil.isNotEmpty(field.getAnnotation(TableField.class).value())
+                        && StringUtil.isNotEmpty(StringUtil.isEmpty(field.get(obj)))) {
+                    stringBufferBe.append("`").append(field.getAnnotation(TableField.class).value()).append("`").append(",");
+                    if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
+                        stringBufferAf.append(field.get(obj));
+                    } else {
+                        String replaceStr = field.get(obj).toString();
+                        stringBufferAf.append("'").append(replaceStr.replace("'", "^")).append("'");
+                    }
+                    stringBufferAf.append(",");
+                    continue;
+                }
+            }
+            if ("id".equals(field.getName())) {
+                if (StringUtil.isEmpty(field.get(obj))) {
+                    field.set(obj, UUID.randomUUID().toString());
+                }
+            } else if ("createTime".equals(field.getName()) || "updateTime".equals(field.getName())) {
+                if (StringUtil.isEmpty(field.get(obj))) {
+                    if (field.getType().getSimpleName().equals(LocalDateTime.class.getSimpleName())) {
+                        field.set(obj, LocalDateTime.now());
+                    } else {
+                        field.set(obj, LocalDateUtil.localDateTimeFormatyMdHms(LocalDateTime.now()));
+                    }
+                }
+            }
+            if (StringUtil.isEmpty(field.get(obj))) {
+                continue;
+            }
+            if (containsSqlInjection(field.get(obj))) {
+                throw new Exception("非法参数");
+            }
+
+            String column = humpToLine(field.getName());
+            stringBufferBe.append("`").append(column).append("`").append(",");
+            if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
+                stringBufferAf.append(field.get(obj));
+            } else {
+                String replaceStr = field.get(obj).toString();
+                stringBufferAf.append("'").append(replaceStr.replace("'", "^")).append("'");
+            }
+            stringBufferAf.append(",");
+        }
+
+        stringBufferBe.deleteCharAt(stringBufferBe.toString().length() - 1);
+        stringBufferAf.deleteCharAt(stringBufferAf.toString().length() - 1);
+        stringBuffer.append("(").append(stringBufferBe).append(") values (").append(stringBufferAf).append(")");
+
+        return stringBuffer.toString();
+    }
+
+    public static String formatUpdateByIdSql(Object obj) throws Exception {
+        Field[] declaredFields = obj.getClass().getDeclaredFields();
+        Map<String, Object> map = new HashMap<>();
+        for (Field field : declaredFields) {
+            if (field.isAnnotationPresent(TableKey.class)) {
+                field.setAccessible(true);
+                map.put(field.getName(), field.get(obj));
+                break;
+            }
+            if (field.isAnnotationPresent(TableId.class)) {
+                field.setAccessible(true);
+                if (StringUtil.isEmpty(field.getAnnotation(TableId.class).value())) {
+                    map.put(field.getName(), field.get(obj));
+                } else {
+                    map.put(field.getAnnotation(TableId.class).value(), field.get(obj));
+                }
+                break;
+            }
+        }
+        if (map.isEmpty()) {
+            Field idField = obj.getClass().getDeclaredField("id");
+            idField.setAccessible(true);
+            map.put("id", idField.get(obj));
+        }
+
+        return formatUpdateSql(obj, map);
+    }
+
+    public static String formatUpdateSql(Object obj, Map<String, Object> whereMap) throws Exception {
+        String tableName = obj.getClass().getSimpleName();
+        tableName = ResultSetUtil.humpToLine(tableName);
+
+        StringBuilder stringBuffer = new StringBuilder();
+        stringBuffer.append("update ").append(tableName).append(" set ");
+        Field[] declaredFields = obj.getClass().getDeclaredFields();
+        for (Field field : declaredFields) {
+            field.setAccessible(true);
+            if ("folders".equals(field.getName()) || "serialVersionUID".equals(field.getName())) {
+                continue;
+            }
+            if (field.isAnnotationPresent(NotTableField.class)) {
+                if ("emptyField".equals(field.getName()) && StringUtil.isNotEmpty(field.get(obj))) {
+                    String[] emptyFields = field.get(obj).toString().split(",");
+                    for (String fieldName : emptyFields) {
+                        stringBuffer.append("`").append(humpToLine(fieldName)).append("`").append("=null,");
+                    }
+                }
+                continue;
+            }
+            if (field.isAnnotationPresent(TableField.class)) {
+                if (!field.getAnnotation(TableField.class).exist()) {
+                    continue;
+                }
+                //解决字段不是根据驼峰与下划线的转换规则的情况
+                if (StringUtil.isNotEmpty(field.getAnnotation(TableField.class).value())
+                        && StringUtil.isNotEmpty(StringUtil.isEmpty(field.get(obj)))) {
+                    stringBuffer.append("`").append(field.getAnnotation(TableField.class).value()).append("`").append("=");
+                    if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
+                        stringBuffer.append(field.get(obj));
+                    } else {
+                        String replaceStr = field.get(obj).toString();
+                        stringBuffer.append("'").append(replaceStr.replace("'", "^")).append("'");
+                    }
+                    stringBuffer.append(",");
+                    continue;
+                }
+            }
+            if (field.isAnnotationPresent(TableKey.class) || field.isAnnotationPresent(TableId.class) || "id".equals(field.getName())) {
+                continue;
+            }
+
+            if ("updateTime".equals(field.getName()) && field.get(obj) == null) {
+                if (field.getType().getSimpleName().equals(LocalDateTime.class.getSimpleName())) {
+                    field.set(obj, LocalDateTime.now());
+                } else {
+                    field.set(obj, LocalDateUtil.localDateTimeFormatyMdHms(LocalDateTime.now()));
+                }
+            }
+            if (StringUtil.isEmpty(field.get(obj))) {
+                continue;
+            }
+            if (containsSqlInjection(field.get(obj))) {
+                throw new Exception("非法参数");
+            }
+            String column = humpToLine(field.getName());
+            stringBuffer.append("`").append(column).append("`").append("=");
+            if (field.getType().getSimpleName().equals(Boolean.class.getSimpleName())) {
+                stringBuffer.append(field.get(obj));
+            } else {
+                stringBuffer.append("'").append(field.get(obj)).append("'");
+            }
+            stringBuffer.append(",");
+        }
+
+        if (whereMap.size() == 0) {
+            throw new Exception("关键参数缺失");
+        }
+
+        stringBuffer.deleteCharAt(stringBuffer.toString().length() - 1);
+
+        stringBuffer.append(formatSqlCondition(whereMap));
+        if (stringBuffer.toString().endsWith("1=1 ")) {
+            throw new Exception("关键参数缺失");
+        }
+        return stringBuffer.toString();
+    }
+
+    public static String formatRemoveByIdSql(Object obj) throws Exception {
+        String tableName = obj.getClass().getSimpleName();
+        tableName = ResultSetUtil.humpToLine(tableName);
+        Map<String, Object> map = new HashMap<>();
+
+        Field[] declaredFields = obj.getClass().getDeclaredFields();
+        for (Field field : declaredFields) {
+            if (field.isAnnotationPresent(TableKey.class)) {
+                field.setAccessible(true);
+                map.put(field.getName(), field.get(obj));
+                break;
+            }
+            if (field.isAnnotationPresent(TableId.class)) {
+                field.setAccessible(true);
+                if (StringUtil.isNotEmpty(field.getAnnotation(TableId.class).value())) {
+                    map.put(field.getAnnotation(TableId.class).value(), field.get(obj));
+                } else {
+                    map.put(field.getName(), field.get(obj));
+                }
+                break;
+            }
+        }
+        if (map.isEmpty()) {
+            Field idField = obj.getClass().getDeclaredField("id");
+            idField.setAccessible(true);
+            map.put("id", idField.get(obj));
+        }
+        StringBuilder sql = new StringBuilder("delete from " + tableName + " where ");
+
+        for (String key : map.keySet()) {
+            sql.append(humpToLine(key)).append(" = '").append(map.get(key)).append("'");
+        }
+
+        return sql.toString();
+    }
+
+    public static String formatRemoveSql(Object obj) throws Exception {
+        String tableName = obj.getClass().getSimpleName();
+        tableName = ResultSetUtil.humpToLine(tableName);
+        Map<String, Object> map = MapUtil.objectToMap(obj);
+
+        String sql = "delete from " + tableName + formatSqlCondition(map);
+        if (sql.endsWith("1=1 ")) {
+            throw new Exception("关键参数缺失");
+        }
+        return sql;
+    }
+
+    public static AutoIncrementModel isExistAutoIncrement(Object obj) throws Exception {
+        AutoIncrementModel incrementModel = new AutoIncrementModel();
+
+        Field[] declaredFields = obj.getClass().getDeclaredFields();
+        for (Field field : declaredFields) {
+            if (field.isAnnotationPresent(TableId.class)) {
+                if (IdType.AUTO.equals(field.getAnnotation(TableId.class).type())) {
+                    incrementModel.setIsAutoIncrement(true);
+                    incrementModel.setFieldName(field.getName());
+                    if (StringUtil.isNotEmpty(field.getAnnotation(TableId.class).value())) {
+                        incrementModel.setColumnName(field.getAnnotation(TableId.class).value());
+                    } else {
+                        incrementModel.setColumnName(ResultSetUtil.humpToLine(field.getName()));
+                    }
+                    return incrementModel;
+                }
+            }
+        }
+        return incrementModel;
+    }
+
+    /**
+     * 是否含有sql注入,返回true表示含有
+     *
+     * @param obj
+     * @return
+     */
+    private static boolean containsSqlInjection(Object obj) {
+        String objStr = obj.toString().toLowerCase();
+        // 使用正则表达式匹配独立的SQL关键字,注意正则表达式需要根据实际需求调整
+        String pattern = "\\b(and|exec|insert|select|drop|grant|alter|delete|update|count|chr|mid|master|truncate|char|declare|or|;)\\b";
+        return objStr.matches(pattern);
+    }
+}

+ 180 - 0
easydo-common/src/main/java/easydo/technology/utils/RsaUtils.java

@@ -0,0 +1,180 @@
+package easydo.technology.utils;
+
+import org.apache.commons.codec.binary.Base64;
+import javax.crypto.Cipher;
+import java.security.*;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * @author https://www.cnblogs.com/nihaorz/p/10690643.html
+ * @description Rsa 工具类,公钥私钥生成,加解密
+ * @date 2020-05-18
+ **/
+public class RsaUtils {
+
+    private static final String SRC = "123456";
+
+    public static void main(String[] args) throws Exception {
+        System.out.println("\n");
+        RsaKeyPair keyPair = generateKeyPair();
+        System.out.println("公钥:" + keyPair.getPublicKey());
+        System.out.println("私钥:" + keyPair.getPrivateKey());
+        System.out.println("\n");
+        test1(keyPair);
+        System.out.println("\n");
+        test2(keyPair);
+        System.out.println("\n");
+    }
+
+    /**
+     * 公钥加密私钥解密
+     */
+    private static void test1(RsaKeyPair keyPair) throws Exception {
+        System.out.println("***************** 公钥加密私钥解密开始 *****************");
+        String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
+        String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
+        System.out.println("加密前:" + RsaUtils.SRC);
+        System.out.println("加密后:" + text1);
+        System.out.println("解密后:" + text2);
+        if (RsaUtils.SRC.equals(text2)) {
+            System.out.println("解密字符串和原始字符串一致,解密成功");
+        } else {
+            System.out.println("解密字符串和原始字符串不一致,解密失败");
+        }
+        System.out.println("***************** 公钥加密私钥解密结束 *****************");
+    }
+
+    /**
+     * 私钥加密公钥解密
+     * @throws Exception /
+     */
+    private static void test2(RsaKeyPair keyPair) throws Exception {
+        System.out.println("***************** 私钥加密公钥解密开始 *****************");
+        String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);
+        String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
+        System.out.println("加密前:" + RsaUtils.SRC);
+        System.out.println("加密后:" + text1);
+        System.out.println("解密后:" + text2);
+        if (RsaUtils.SRC.equals(text2)) {
+            System.out.println("解密字符串和原始字符串一致,解密成功");
+        } else {
+            System.out.println("解密字符串和原始字符串不一致,解密失败");
+        }
+        System.out.println("***************** 私钥加密公钥解密结束 *****************");
+    }
+
+    /**
+     * 公钥解密
+     *
+     * @param publicKeyText 公钥
+     * @param text 待解密的信息
+     * @return /
+     * @throws Exception /
+     */
+    public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
+        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, publicKey);
+        byte[] result = cipher.doFinal(Base64.decodeBase64(text));
+        return new String(result);
+    }
+
+    /**
+     * 私钥加密
+     *
+     * @param privateKeyText 私钥
+     * @param text 待加密的信息
+     * @return /
+     * @throws Exception /
+     */
+    public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
+        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
+        byte[] result = cipher.doFinal(text.getBytes());
+        return Base64.encodeBase64String(result);
+    }
+
+    /**
+     * 私钥解密
+     *
+     * @param privateKeyText 私钥
+     * @param text 待解密的文本
+     * @return /
+     * @throws Exception /
+     */
+    public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
+        PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, privateKey);
+        byte[] result = cipher.doFinal(Base64.decodeBase64(text));
+        return new String(result);
+    }
+
+    /**
+     * 公钥加密
+     *
+     * @param publicKeyText 公钥
+     * @param text 待加密的文本
+     * @return /
+     */
+    public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
+        X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+        byte[] result = cipher.doFinal(text.getBytes());
+        return Base64.encodeBase64String(result);
+    }
+
+    /**
+     * 构建RSA密钥对
+     *
+     * @return /
+     * @throws NoSuchAlgorithmException /
+     */
+    public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(1024);
+        KeyPair keyPair = keyPairGenerator.generateKeyPair();
+        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+        String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
+        String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
+        return new RsaKeyPair(publicKeyString, privateKeyString);
+    }
+
+
+    /**
+     * RSA密钥对对象
+     */
+    public static class RsaKeyPair {
+
+        private final String publicKey;
+        private final String privateKey;
+
+        public RsaKeyPair(String publicKey, String privateKey) {
+            this.publicKey = publicKey;
+            this.privateKey = privateKey;
+        }
+
+        public String getPublicKey() {
+            return publicKey;
+        }
+
+        public String getPrivateKey() {
+            return privateKey;
+        }
+
+    }
+}

+ 101 - 0
easydo-common/src/main/java/easydo/technology/utils/SecurityUtils.java

@@ -0,0 +1,101 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+import java.util.List;
+
+import com.alibaba.fastjson.JSONObject;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+import easydo.technology.exception.BadRequestException;
+import easydo.technology.utils.enums.DataScopeEnum;
+
+public class SecurityUtils {
+
+    /**
+     * 获取当前登录的用户
+     *
+     * @return UserDetails
+     */
+    public static UserDetails getCurrentUser() {
+        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null) {
+            throw new BadRequestException(HttpStatus.UNAUTHORIZED, "当前登录状态过期");
+        }
+        if (authentication.getPrincipal() instanceof UserDetails) {
+            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
+            UserDetailsService userDetailsService = SpringContextHolder.getBean(UserDetailsService.class);
+            return userDetailsService.loadUserByUsername(userDetails.getUsername());
+        }
+        throw new BadRequestException(HttpStatus.UNAUTHORIZED, "找不到当前登录的信息");
+    }
+
+    /**
+     * 获取系统用户名称
+     *
+     * @return 系统用户名称
+     */
+    public static String getCurrentUsername() {
+        final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null) {
+            throw new BadRequestException(HttpStatus.UNAUTHORIZED, "当前登录状态过期");
+        }
+        if (authentication.getPrincipal() instanceof UserDetails) {
+            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
+            return userDetails.getUsername();
+        }
+        throw new BadRequestException(HttpStatus.UNAUTHORIZED, "找不到当前登录的信息");
+    }
+
+    /**
+     * 获取系统用户ID
+     *
+     * @return 系统用户ID
+     */
+    public static Long getCurrentUserId() {
+        UserDetails userDetails = getCurrentUser();
+        return JSONObject.parseObject(JSONObject.toJSONString(userDetails)).getJSONObject("user").getLong("id");
+    }
+
+    /**
+     * 获取当前用户的数据权限
+     *
+     * @return /
+     */
+    public static List<Long> getCurrentUserDataScope() {
+        UserDetails userDetails = getCurrentUser();
+        return JSONObject.parseObject(JSONObject.toJSONString(userDetails)).getJSONObject("user")
+                .getJSONArray("dataScopes").toJavaList(Long.class);
+    }
+
+    /**
+     * 获取数据权限级别
+     *
+     * @return 级别
+     */
+    public static String getDataScopeType() {
+        List<Long> dataScopes = getCurrentUserDataScope();
+        if (dataScopes.size() != 0) {
+            return "";
+        }
+        return DataScopeEnum.ALL.getValue();
+    }
+}

+ 145 - 0
easydo-common/src/main/java/easydo/technology/utils/SpringContextHolder.java

@@ -0,0 +1,145 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.env.Environment;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Jie
+ * @date 2019-01-07
+ */
+@Slf4j
+public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
+
+    private static ApplicationContext applicationContext = null;
+    private static final List<CallBack> CALL_BACKS = new ArrayList<>();
+    private static boolean addCallback = true;
+
+    /**
+     * 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。
+     * 在SpringContextHolder 初始化后,进行回调使用
+     *
+     * @param callBack 回调函数
+     */
+    public synchronized static void addCallBacks(CallBack callBack) {
+        if (addCallback) {
+            SpringContextHolder.CALL_BACKS.add(callBack);
+        } else {
+            log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());
+            callBack.executor();
+        }
+    }
+
+    /**
+     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) {
+        assertContextInjected();
+        return (T) applicationContext.getBean(name);
+    }
+
+    /**
+     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+     */
+    public static <T> T getBean(Class<T> requiredType) {
+        assertContextInjected();
+        return applicationContext.getBean(requiredType);
+    }
+
+    /**
+     * 获取SpringBoot 配置信息
+     *
+     * @param property     属性key
+     * @param defaultValue 默认值
+     * @param requiredType 返回类型
+     * @return /
+     */
+    public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {
+        T result = defaultValue;
+        try {
+            result = getBean(Environment.class).getProperty(property, requiredType);
+        } catch (Exception ignored) {}
+        return result;
+    }
+
+    /**
+     * 获取SpringBoot 配置信息
+     *
+     * @param property 属性key
+     * @return /
+     */
+    public static String getProperties(String property) {
+        return getProperties(property, null, String.class);
+    }
+
+    /**
+     * 获取SpringBoot 配置信息
+     *
+     * @param property     属性key
+     * @param requiredType 返回类型
+     * @return /
+     */
+    public static <T> T getProperties(String property, Class<T> requiredType) {
+        return getProperties(property, null, requiredType);
+    }
+
+    /**
+     * 检查ApplicationContext不为空.
+     */
+    private static void assertContextInjected() {
+        if (applicationContext == null) {
+            throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
+                    ".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
+        }
+    }
+
+    /**
+     * 清除SpringContextHolder中的ApplicationContext为Null.
+     */
+    private static void clearHolder() {
+        log.debug("清除SpringContextHolder中的ApplicationContext:"
+                + applicationContext);
+        applicationContext = null;
+    }
+
+    @Override
+    public void destroy() {
+        SpringContextHolder.clearHolder();
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        if (SpringContextHolder.applicationContext != null) {
+            log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
+        }
+        SpringContextHolder.applicationContext = applicationContext;
+        if (addCallback) {
+            for (CallBack callBack : SpringContextHolder.CALL_BACKS) {
+                callBack.executor();
+            }
+            CALL_BACKS.clear();
+        }
+        SpringContextHolder.addCallback = false;
+    }
+}

+ 449 - 0
easydo-common/src/main/java/easydo/technology/utils/StringUtil.java

@@ -0,0 +1,449 @@
+package easydo.technology.utils;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.*;
+import java.util.stream.IntStream;
+
+/**
+ * 字符串工具类
+ * 
+ * @author Ready 2012-10-17
+ */
+public class StringUtil {
+
+    /**
+     * 如果字符串为空,则返回空字符""
+     *
+     * @param str
+     * @return
+     */
+    public static String stringNullToEmpty(String str) {
+        return str == null ? "" : str;
+    }
+
+    /**
+     * 如果字符串为空,则返回null
+     *
+     * @param str
+     * @return
+     */
+    public static String stringEmptyToNull(String str) {
+        if (str == null)
+            return null;
+        return "".equals(str.trim()) ? null : str.trim();
+    }
+
+    public static String getPre(int count, String c) {
+        StringBuilder result = new StringBuilder("");
+        for (int i = 0; i < count; i++) {
+            result.append(c);
+        }
+        return result.toString();
+    }
+
+    public static String disponseStr(String str) {
+        if (str != null) {
+            return str.trim();
+        }
+        return str;
+    }
+
+    /**
+     * 是否为空,如果为null或者"",返回false,不为空返回true
+     *
+     * @param target
+     * @return
+     */
+    public static boolean isNotEmpty(Object target) {
+        if (target == null || "".equals(target.toString().trim())
+                || "null".equals(target.toString().toLowerCase().trim())) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 判断字符串是否为空,如果为空返回true
+     *
+     * @param target
+     * @return
+     */
+    public static boolean isEmpty(Object target) {
+        return !isNotEmpty(target);
+    }
+
+    /**
+     * @param length是随机数长度
+     * @return String
+     * @throws 无
+     * @author jchen.Kevin
+     * @date 2015年04月04日
+     * @description 生成长度为length的随机数
+     * @version 1.0.0
+     */
+    public static String getRandom(int length) {
+        if (length < 1) {
+            return null;
+        }
+        StringBuffer str = new StringBuffer();
+        Random random = new Random();
+        for (int i = 0; i < length; i++) {
+            str.append(String.valueOf(random.nextInt(10)));
+        }
+        return str.toString();
+    }
+
+    /**
+     * @param length是验证码长度
+     * @return String
+     * @throws 无
+     * @author lkang
+     * @date 2014年5月27日
+     * @description 生成长度为length的16进制字符串验证码
+     * @version 1.0.0
+     */
+    public static String getValidateString(int length) {
+        /*
+         * 以时间为随机种子
+         */
+        Date d = new Date();
+        long lseed = d.getTime();
+        StringBuffer ramdomCode = new StringBuffer();
+        Random r = new Random(lseed);
+
+        /*
+         * 产生16进制验证码字符串
+         */
+        for (int i = 0; i < length; i++) {
+            ramdomCode.append(Integer.toHexString(r.nextInt(15)));
+        }
+        return ramdomCode.toString();
+    }
+
+    /**
+     * @param length是验证码长度
+     * @return String
+     * @throws 无
+     * @author lkang
+     * @date 2014年5月27日
+     * @description 生成长度为length的16进制数字验证码
+     * @version 1.0.0
+     */
+    public static String getValidateNumber(int length) {
+        /*
+         * 以时间为随机种子
+         */
+        Date d = new Date();
+        long lseed = d.getTime();
+        StringBuffer ramdomCode = new StringBuffer();
+        Random r = new Random(lseed);
+
+        /*
+         * 产生数字验证码
+         */
+        for (int i = 0; i < length; i++) {
+            ramdomCode.append(Integer.toString(r.nextInt(9)));
+        }
+        return ramdomCode.toString();
+    }
+
+    /**
+     * @param source     要填充的字符串
+     * @param length     字符串填充后满足的长度
+     * @param fillString 填充物
+     * @return 填充后的字符串
+     * @throws
+     * @author yldong.water
+     * @date 2014年8月25日
+     * @description 填充特定字符串以满足指定长度
+     * @version 1.0.0
+     */
+    public static String fill(String source, int length, String fillString) {
+        if (isNotEmpty(source)) {
+            int sourceLength = source.length();
+            if (sourceLength < length) {
+                source = getPre(length - sourceLength, fillString) + source;
+            }
+        } else {
+            source = getPre(length, fillString);
+        }
+        return source;
+    }
+
+    /**
+     * 格式化文件大小
+     *
+     * @param target
+     * @return
+     */
+    public static String formatFileSize(double size) {
+        size /= 1024;
+        if (size >= 1024) {
+            size /= 1024;
+            double a = Math.round(size * 100);
+            return a / 100 + "MB";
+        } else {
+            double a = Math.round(size * 100);
+            return a / 100 + "KB";
+        }
+    }
+
+    /**
+     * 校验中英文的长度
+     *
+     * @param target
+     * @return
+     */
+    public static int lenString(String str) {
+        if (isNotEmpty(str)) {
+            int l = 0;
+            for (int i = 0; i < str.length(); i++) {
+                if (str.charAt(i) < 255) {
+                    l++;
+                } else {
+                    l += 2;
+                }
+            }
+            return l;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * 截取字符串
+     *
+     * @param target
+     * @return
+     */
+    public static String splitString(String str, int len, boolean flag) {
+        if (isNotEmpty(str)) {
+            if (lenString(str) > len) {
+                int l = 0;
+                int splitLen = 0;
+                for (int i = 0; i < str.length(); i++) {
+                    if (str.charAt(i) < 255) {
+                        l++;
+                    } else {
+                        l += 2;
+                    }
+                    if (l >= len) {
+                        splitLen = i;
+                        break;
+                    }
+                }
+                if (flag) {
+                    return str.substring(0, splitLen + 1);
+                } else {
+                    return str.substring(0, splitLen + 1) + "...";
+                }
+            } else {
+                return str;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @param String
+     * @return boolean
+     * @author jchen.Kevin
+     * @date 2015年01月28日
+     * @description 判断是否是固定电话
+     * @version 1.0.0
+     */
+    public static boolean isTel(String str) {
+        return str
+                .matches("(^(\\d{3,4}-)?\\d{7,8})$|(13[0-9]{9})|(1[0-9]{10})");
+    }
+
+    /**
+     * @param String
+     * @return boolean
+     * @author jchen.Kevin
+     * @date 2015年01月28日
+     * @description 判断是否是手机号码
+     * @version 1.0.0
+     */
+    public static boolean isPhone(String str) {
+        return str.matches("^1[0-9][0-9]\\d{8}$");
+    }
+
+    /**
+     * @param String
+     * @return boolean
+     * @author jchen.Kevin
+     * @date 2015年01月28日
+     * @description 判断是否是邮箱
+     * @version 1.0.0
+     */
+    public static boolean isEmail(String str) {
+        return str
+                .matches("^[A-Za-z\\d]+([-_.][A-Za-z\\d]+)*@([A-Za-z\\d]+[-.])+[A-Za-z\\d]{2,5}$");
+    }
+
+    /**
+     * @param String
+     * @return boolean
+     * @author jchen.Kevin
+     * @date 2015年01月28日
+     * @description 判断是否是中国邮政编码(6位)
+     * @version 1.0.0
+     */
+    public static boolean isZipCode(String str) {
+        return str.matches("^[1-9][0-9]{5}$");
+    }
+
+    /**
+     * @param String
+     * @return boolean
+     * @author jchen.Kevin
+     * @date 2015年01月28日
+     * @description 判断是否是腾讯QQ号
+     * @version 1.0.0
+     */
+    public static boolean isQQ(String str) {
+        return str.matches("[1-9][0-9]{4,}");
+    }
+
+    /**
+     * @param String
+     * @return boolean
+     * @author jchen.Kevin
+     * @date 2015年01月28日
+     * @description 判断是否是身份证(15位或18位)
+     * @version 1.0.0
+     */
+    public static boolean isIdcard(String str) {
+        return str.matches("\\d{15}|\\d{18}");
+    }
+
+
+    /*
+     * 得到两个数组中相同元素
+     */
+    public static Set<Integer> getSames(Integer[] a, Integer[] b) {
+        Set<Integer> sames = new HashSet<Integer>();
+        Set<Integer> set = new HashSet<Integer>(Arrays.asList(a));
+
+        for (Integer i : b) {
+            if (!set.add(i))
+                sames.add(i);
+        }
+
+        return sames;
+    }
+
+    /*
+     * 得到两个数组中相同元素
+     */
+    public static Set<Integer> getDefferent(Integer[] a, Integer[] b) {
+        Set<Integer> defferents = new HashSet<Integer>();
+        Set<Integer> set = new HashSet<Integer>(Arrays.asList(a));
+
+        for (Integer i : b) {
+            if (set.add(i))
+                defferents.add(i);
+        }
+
+        return defferents;
+    }
+
+    /*
+     * String数组转int数组
+     */
+    public static Integer[] StringToInt(String[] stringArr) {
+        Integer[] intArr = new Integer[stringArr.length];
+        for (int i = 0; i < stringArr.length; i++) {
+            intArr[i] = Integer.parseInt(stringArr[i]);
+        }
+        return intArr;
+    }
+
+    /**
+     * 使用 Java 8 Stream API 的替代实现
+     */
+    public static Object getNextFieldValueStream(JSONObject json, String path) {
+        try {
+            String[] keys = path.split("\\.");
+
+            // 使用reduce来处理嵌套访问
+            Optional<Object> result = Arrays.stream(keys)
+                    .reduce(Optional.of(json),
+                            (currentOpt, key) -> currentOpt
+                                    .filter(JSONObject.class::isInstance)
+                                    .map(JSONObject.class::cast)
+                                    .flatMap(obj -> Optional.ofNullable(obj.get(key))),
+                            (opt1, opt2) -> opt2 // 在并行流中合并,但这里不需要
+                    );
+
+            return result.orElse(null);
+
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public static boolean setNextFieldValueStream(JSONObject json, String path, Object newValue) {
+        try {
+            String[] keys = path.split("\\.");
+
+            if (keys.length == 0) {
+                return false;
+            }
+            // 获取最后一个键之前的对象
+            String[] parentKeys = Arrays.copyOfRange(keys, 0, keys.length - 1);
+            String lastKey = keys[keys.length - 1];
+            // 找到父级对象
+            Optional<Object> parentOpt = Arrays.stream(parentKeys)
+                    .reduce(Optional.of(json),
+                            (currentOpt, key) -> currentOpt
+                                    .filter(JSONObject.class::isInstance)
+                                    .map(JSONObject.class::cast)
+                                    .flatMap(obj -> Optional.ofNullable(obj.get(key))),
+                            (opt1, opt2) -> opt2);
+
+            // 如果父级对象存在且是JSONObject,则设置新值
+            if (parentOpt.isPresent() && parentOpt.get() instanceof JSONObject) {
+                JSONObject parent = (JSONObject) parentOpt.get();
+                parent.put(lastKey, newValue);
+                return true;
+            }
+            return false;
+
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    public static String extractNumberBeforeHdLive(String url) {
+        String target = ".hd.live";
+
+        // 查找 ".hd.live" 的位置
+        int hdLiveIndex = url.indexOf(target);
+        if (hdLiveIndex == -1) {
+            return null; // 未找到 ".hd.live"
+        }
+
+        // 从 ".hd.live" 开始向前查找最近的 '/'
+        int lastSlashBeforeHdLive = url.lastIndexOf('/', hdLiveIndex - 1);
+        if (lastSlashBeforeHdLive == -1) {
+            return null; // 在所需位置之前未找到 '/'
+        }
+
+        // 截取两个位置之间的子字符串(从斜杠后开始,到 ".hd.live" 前结束)
+        String numberStr = url.substring(lastSlashBeforeHdLive + 1, hdLiveIndex);
+
+        // 可选:验证截取的部分是否全为数字
+        if (!numberStr.matches("\\d+")) {
+            return null; // 包含非数字字符
+        }
+
+        return numberStr;
+    }
+}

+ 268 - 0
easydo-common/src/main/java/easydo/technology/utils/StringUtils.java

@@ -0,0 +1,268 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Calendar;
+import java.util.Date;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.lionsoul.ip2region.DataBlock;
+import org.lionsoul.ip2region.DbConfig;
+import org.lionsoul.ip2region.DbSearcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import eu.bitwalker.useragentutils.Browser;
+import eu.bitwalker.useragentutils.UserAgent;
+
+/**
+ * @author Zheng Jie
+ * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
+ */
+public class StringUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(StringUtils.class);
+    private static boolean ipLocal = false;
+    private static File file = null;
+    private static DbConfig config;
+    private static final char SEPARATOR = '_';
+    private static final String UNKNOWN = "unknown";
+
+    static {
+        SpringContextHolder.addCallBacks(() -> {
+            StringUtils.ipLocal = SpringContextHolder.getProperties("ip.local-parsing", false, Boolean.class);
+            if (ipLocal) {
+                /*
+                 * 此文件为独享 ,不必关闭
+                 */
+                // String path = "ip2region/ip2region.db";
+                // String name = "ip2region.db";
+                try {
+                    config = new DbConfig();
+                    // file = FileUtil.inputStreamToFile(new ClassPathResource(path).getInputStream(), name);
+                } catch (Exception e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+        });
+    }
+
+    /**
+     * 驼峰命名法工具
+     *
+     * @return toCamelCase(" hello_world ") == "helloWorld"
+     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
+     * toUnderScoreCase("helloWorld") = "hello_world"
+     */
+    public static String toCamelCase(String s) {
+        if (s == null) {
+            return null;
+        }
+
+        s = s.toLowerCase();
+
+        StringBuilder sb = new StringBuilder(s.length());
+        boolean upperCase = false;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+
+            if (c == SEPARATOR) {
+                upperCase = true;
+            } else if (upperCase) {
+                sb.append(Character.toUpperCase(c));
+                upperCase = false;
+            } else {
+                sb.append(c);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * 驼峰命名法工具
+     *
+     * @return toCamelCase(" hello_world ") == "helloWorld"
+     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
+     * toUnderScoreCase("helloWorld") = "hello_world"
+     */
+    public static String toCapitalizeCamelCase(String s) {
+        if (s == null) {
+            return null;
+        }
+        s = toCamelCase(s);
+        return s.substring(0, 1).toUpperCase() + s.substring(1);
+    }
+
+    /**
+     * 驼峰命名法工具
+     *
+     * @return toCamelCase(" hello_world ") == "helloWorld"
+     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
+     * toUnderScoreCase("helloWorld") = "hello_world"
+     */
+    static String toUnderScoreCase(String s) {
+        if (s == null) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        boolean upperCase = false;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+
+            boolean nextUpperCase = true;
+
+            if (i < (s.length() - 1)) {
+                nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
+            }
+
+            if ((i > 0) && Character.isUpperCase(c)) {
+                if (!upperCase || !nextUpperCase) {
+                    sb.append(SEPARATOR);
+                }
+                upperCase = true;
+            } else {
+                upperCase = false;
+            }
+
+            sb.append(Character.toLowerCase(c));
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * 获取ip地址
+     */
+    public static String getIp(HttpServletRequest request) {
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        String comma = ",";
+        String localhost = "127.0.0.1";
+        if (ip.contains(comma)) {
+            ip = ip.split(",")[0];
+        }
+        if (localhost.equals(ip)) {
+            // 获取本机真正的ip地址
+            try {
+                ip = InetAddress.getLocalHost().getHostAddress();
+            } catch (UnknownHostException e) {
+                log.error(e.getMessage(), e);
+            }
+        }
+        return ip;
+    }
+
+    /**
+     * 根据ip获取详细地址
+     */
+    public static String getCityInfo(String ip) {
+        return ip;
+//        if (ipLocal) {
+//            return getLocalCityInfo(ip);
+//        } else {
+//            return getHttpCityInfo(ip);
+//        }
+    }
+
+    /**
+     * 根据ip获取详细地址
+     */
+    public static String getHttpCityInfo(String ip) {
+        // String api = String.format(ElAdminConstant.Url.IP_URL, ip);
+        // JSONObject object = JSONUtil.parseObj(HttpUtil.get(api));
+        // return object.get("addr", String.class);
+        return "";
+    }
+
+    /**
+     * 根据ip获取详细地址
+     */
+    public static String getLocalCityInfo(String ip) {
+        try {
+            DataBlock dataBlock = new DbSearcher(config, file.getPath())
+                    .binarySearch(ip);
+            String region = dataBlock.getRegion();
+            String address = region.replace("0|", "");
+            char symbol = '|';
+            if (address.charAt(address.length() - 1) == symbol) {
+                address = address.substring(0, address.length() - 1);
+            }
+            return address.equals(ElAdminConstant.REGION) ? "内网IP" : address;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return "";
+    }
+
+    public static String getBrowser(HttpServletRequest request) {
+        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
+        Browser browser = userAgent.getBrowser();
+        return browser.getName();
+    }
+
+    /**
+     * 获得当天是周几
+     */
+    public static String getWeekDay() {
+        String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(new Date());
+
+        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
+        if (w < 0) {
+            w = 0;
+        }
+        return weekDays[w];
+    }
+
+    /**
+     * 获取当前机器的IP
+     *
+     * @return /
+     */
+    public static String getLocalIp() {
+        InetAddress addr;
+        try {
+            addr = InetAddress.getLocalHost();
+        } catch (UnknownHostException e) {
+            return "unknown";
+        }
+        byte[] ipAddr = addr.getAddress();
+        StringBuilder ipAddrStr = new StringBuilder();
+        for (int i = 0; i < ipAddr.length; i++) {
+            if (i > 0) {
+                ipAddrStr.append(".");
+            }
+            ipAddrStr.append(ipAddr[i] & 0xFF);
+        }
+        return ipAddrStr.toString();
+    }
+}

+ 37 - 0
easydo-common/src/main/java/easydo/technology/utils/ThrowableUtil.java

@@ -0,0 +1,37 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * 异常工具 2019-01-06
+ * @author Zheng Jie
+ */
+public class ThrowableUtil {
+
+    /**
+     * 获取堆栈信息
+     */
+    public static String getStackTrace(Throwable throwable){
+        StringWriter sw = new StringWriter();
+        try (PrintWriter pw = new PrintWriter(sw)) {
+            throwable.printStackTrace(pw);
+            return sw.toString();
+        }
+    }
+}

+ 69 - 0
easydo-common/src/main/java/easydo/technology/utils/TranslatorUtil.java

@@ -0,0 +1,69 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+
+import com.alibaba.fastjson.JSONArray;
+
+/**
+ * @author Zheng Jie
+ * 翻译工具类
+ */
+public class TranslatorUtil {
+
+    public static String translate(String word){
+        try {
+            String url = "https://translate.googleapis.com/translate_a/single?" +
+                    "client=gtx&" +
+                    "sl=en" +
+                    "&tl=zh-CN" +
+                    "&dt=t&q=" + URLEncoder.encode(word, "UTF-8");
+
+            URL obj = new URL(url);
+            HttpURLConnection con = (HttpURLConnection) obj.openConnection();
+            con.setRequestProperty("User-Agent", "Mozilla/5.0");
+
+            BufferedReader in = new BufferedReader(
+                    new InputStreamReader(con.getInputStream()));
+            String inputLine;
+            StringBuilder response = new StringBuilder();
+
+            while ((inputLine = in.readLine()) != null) {
+                response.append(inputLine);
+            }
+            in.close();
+            return parseResult(response.toString());
+        }catch (Exception e){
+          return  word;
+        }
+    }
+
+    private static String parseResult(String inputJson){
+        
+        JSONArray jsonArray = JSONArray.parseArray(inputJson);
+        StringBuilder result = new StringBuilder();
+        for (Object o : jsonArray) {
+            result.append(((JSONArray) o).get(0).toString());
+        }
+        return result.toString();
+    }
+}

+ 46 - 0
easydo-common/src/main/java/easydo/technology/utils/ValidationUtil.java

@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils;
+
+import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
+
+import easydo.technology.exception.BadRequestException;
+
+/**
+ * 验证工具
+ * 
+ * @author Zheng Jie
+ * @date 2018-11-23
+ */
+public class ValidationUtil {
+
+    /**
+     * 验证空
+     */
+    public static void isNull(Object obj, String entity, String parameter, Object value) {
+        if (obj == null) {
+            String msg = entity + " 不存在: " + parameter + " is " + value;
+            throw new BadRequestException(msg);
+        }
+    }
+
+    /**
+     * 验证是否为邮箱
+     */
+    public static boolean isEmail(String email) {
+        return new EmailValidator().isValid(email, null);
+    }
+}

+ 50 - 0
easydo-common/src/main/java/easydo/technology/utils/enums/CodeBiEnum.java

@@ -0,0 +1,50 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * <p>
+ * 验证码业务场景
+ * </p>
+ * @author Zheng Jie
+ * @date 2020-05-02
+ */
+@Getter
+@AllArgsConstructor
+public enum CodeBiEnum {
+
+    /* 旧邮箱修改邮箱 */
+    ONE(1, "旧邮箱修改邮箱"),
+
+    /* 通过邮箱修改密码 */
+    TWO(2, "通过邮箱修改密码");
+
+    private final Integer code;
+    private final String description;
+
+    public static CodeBiEnum find(Integer code) {
+        for (CodeBiEnum value : CodeBiEnum.values()) {
+            if (code.equals(value.getCode())) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+}

+ 46 - 0
easydo-common/src/main/java/easydo/technology/utils/enums/CodeEnum.java

@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * <p>
+ * 验证码业务场景对应的 Redis 中的 key
+ * </p>
+ * @author Zheng Jie
+ * @date 2020-05-02
+ */
+@Getter
+@AllArgsConstructor
+public enum CodeEnum {
+
+    /* 通过手机号码重置邮箱 */
+    PHONE_RESET_EMAIL_CODE("phone_reset_email_code_", "通过手机号码重置邮箱"),
+
+    /* 通过旧邮箱重置邮箱 */
+    EMAIL_RESET_EMAIL_CODE("email_reset_email_code_", "通过旧邮箱重置邮箱"),
+
+    /* 通过手机号码重置密码 */
+    PHONE_RESET_PWD_CODE("phone_reset_pwd_code_", "通过手机号码重置密码"),
+
+    /* 通过邮箱重置密码 */
+    EMAIL_RESET_PWD_CODE("email_reset_pwd_code_", "通过邮箱重置密码");
+
+    private final String key;
+    private final String description;
+}

+ 53 - 0
easydo-common/src/main/java/easydo/technology/utils/enums/DataScopeEnum.java

@@ -0,0 +1,53 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * <p>
+ * 数据权限枚举
+ * </p>
+ * @author Zheng Jie
+ * @date 2020-05-07
+ */
+@Getter
+@AllArgsConstructor
+public enum DataScopeEnum {
+
+    /* 全部的数据权限 */
+    ALL("全部", "全部的数据权限"),
+
+    /* 自己部门的数据权限 */
+    THIS_LEVEL("本级", "自己部门的数据权限"),
+
+    /* 自定义的数据权限 */
+    CUSTOMIZE("自定义", "自定义的数据权限");
+
+    private final String value;
+    private final String description;
+
+    public static DataScopeEnum find(String val) {
+        for (DataScopeEnum dataScopeEnum : DataScopeEnum.values()) {
+            if (val.equals(dataScopeEnum.getValue())) {
+                return dataScopeEnum;
+            }
+        }
+        return null;
+    }
+
+}

+ 43 - 0
easydo-common/src/main/java/easydo/technology/utils/enums/MenuType.java

@@ -0,0 +1,43 @@
+/*
+ *  Copyright 2019-2020 Fang Jin Biao
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 菜单类型:目录、菜单、按钮
+ * 
+ * @author adyfang
+ * @date 2020年9月12日
+ */
+@Getter
+@AllArgsConstructor
+public enum MenuType {
+	FOLDER(0, "目录"), MENU(1, "菜单"), BUTTON(2, "按钮");
+
+	private final int value;
+	private final String description;
+
+	public static MenuType find(int code) {
+		for (MenuType value : MenuType.values()) {
+			if (code == value.getValue()) {
+				return value;
+			}
+		}
+		return null;
+	}
+}

+ 74 - 0
easydo-common/src/main/java/easydo/technology/utils/enums/RequestMethodEnum.java

@@ -0,0 +1,74 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author Zheng Jie
+ * @website https://el-admin.vip
+ * @description
+ * @date 2020-06-10
+ **/
+@Getter
+@AllArgsConstructor
+public enum RequestMethodEnum {
+
+    /**
+     * 搜寻 @AnonymousGetMapping
+     */
+    GET("GET"),
+
+    /**
+     * 搜寻 @AnonymousPostMapping
+     */
+    POST("POST"),
+
+    /**
+     * 搜寻 @AnonymousPutMapping
+     */
+    PUT("PUT"),
+
+    /**
+     * 搜寻 @AnonymousPatchMapping
+     */
+    PATCH("PATCH"),
+
+    /**
+     * 搜寻 @AnonymousDeleteMapping
+     */
+    DELETE("DELETE"),
+
+    /**
+     * 否则就是所有 Request 接口都放行
+     */
+    ALL("All");
+
+    /**
+     * Request 类型
+     */
+    private final String type;
+
+    public static RequestMethodEnum find(String type) {
+        for (RequestMethodEnum value : RequestMethodEnum.values()) {
+            if (type.equals(value.getType())) {
+                return value;
+            }
+        }
+        return ALL;
+    }
+}

+ 27 - 0
easydo-core/pom.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+    <parent>
+        <groupId>easydo.technology</groupId>
+        <artifactId>easydo</artifactId>
+        <version>v20220507</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>easydo-core</artifactId>
+    <version>v1.0</version>
+
+    <name>基础模块</name>
+    <packaging>jar</packaging>
+    <properties>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>easydo.technology</groupId>
+            <artifactId>easydo-common</artifactId>
+            <version>v20220507</version>
+        </dependency>
+    </dependencies>
+</project>

+ 996 - 0
easydo-core/src/main/java/easydo/technology/components/JdbcClient.java

@@ -0,0 +1,996 @@
+package easydo.technology.components;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import easydo.technology.AutoIncrementModel;
+import easydo.technology.annotation.NotTableField;
+import easydo.technology.annotation.TableKey;
+import easydo.technology.utils.MapUtil;
+import easydo.technology.utils.ResultSetUtil;
+import easydo.technology.utils.StringUtil;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+import java.lang.reflect.Field;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+//version_1.0 whz 20230725
+//version_2.0 whz 20230802 修复分页where条件ambiguous的问题
+//version_3.0 whz 20230830 修复字段里有关键字的问题
+//version_4.0 whz 20240119 修复修改和删除会更新整表的操作
+//version_4.1 whz 20240130 修复修改不能改字段为空的问题
+//version_4.2 whz 20240227 修复in中不能使用非String类型的字段问题
+//version_5.0 whz 20250818 兼容baomidou实体类的情况(目前如果tableId的字段名和列名不一致时,会出现报错)
+@Component
+public class JdbcClient {
+    @Resource
+    DataSource dataSource;
+
+    /**
+     * 处理连接
+     *
+     * @param connection
+     * @param statements
+     */
+    public void finallyExecute(Connection connection, Statement... statements) {
+        if (connection != null) {
+            try {
+                connection.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        if (statements.length > 0) {
+            for (Statement statement : statements) {
+                try {
+                    statement.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    private void finallyExecute(Statement statement) {
+        if (statement != null) {
+            try {
+                statement.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void rollback(Connection connection) {
+        try {
+            if (connection != null) {
+                connection.rollback();
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    /**
+     * 单表分页查询,含实体类
+     *
+     * @param map
+     * @param clazz
+     * @param <T>
+     * @return
+     * @throws Exception
+     */
+    public <T> Map<String, Object> getJdbcPage(Map<String, Object> map, Class<T> clazz, Connection... connections) throws Exception {
+        String tableName = ResultSetUtil.humpToLine(clazz.getSimpleName());
+        Map<String, Object> result = new HashMap<>();
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            String condition = ResultSetUtil.formatSqlCondition(map);
+            sql = "select * from " + tableName + condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+            List<T> resultList = ResultSetUtil.formatResultSetToList(resultSet, clazz);
+
+            map.remove("current");
+            map.remove("size");
+            map.remove("orderBy");
+            condition = ResultSetUtil.formatSqlCondition(map);
+            sql = "select count(1) from " + tableName + condition;
+            resultSet = statement.executeQuery(sql);
+            if (resultSet.next()) {
+                result.put("total", resultSet.getInt(1));
+                result.put("totalElements", resultSet.getInt(1));
+            }
+            result.put("records", resultList);
+            result.put("content", resultList);
+            return result;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public <T> Map<String, Object> getJdbcPageBySql(Map<String, Object> map, String viewSql, Class<T> clazz, Connection... connections) throws Exception {
+        Map<String, Object> result = new HashMap<>();
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            String condition = ResultSetUtil.formatSqlCondition(map);
+            sql = viewSql + condition;
+            sql = sql.replace("`", "");
+            ResultSet resultSet = statement.executeQuery(sql);
+            List<T> resultList = ResultSetUtil.formatResultSetToList(resultSet, clazz);
+
+            map.remove("current");
+            map.remove("size");
+            map.remove("orderBy");
+            condition = ResultSetUtil.formatSqlCondition(map);
+            // 找到第一个FROM的位置(避免子查询中的FROM)
+            int fromIndex = viewSql.toLowerCase().indexOf("from");
+
+            viewSql = "select count(1) " + viewSql.substring(fromIndex);
+            sql = viewSql + condition;
+            sql = sql.replace("`", "");
+            resultSet = statement.executeQuery(sql);
+            if (resultSet.next()) {
+                result.put("total", resultSet.getInt(1));
+                result.put("totalElements", resultSet.getInt(1));
+            }
+            result.put("records", resultList);
+            result.put("content", resultList);
+            return result;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 连表分页查询
+     *
+     * @param map
+     * @param listSql
+     * @return
+     * @throws Exception
+     */
+    public Map<String, Object> getJdbcPageViewMap(Map<String, Object> map, String listSql, Connection... connections) throws Exception {
+        Map<String, Object> result = new HashMap<>();
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            String condition = ResultSetUtil.formatSqlCondition(map);
+            String viewSql = "select v.* from (" + listSql + ") v";
+            sql = viewSql + condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+            List<Map<String, Object>> resultList = ResultSetUtil.formatResultSetToListMap(resultSet);
+
+            map.remove("current");
+            map.remove("size");
+            map.remove("orderBy");
+            condition = ResultSetUtil.formatSqlCondition(map);
+            String countSql = "select count(1) from (" + listSql + ") v";
+            sql = countSql + condition;
+            resultSet = statement.executeQuery(sql);
+            if (resultSet.next()) {
+                result.put("total", resultSet.getInt(1));
+                result.put("totalElements", resultSet.getInt(1));
+            }
+            result.put("records", resultList);
+            result.put("content", resultList);
+            return result;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 连表分页查询,含实体类
+     *
+     * @param map
+     * @param listSql
+     * @param clazz
+     * @param <T>
+     * @return
+     * @throws Exception
+     */
+    public <T> Map<String, Object> getJdbcPageView(Map<String, Object> map, String listSql, Class<T> clazz, Connection... connections) throws Exception {
+        Map<String, Object> result = new HashMap<>();
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            String condition = ResultSetUtil.formatSqlCondition(map);
+            String viewSql = "select v.* from (" + listSql + ") v";
+            sql = viewSql + condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+            List<T> resultList = ResultSetUtil.formatResultSetToList(resultSet, clazz);
+
+            map.remove("current");
+            map.remove("size");
+            map.remove("orderBy");
+            condition = ResultSetUtil.formatSqlCondition(map);
+            String countSql = "select count(1) from (" + listSql + ") v";
+            sql = countSql + condition;
+            resultSet = statement.executeQuery(sql);
+            if (resultSet.next()) {
+                result.put("total", resultSet.getInt(1));
+                result.put("totalElements", resultSet.getInt(1));
+            }
+            result.put("records", resultList);
+            result.put("content", resultList);
+            return result;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 单表,多表查询集合
+     *
+     * @param map
+     * @param listSql
+     * @return
+     * @throws Exception
+     */
+    public List<Map<String, Object>> getJdbcListMap(Map<String, Object> map, String listSql, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            String condition = ResultSetUtil.formatSqlCondition(map);
+            String viewSql = "select v.* from (" + listSql + ") v";
+            sql = viewSql + condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+            List<Map<String, Object>> resultList = ResultSetUtil.formatResultSetToListMap(resultSet);
+            return resultList;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 单表查询集合,含实体类
+     *
+     * @param map
+     * @param clazz
+     * @param <T>
+     * @return
+     * @throws Exception
+     */
+    public <T> List<T> getJdbcList(Map<String, Object> map, Class<T> clazz, Connection... connections) throws Exception {
+        String tableName = ResultSetUtil.humpToLine(clazz.getSimpleName());
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            String condition = ResultSetUtil.formatSqlCondition(map);
+            sql = "select * from " + tableName + condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+            List<T> resultList = ResultSetUtil.formatResultSetToList(resultSet, clazz);
+            return resultList;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public <T> List<T> getJdbcList(T object, Connection... connections) throws Exception {
+        String tableName = ResultSetUtil.humpToLine(object.getClass().getSimpleName());
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            Map<String, Object> map = MapUtil.objectToMap(object);
+            String condition = ResultSetUtil.formatSqlCondition(map);
+            sql = "select * from " + tableName + condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+            List<T> resultList = (List<T>) ResultSetUtil.formatResultSetToList(resultSet, object.getClass());
+            return resultList;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 多表查询集合,含实体类
+     *
+     * @param map
+     * @param listSql
+     * @param clazz
+     * @param <T>
+     * @return
+     * @throws Exception
+     */
+    @Deprecated
+    public <T> List<T> getJdbcList(Map<String, Object> map, String listSql, Class<T> clazz, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            String condition = ResultSetUtil.formatSqlCondition(map);
+            String viewSql = "select v.* from (" + listSql + ") v";
+            sql = viewSql + condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+            List<T> resultList = ResultSetUtil.formatResultSetToList(resultSet, clazz);
+            return resultList;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public <T> List<T> getJdbcListBySql(Map<String, Object> map,String sql, Class<T> clazz, Connection... connections) throws Exception {
+        String condition = ResultSetUtil.formatSelectSql(map, clazz);
+        Connection connection = null;
+        Statement statement = null;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql += condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+            List<T> resultList = ResultSetUtil.formatResultSetToList(resultSet, clazz);
+            return resultList;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+
+    /**
+     * 单表查询字段,含实体类
+     *
+     * @param obj
+     * @param <T>
+     * @return
+     * @throws Exception
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T getJdbcModel(T obj, Connection... connections) throws Exception {
+        String condition = ResultSetUtil.formatSelectSql(obj);
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+
+            return (T) ResultSetUtil.formatResultSetToModel(resultSet, obj.getClass());
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public <T> T getJdbcModelByMap(Map<String, Object> map, Class<T> clazz, Connection... connections) throws Exception {
+        String condition = ResultSetUtil.formatSelectSql(map, clazz);
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+            sql = condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+
+            return (T) ResultSetUtil.formatResultSetToModel(resultSet, clazz);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public <T> T getJdbcModelByMap(Map<String, Object> map, String sql, Class<T> clazz, Connection... connections) throws Exception {
+        String condition = ResultSetUtil.formatSelectSql(map, sql);
+        Connection connection = null;
+        Statement statement = null;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+            sql = condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+
+            return (T) ResultSetUtil.formatResultSetToModel(resultSet, clazz);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 单表根据id查询字段,含实体类
+     *
+     * @param obj
+     * @param <T>
+     * @return
+     * @throws Exception
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T getJdbcModelById(T obj, Connection... connections) throws Exception {
+        String condition = ResultSetUtil.formatSelectByIdSql(obj);
+
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = condition;
+            ResultSet resultSet = statement.executeQuery(sql);
+
+            return (T) ResultSetUtil.formatResultSetToModel(resultSet, obj.getClass());
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 新增
+     *
+     * @param model
+     * @throws Exception
+     */
+    public void jdbcInsert(Object model, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatInsertSql(model);
+
+            AutoIncrementModel incrementModel = ResultSetUtil.isExistAutoIncrement(model);
+            if (!incrementModel.getIsAutoIncrement()) {
+                statement.executeUpdate(sql);
+            } else {
+                statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
+
+                ResultSet generatedKeys = statement.getGeneratedKeys();
+                if (generatedKeys.next()) {
+                    long newId = generatedKeys.getLong(1);
+
+                    Field[] declaredFields = model.getClass().getDeclaredFields();
+                    for (Field field : declaredFields) {
+                        field.setAccessible(true);
+                        if (incrementModel.getFieldName().equals(field.getName())) {
+                            if (field.getType().getSimpleName().equals(Long.class.getSimpleName())) {
+                                field.set(model, newId);
+                            } else if (field.getType().getSimpleName().equals(Integer.class.getSimpleName())) {
+                                field.set(model, (int) newId);
+                            } else {
+                                field.set(model, String.valueOf(newId));
+                            }
+                        }
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public void jdbcInsertExcludeFile(Object model, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatInsertSql(model);
+            statement.executeUpdate(sql);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 修改
+     *
+     * @param model
+     * @param whereMap
+     * @throws Exception
+     */
+    public void jdbcUpdate(Object model, Map<String, Object> whereMap, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatUpdateSql(model, whereMap);
+            statement.executeUpdate(sql);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 根据id修改
+     *
+     * @param model
+     * @throws Exception
+     */
+    public void jdbcUpdateById(Object model, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatUpdateByIdSql(model);
+            statement.executeUpdate(sql);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public void jdbcUpdateByIdExcludeFile(Object model, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatUpdateByIdSql(model);
+            statement.executeUpdate(sql);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 根据id删除
+     *
+     * @param model
+     * @throws Exception
+     */
+
+
+    public void jdbcRemoveById(Object model, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatRemoveByIdSql(model);
+            statement.executeUpdate(sql);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public void jdbcRemoveByIdExcludeFile(Object model, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatRemoveByIdSql(model);
+            statement.executeUpdate(sql);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public void jdbcRemove(Object model, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatRemoveSql(model);
+            statement.executeUpdate(sql);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 统计条数
+     *
+     * @param obj
+     * @return
+     * @throws Exception
+     */
+    public int getJdbcCount(Object obj, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatSelectCountSql(obj);
+            ResultSet resultSet = statement.executeQuery(sql);
+            if (resultSet.next()) {
+                return resultSet.getInt(1);
+            }
+            return 0;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public <T> int getJdbcCountByMap(Map<String, Object> map, Class<T> clazz, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            sql = ResultSetUtil.formatSelectCountSql(clazz, map);
+            ResultSet resultSet = statement.executeQuery(sql);
+            if (resultSet.next()) {
+                return resultSet.getInt(1);
+            }
+            return 0;
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    /**
+     * 批量新增,不包含文件上传
+     *
+     * @param models
+     * @throws Exception
+     */
+    public void jdbcInsertBatchExcludeFile(List models, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        String sql;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            for (Object item : models) {
+                statement.addBatch(ResultSetUtil.formatInsertSql(item));
+            }
+            statement.executeBatch();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+
+    public void jdbcUpdateBatchByIdExcludeFile(List models, Connection... connections) throws Exception {
+        Connection connection = null;
+        Statement statement = null;
+        try {
+            if (connections.length > 0) {
+                connection = connections[0];
+            } else {
+                connection = dataSource.getConnection();
+            }
+            statement = connection.createStatement();
+
+            for (Object model : models) {
+                statement.addBatch(ResultSetUtil.formatUpdateByIdSql(model));
+            }
+            statement.executeBatch();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connections.length == 0) {
+                finallyExecute(connection, statement);
+            } else {
+                finallyExecute(statement);
+            }
+        }
+    }
+}

+ 47 - 0
easydo-core/src/main/java/easydo/technology/enums/RedisKeyEnum.java

@@ -0,0 +1,47 @@
+package easydo.technology.enums;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum RedisKeyEnum {
+
+    KEY_ESIGN_TOKEN_KEY("esignTokenKey", "E签宝token"),
+    KEY_ESIGN_REFRESH_KEY("esignRefreshKey", "E签宝refreshToken"),
+    KEY_QDMETRO_LIVE_REDIS_KEY("QdMetro_live_redis_key_", "地铁大屏"),
+    KEY_EZVIZ_TOKEN_KEY("ezviz_token_key_", "萤石云token"),
+    KEY_WECHAT_TOKEN_KEY("wechat_token_key_", "微信公众平台token"),
+    KEY_ENERGY_WATER_METER_TOKEN("energy_water_meter_token", "能源管理(水电表)token"),
+    KEY_CAR_RINSE_SIDE_IMAGE("carRinse_side_image_", "车辆冲洗侧面摄像头抓拍图片"),
+    KEY_CF_GLD_MASTER_GET_TOKEN("cf_gld_master_get_token", "广联达主数据token"),
+    ;
+
+    RedisKeyEnum(String value, String comment) {
+        this.value = value;
+        this.comment = comment;
+    }
+
+    private static Map<String, RedisKeyEnum> typelookup = new HashMap<>();
+
+    static {
+        for (RedisKeyEnum baseEnum : RedisKeyEnum.values()) {
+            typelookup.put(baseEnum.value, baseEnum);
+        }
+    }
+
+    public static RedisKeyEnum forType(String type) {
+        return typelookup.get(type);
+    }
+
+    private String value;
+    private String comment;
+
+    public String getValue() {
+        return value;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+
+}

+ 22 - 0
easydo-logging/pom.xml

@@ -0,0 +1,22 @@
+<?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">
+    <parent>
+        <groupId>easydo.technology</groupId>
+        <artifactId>easydo</artifactId>
+        <version>v20220507</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>easydo-logging</artifactId>
+    <name>日志模块</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>easydo.technology</groupId>
+            <artifactId>easydo-common</artifactId>
+            <version>v20220507</version>
+        </dependency>
+    </dependencies>
+</project>

+ 42 - 0
easydo-logging/src/main/java/easydo/technology/annotation/Log.java

@@ -0,0 +1,42 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import easydo.technology.annotation.type.LogActionType;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Log {
+    String value() default "";
+
+    /**
+     * 是否启用
+     *
+     * @return
+     */
+    boolean enable() default true;
+
+    LogActionType type() default LogActionType.SELECT;
+}

+ 45 - 0
easydo-logging/src/main/java/easydo/technology/annotation/type/LogActionType.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package easydo.technology.annotation.type;
+
+/**
+ * @author: liaojinlong
+ * @date: 2020/6/11 19:47
+ * @apiNote: 日志类型
+ */
+
+public enum LogActionType {
+    /**
+     * 增删改查
+     */
+    ADD("新增"),
+    SELECT("查询"),
+    UPDATE("更新"),
+    DELETE("删除");
+    private String value;
+
+    LogActionType(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}

+ 65 - 0
easydo-logging/src/main/java/easydo/technology/domain/Log.java

@@ -0,0 +1,65 @@
+package easydo.technology.domain;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import org.springframework.beans.BeanUtils;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author jinjin
+ * @date 2020-09-27
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+@TableName("sys_log")
+public class Log implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "log_id", type = IdType.AUTO)
+    private Long id;
+
+    private String description;
+
+    private String logType;
+
+    private String method;
+
+    private String params;
+
+    private String requestIp;
+
+    private Long time;
+
+    private String username;
+
+    private String address;
+
+    private String browser;
+
+    private byte[] exceptionDetail;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    public Log(String logType, Long time) {
+        this.logType = logType;
+        this.time = time;
+    }
+
+    public void copyFrom(Log source) {
+        BeanUtils.copyProperties(source, this);
+    }
+}

+ 15 - 0
easydo-logging/src/main/java/easydo/technology/mapper/LogMapper.java

@@ -0,0 +1,15 @@
+package easydo.technology.mapper;
+
+import org.springframework.stereotype.Repository;
+
+import easydo.technology.base.CommonMapper;
+import easydo.technology.domain.Log;
+
+/**
+ * @author jinjin
+ * @date 2020-09-27
+ */
+@Repository
+public interface LogMapper extends CommonMapper<Log> {
+
+}

+ 106 - 0
easydo-logging/src/main/java/easydo/technology/rest/LogController.java

@@ -0,0 +1,106 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.rest;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import easydo.technology.annotation.Log;
+import easydo.technology.service.LogService;
+import easydo.technology.service.dto.LogQueryParam;
+import easydo.technology.utils.SecurityUtils;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author Zheng Jie
+ * @date 2018-11-24
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/logs")
+public class LogController {
+
+    private final LogService logService;
+
+    @Log("导出数据")
+    @GetMapping(value = "/download")
+    @PreAuthorize("@el.check()")
+    public void download(HttpServletResponse response, LogQueryParam criteria) throws IOException {
+        criteria.setLogType("INFO");
+        logService.download(logService.queryAll(criteria), response);
+    }
+
+    @Log("导出错误数据")
+    @GetMapping(value = "/error/download")
+    @PreAuthorize("@el.check()")
+    public void downloadErrorLog(HttpServletResponse response, LogQueryParam criteria) throws IOException {
+        criteria.setLogType("ERROR");
+        logService.download(logService.queryAll(criteria), response);
+    }
+
+    @GetMapping
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> query(LogQueryParam criteria, Pageable pageable) {
+        criteria.setLogType("INFO");
+        return new ResponseEntity<>(logService.queryAll(criteria, pageable), HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/user")
+    public ResponseEntity<Object> queryUserLog(LogQueryParam criteria, Pageable pageable) {
+        criteria.setLogType("INFO");
+        criteria.setBlurry(SecurityUtils.getCurrentUsername());
+        return new ResponseEntity<>(logService.queryAllByUser(criteria, pageable), HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/error")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> queryErrorLog(LogQueryParam criteria, Pageable pageable) {
+        criteria.setLogType("ERROR");
+        return new ResponseEntity<>(logService.queryAll(criteria, pageable), HttpStatus.OK);
+    }
+
+    @GetMapping(value = "/error/{id}")
+    public ResponseEntity<Object> queryErrorLogs(@PathVariable Long id) {
+        return new ResponseEntity<>(logService.findByErrDetail(id), HttpStatus.OK);
+    }
+
+    @DeleteMapping(value = "/del/error")
+    @Log("删除所有ERROR日志")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> delAllErrorLog() {
+        logService.delAllByError();
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @DeleteMapping(value = "/del/info")
+    @Log("删除所有INFO日志")
+    @PreAuthorize("@el.check()")
+    public ResponseEntity<Object> delAllInfoLog() {
+        logService.delAllByInfo();
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}

+ 94 - 0
easydo-logging/src/main/java/easydo/technology/service/LogService.java

@@ -0,0 +1,94 @@
+package easydo.technology.service;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.springframework.data.domain.Pageable;
+import org.springframework.scheduling.annotation.Async;
+
+import easydo.technology.base.CommonService;
+import easydo.technology.base.PageInfo;
+import easydo.technology.domain.Log;
+import easydo.technology.service.dto.LogQueryParam;
+import easydo.technology.service.dto.LogSmallDTO;
+
+/**
+ * @author jinjin
+ * @date 2020-09-27
+ */
+public interface LogService extends CommonService<Log> {
+
+    static final String CACHE_KEY = "log";
+
+    /**
+     * 查询数据分页
+     *
+     * @param query    条件
+     * @param pageable 分页参数
+     * @return PageInfo<Log>
+     */
+    Object queryAll(LogQueryParam query, Pageable pageable);
+
+    /**
+     * 查询所有数据不分页
+     *
+     * @param query 条件参数
+     * @return List<Log>
+     */
+    List<Log> queryAll(LogQueryParam query);
+
+    Log findById(Long id);
+
+    /**
+     * 查询用户日志
+     *
+     * @param criteria 查询条件
+     * @param pageable 分页参数
+     * @return -
+     */
+    PageInfo<LogSmallDTO> queryAllByUser(LogQueryParam criteria, Pageable pageable);
+
+    /**
+     * 保存日志数据
+     *
+     * @param username  用户
+     * @param browser   浏览器
+     * @param ip        请求IP
+     * @param joinPoint /
+     * @param log       日志实体
+     */
+    @Async
+    void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, Log log);
+
+    /**
+     * 查询异常详情
+     *
+     * @param id 日志ID
+     * @return Object
+     */
+    Object findByErrDetail(Long id);
+
+    /**
+     * 导出日志
+     *
+     * @param logs     待导出的数据
+     * @param response /
+     * @throws IOException /
+     */
+    void download(List<Log> logs, HttpServletResponse response) throws IOException;
+
+    boolean removeByLogType(String logType);
+
+    /**
+     * 删除所有错误日志
+     */
+    void delAllByError();
+
+    /**
+     * 删除所有INFO日志
+     */
+    void delAllByInfo();
+}

+ 47 - 0
easydo-logging/src/main/java/easydo/technology/service/dto/LogErrorDTO.java

@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.service.dto;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import lombok.Data;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-5-22
+ */
+@Data
+public class LogErrorDTO implements Serializable {
+
+    private Long id;
+
+    private String username;
+
+    private String description;
+
+    private String method;
+
+    private String params;
+
+    private String browser;
+
+    private String requestIp;
+
+    private String address;
+
+    private Date createTime;
+}

+ 40 - 0
easydo-logging/src/main/java/easydo/technology/service/dto/LogQueryCriteria.java

@@ -0,0 +1,40 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.service.dto;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+import easydo.technology.annotation.Query;
+import lombok.Data;
+
+/**
+ * 日志查询类
+ * @author Zheng Jie
+ * @date 2019-6-4 09:23:07
+ */
+@Data
+public class LogQueryCriteria {
+
+    @Query(blurry = "username,description,address,requestIp,method,params")
+    private String blurry;
+
+    @Query
+    private String logType;
+
+    @Query(type = Query.Type.BETWEEN)
+    private List<Timestamp> createTime;
+}

+ 31 - 0
easydo-logging/src/main/java/easydo/technology/service/dto/LogQueryParam.java

@@ -0,0 +1,31 @@
+package easydo.technology.service.dto;
+
+import java.util.Date;
+import java.util.List;
+
+import org.springframework.format.annotation.DateTimeFormat;
+
+import easydo.technology.annotation.Query;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+* @author jinjin
+* @date 2020-09-27
+*/
+@Getter
+@Setter
+public class LogQueryParam {
+
+    @Query(blurry = "username,description,address,requestIp,method,params")
+    private String blurry;
+
+    /** 精确 */
+    @Query
+    private String logType;
+
+    /** BETWEEN */
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @Query(type = Query.Type.BETWEEN)
+    private List<Date> createTime;
+}

+ 41 - 0
easydo-logging/src/main/java/easydo/technology/service/dto/LogSmallDTO.java

@@ -0,0 +1,41 @@
+/*
+ *  Copyright 2019-2020 Zheng Jie
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package easydo.technology.service.dto;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import lombok.Data;
+
+/**
+ * @author Zheng Jie
+ * @date 2019-5-22
+ */
+@Data
+public class LogSmallDTO implements Serializable {
+
+    private String description;
+
+    private String requestIp;
+
+    private Long time;
+
+    private String address;
+
+    private String browser;
+
+    private Date createTime;
+}

+ 181 - 0
easydo-logging/src/main/java/easydo/technology/service/impl/LogServiceImpl.java

@@ -0,0 +1,181 @@
+package easydo.technology.service.impl;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+// 默认不使用缓存
+//import org.springframework.cache.annotation.CacheConfig;
+//import org.springframework.cache.annotation.CacheEvict;
+//import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import ch.qos.logback.core.util.FileUtil;
+import easydo.technology.base.PageInfo;
+import easydo.technology.base.QueryHelpMybatisPlus;
+import easydo.technology.base.impl.CommonServiceImpl;
+import easydo.technology.domain.Log;
+import easydo.technology.mapper.LogMapper;
+import easydo.technology.service.LogService;
+import easydo.technology.service.dto.LogErrorDTO;
+import easydo.technology.service.dto.LogQueryParam;
+import easydo.technology.service.dto.LogSmallDTO;
+import easydo.technology.utils.ConvertUtil;
+import easydo.technology.utils.PageUtil;
+import easydo.technology.utils.ValidationUtil;
+import io.netty.util.internal.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author jinjin
+ * @date 2020-09-27
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+// @CacheConfig(cacheNames = LogService.CACHE_KEY)
+@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
+public class LogServiceImpl extends CommonServiceImpl<Log> implements LogService {
+
+    // private final RedisUtils redisUtils;
+    private final LogMapper logMapper;
+
+    @Override
+    public Object queryAll(LogQueryParam query, Pageable pageable) {
+        IPage<Log> page = PageUtil.toMybatisPage(pageable);
+        IPage<Log> pageList = logMapper.selectPage(page, QueryHelpMybatisPlus.getPredicate(query));
+        String status = "ERROR";
+        if (status.equals(query.getLogType())) {
+            return ConvertUtil.convertPage(pageList, LogErrorDTO.class);
+        }
+        return ConvertUtil.convertPage(pageList, Log.class);
+    }
+
+    @Override
+    public List<Log> queryAll(LogQueryParam query) {
+        return logMapper.selectList(QueryHelpMybatisPlus.getPredicate(query));
+    }
+
+    @Override
+    public PageInfo<LogSmallDTO> queryAllByUser(LogQueryParam query, Pageable pageable) {
+        IPage<Log> page = PageUtil.toMybatisPage(pageable);
+        IPage<Log> pageList = logMapper.selectPage(page, QueryHelpMybatisPlus.getPredicate(query));
+        return ConvertUtil.convertPage(pageList, LogSmallDTO.class);
+    }
+
+    @Override
+    // @Cacheable(key = "'id:' + #p0")
+    public Log findById(Long id) {
+        return logMapper.selectById(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean removeByLogType(String logType) {
+        UpdateWrapper<Log> wrapper = new UpdateWrapper<>();
+        wrapper.lambda().eq(Log::getLogType, logType);
+        return logMapper.delete(wrapper) > 0;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, Log log) {
+
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        easydo.technology.annotation.Log aopLog = method.getAnnotation(easydo.technology.annotation.Log.class);
+
+        // 方法路径
+        String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()";
+
+        StringBuilder params = new StringBuilder("{");
+        // 参数值
+        List<Object> argValues = new ArrayList<>(Arrays.asList(joinPoint.getArgs()));
+        // 参数名称
+        for (Object argValue : argValues) {
+            params.append(argValue).append(" ");
+        }
+        // 描述
+        if (log != null) {
+            log.setDescription(aopLog.value());
+        }
+        assert log != null;
+        log.setRequestIp(ip);
+
+        String loginPath = "login";
+        if (loginPath.equals(signature.getName())) {
+            try {
+                username = JSONObject.parseObject(JSONObject.toJSONString(argValues.get(0))).getString("username");
+            } catch (Exception e) {
+                LogServiceImpl.log.error(e.getMessage(), e);
+            }
+        }
+        // log.setAddress(StringUtils.getCityInfo(log.getRequestIp()));
+        log.setMethod(methodName);
+        log.setUsername(username);
+        log.setParams(params.toString() + " }");
+        log.setBrowser(browser);
+        if (log.getId() == null) {
+            logMapper.insert(log);
+        } else {
+            logMapper.updateById(log);
+        }
+    }
+
+    @Override
+    public Object findByErrDetail(Long id) {
+        Log log = findById(id);
+        ValidationUtil.isNull(log.getId(), "Log", "id", id);
+        byte[] details = log.getExceptionDetail();
+        // return Dict.create().set("exception", new String(details != null ? details :
+        // "".getBytes()));
+        return new String(details != null ? details : "".getBytes());
+    }
+
+    @Override
+    public void download(List<Log> logs, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (Log log : logs) {
+            Map<String, Object> map = new LinkedHashMap<>();
+            map.put("用户名", log.getUsername());
+            map.put("IP", log.getRequestIp());
+            map.put("IP来源", log.getAddress());
+            map.put("描述", log.getDescription());
+            map.put("浏览器", log.getBrowser());
+            map.put("请求耗时/毫秒", log.getTime());
+            map.put("异常详情", new String(
+                    log.getExceptionDetail() != null ? log.getExceptionDetail() : "".getBytes()));
+            map.put("创建日期", log.getCreateTime());
+            list.add(map);
+        }
+        // FileUtil.downloadExcel(list, response);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delAllByError() {
+        this.removeByLogType("ERROR");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delAllByInfo() {
+        this.removeByLogType("INFO");
+    }
+}

+ 118 - 0
easydo-mes/pom.xml

@@ -0,0 +1,118 @@
+<?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">
+    <parent>
+        <groupId>easydo.technology</groupId>
+        <artifactId>easydo</artifactId>
+        <version>v20220507</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>easydo-mes</artifactId>
+    <name>智能生产运管平台</name>
+    <packaging>jar</packaging>
+    <properties>
+        <jjwt.version>0.11.1</jjwt.version>
+        <jna.version>5.5.0</jna.version>
+        <shiro.version>1.2.4</shiro.version>
+    </properties>
+
+    <profiles>
+        <profile>
+            <id>prod</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+            </activation>
+            <properties>
+                <environment>prod</environment>
+            </properties>
+        </profile>
+        <profile>
+            <id>owntest</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <environment>owntest</environment>
+            </properties>
+        </profile>
+    </profiles>
+
+    <dependencies>
+
+        <!-- tools 模块包含了 common 和 logging 模块 -->
+        <dependency>
+            <groupId>easydo.technology</groupId>
+            <artifactId>easydo-logging</artifactId>
+            <version>v20220507</version>
+        </dependency>
+
+        <dependency>
+            <groupId>easydo.technology</groupId>
+            <artifactId>easydo-system</artifactId>
+            <version>v20220507</version>
+        </dependency>
+
+        <!-- log4j -->
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.17</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <version>1.4</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>8.5.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-stdlib</artifactId>
+            <version>1.8.20</version>
+        </dependency>
+    </dependencies>
+
+    <!-- 打包 -->
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes>
+                    <include>**/*.yml</include>
+                    <include>**/*.properties</include>
+                    <include>**/*.xml</include>
+                </includes>
+                <filtering>true</filtering>
+            </resource>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.yml</include>
+                    <include>**/*.properties</include>
+                    <include>**/*.xml</include>
+                </includes>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <skipTests>true</skipTests>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 26 - 0
easydo-mes/src/main/java/easydo/technology/MesApplication.java

@@ -0,0 +1,26 @@
+package easydo.technology;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import easydo.technology.utils.SpringContextHolder;
+
+@Configuration
+@SpringBootApplication
+@ComponentScan({ "com", "easydo", "zmo" })
+@EnableTransactionManagement
+public class MesApplication {
+
+    @Bean
+    public SpringContextHolder springContextHolder() {
+        return new SpringContextHolder();
+    }
+
+    public static void main(String[] args) {
+        SpringApplication.run(MesApplication.class, args);
+    }
+}

+ 33 - 0
easydo-mes/src/main/java/easydo/technology/config/AsyncConfig.java

@@ -0,0 +1,33 @@
+package easydo.technology.config;
+
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+
+@Configuration
+@EnableAsync
+public class AsyncConfig implements AsyncConfigurer {
+
+    @Override
+    @Bean(name = "EasyDoTaskExecutor")
+    public Executor getAsyncExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(5);
+        executor.setMaxPoolSize(10);
+        executor.setQueueCapacity(50);
+        executor.setThreadNamePrefix("Async-");
+        executor.initialize();
+        return executor;
+    }
+
+    @Override
+    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+        return new SimpleAsyncUncaughtExceptionHandler();
+    }
+}

+ 30 - 0
easydo-mes/src/main/java/easydo/technology/config/MinioConfig.java

@@ -0,0 +1,30 @@
+package easydo.technology.config;
+
+import io.minio.MinioClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MinioConfig {
+
+    @Value("${minio.endpoint}")
+    private String endpoint;
+
+    @Value("${minio.access-key}")
+    private String accessKey;
+
+    @Value("${minio.secret-key}")
+    private String secretKey;
+
+//    @Value("${minio.secure}")
+//    private Boolean secure;
+
+    @Bean
+    public MinioClient minioClient() {
+        return MinioClient.builder()
+                .endpoint(endpoint)
+                .credentials(accessKey, secretKey)
+                .build();
+    }
+}

+ 26 - 0
easydo-mes/src/main/java/easydo/technology/config/WebMvcConfig.java

@@ -0,0 +1,26 @@
+package easydo.technology.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.multipart.MultipartResolver;
+import org.springframework.web.multipart.commons.CommonsMultipartResolver;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import lombok.extern.slf4j.Slf4j;
+@Slf4j
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+    
+    @Bean(name = "multipartResolver")
+    public MultipartResolver multipartResolver() {
+        //log.info("Loading the multipart resolver");
+        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
+        resolver.setDefaultEncoding("UTF-8");
+        //resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
+        resolver.setResolveLazily(true);
+        resolver.setMaxInMemorySize(40960);
+        //单个上传文件大小 50M 50*1024*1024
+        resolver.setMaxUploadSize(50 * 1024 * 1024);
+        return resolver;
+    }
+}

+ 35 - 0
easydo-mes/src/main/java/easydo/technology/config/exception/BizExceptionHandler.java

@@ -0,0 +1,35 @@
+package easydo.technology.config.exception;
+
+import lombok.extern.log4j.Log4j2;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.BindException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * 处理Controller抛出的异常*
+ * @author tianyf
+ */
+@RestControllerAdvice
+@Log4j2
+public class BizExceptionHandler {
+
+    /**
+     * 为 @NotNull / @NotEmpty / @NotBlank 提供异常后的消息抛出*
+     * @param bindException
+     * @return
+     */
+    @ExceptionHandler({BindException.class})
+    public ResponseEntity<Object> throwCustomException(BindException bindException){
+        log.error("[ @Vaild异常捕获 ] " + bindException.getMessage());
+        Map<String, Object> vo = new HashMap<>();
+        vo.put("message", bindException.getBindingResult().getFieldError().getDefaultMessage());
+        vo.put("code", HttpStatus.BAD_REQUEST.value());
+        return new ResponseEntity(vo, HttpStatus.OK);
+    }
+}

+ 11 - 0
easydo-mes/src/main/java/easydo/technology/controller/TestController.java

@@ -0,0 +1,11 @@
+package easydo.technology.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@RestController
+@RequestMapping("/test")
+public class TestController {
+
+}

+ 152 - 0
easydo-mes/src/main/java/easydo/technology/enums/MESEnum.java

@@ -0,0 +1,152 @@
+package easydo.technology.enums;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum MESEnum {
+    //task_type
+    TASK_TYPE_CAR_RINSE("car_rinse", "渣土运输管理"),
+    TASK_TYPE_AIHAZARD_RECORD("aihazard", "AI视频危险源识别"),
+    TASK_TYPE_ENVDEV_RECORD("envdev", "环境监测"),
+    TASK_TYPE_SCC_RECORD("scc", "标养室监测"),
+    TASK_TYPE_AUTOSPRAY_RECORD("autospray", "自动喷淋系统"),
+    TASK_TYPE_TCM_RECORD("tcm", "塔机监测"),
+    TASK_TYPE_TCM_WARNING_RECORD("tcm_warning", "塔机监测--报警数据"),
+    TASK_TYPE_TCM_PERSON_RECORD("tcm_person", "塔机监测--考勤数据"),
+    TASK_TYPE_ELEVATOR_RECORD("elevator", "施工升降电梯监测"),
+    TASK_TYPE_ELEVATOR_WARNING_RECORD("elevator_warning", "施工升降电梯监测--报警数据"),
+    TASK_TYPE_ELEVATOR_PERSON_RECORD("elevator_person", "施工升降电梯监测--考勤数据"),
+    TASK_TYPE_BROADCAST_RECORD("broadcast", "智能广播"),
+    TASK_TYPE_SMOKEDET_RECORD("smokedet", "智能烟感/库房监测"),
+    TASK_TYPE_SMOKEDET_RECORD_STANDARD("smokedet_standard", "智能烟感"),
+    TASK_TYPE_SMOKEDET_RECORD_STOREHOUSE("smokedet_storehouse", "库房监测"),
+    TASK_TYPE_EDGEPROT_RECORD("edgeprot", "智能临边防护网监测"),
+    TASK_TYPE_EDGEPROT_WARNING_RECORD("edgeprot_warning", "智能临边防护网监测"),
+    TASK_TYPE_PARKING_RECORD("parking", "夜间施工监测"),
+
+    TASK_TYPE_IOT_PLATFORM("iot_platform", "智慧化工地管理平台"),
+    TASK_TYPE_SAFETY_RECORD("safety", "施工安全管理模块"),
+    TASK_TYPE_CAMERA_RECORD("camera", "视频监控"),
+    TASK_TYPE_FOUNDATION_PIT("foundation_pit", "基坑监测"),
+    TASK_TYPE_HOOK_VIEW("hook_view", "吊钩盲区可视化"),
+    TASK_TYPE_SAMPLE_DETECT("sample_detect", "检验检测管理"),
+    TASK_TYPE_RESIDENCE("residence", "智慧化分户验收"),
+    TASK_TYPE_OWNER_ACTIVITY("owner_activity", "业主开放活动智能化"),
+    TASK_TYPE_QUALITY("quality", "质量管理模块"),
+    TASK_TYPE_FACEREC_PERSON("facerec_person", "实名制系统(含工人工资支付)"),
+    TASK_TYPE_FACEREC_RECORD("facerec_record", "智能化考勤"),
+    TASK_TYPE_LASER_POSITION("laser_position", "塔机激光定位系统"),
+    TASK_TYPE_ILLEGAL_ENTRY("illegal_entry", "周界防护"),
+    TASK_TYPE_FULL_VIEW("full_view", "全景成像测距监控"),
+    TASK_TYPE_COMMAND_CENTER("command_center", "智慧工地指挥中心"),
+    TASK_TYPE_PLANNER_NODE("planner_node", "进度管理"),
+    TASK_TYPE_CONSTRUCT_MODEL("construct_model", "施工模型"),
+    TASK_TYPE_BIM_DESIGN("bim_design", "BIM深化设计"),
+    TASK_TYPE_BIM_DISCLOSURE("bim_disclosure", "BIM可视化技术交底"),
+    TASK_TYPE_BIM_SIMULATION("bim_simulation", "BIM施工工艺模拟"),
+    TASK_TYPE_BIM_DRAW("bim_draw", "BIM智慧化图纸管理"),
+
+    TASK_TYPE_CAR_RINSE_REMOVE("car_rinse_remove", "渣土运输管理-批量删除"),
+    TASK_TYPE_AIHAZARD_RECORD_REMOVE("aihazard_remove", "AI视频危险源识别-批量删除"),
+    TASK_TYPE_ENVDEV_RECORD_REMOVE("envdev_remove", "环境监测-批量删除"),
+    TASK_TYPE_SCC_RECORD_REMOVE("scc_remove", "标养室监测-批量删除"),
+    TASK_TYPE_AUTOSPRAY_RECORD_REMOVE("autospray_remove", "自动喷淋系统-批量删除"),
+    TASK_TYPE_TCM_RECORD_REMOVE("tcm_remove", "塔机监测-批量删除"),
+    TASK_TYPE_TCM_WARNING_RECORD_REMOVE("tcm_warning_remove", "塔机监测--报警数据-批量删除"),
+    TASK_TYPE_TCM_PERSON_RECORD_REMOVE("tcm_person_remove", "塔机监测--考勤数据-批量删除"),
+    TASK_TYPE_ELEVATOR_RECORD_REMOVE("elevator_remove", "施工升降电梯监测-批量删除"),
+    TASK_TYPE_ELEVATOR_WARNING_RECORD_REMOVE("elevator_warning_remove", "施工升降电梯监测--报警数据-批量删除"),
+    TASK_TYPE_ELEVATOR_PERSON_RECORD_REMOVE("elevator_person_remove", "施工升降电梯监测--考勤数据-批量删除"),
+    TASK_TYPE_BROADCAST_RECORD_REMOVE("broadcast_remove", "智能广播-批量删除"),
+    TASK_TYPE_SMOKEDET_RECORD_STANDARD_REMOVE("smokedet_standard_remove", "智能烟感-批量删除"),
+    TASK_TYPE_SMOKEDET_RECORD_STOREHOUSE_REMOVE("smokedet_storehouse_remove", "库房监测-批量删除"),
+    TASK_TYPE_EDGEPROT_RECORD_REMOVE("edgeprot_remove", "智能临边防护网监测-批量删除"),
+    TASK_TYPE_EDGEPROT_WARNING_RECORD_REMOVE("edgeprot_warning_remove", "智能临边防护网监测-批量删除"),
+    TASK_TYPE_PARKING_RECORD_REMOVE("parking_remove", "夜间施工监测-批量删除"),
+
+    TASK_TYPE_DATA_DIAGNOSE("data_diagnose", "数据诊断"),
+
+    MINIO_REF_TYPE_IOT_ACCEPTANCE_PROJECT("iot_acceptance_project","项目验收应用项"),
+
+    //task_status
+    TASK_STATUS_ACTIVE("active", "已激活"),
+    TASK_STATUS_INACTIVE("inactive", "未激活"),
+    TASK_STATUS_SUCCESS("success", "已成功"),
+    TASK_STATUS_CANCEL("cancel", "已取消"),
+    TASK_STATUS_FAIL("fail", "已失败"),
+
+    ACCEPTANCE_ITEMS_TYPE_BASIC("basic","基础项"),
+    ACCEPTANCE_ITEMS_TYPE_PROMOTION("promotion","推广项"),
+    ACCEPTANCE_ITEMS_TYPE_IMPROVE("improve","提升项"),
+
+    MESSAGE_SUCCESS("success","成功"),
+    MESSAGE_CANCEL_MANUAL("cancel_manual","手工取消"),
+    MESSAGE_CANCEL_SYSTEM("cancel_system","系统自动取消"),
+    MESSAGE_UNKNOWN("unknown", "未知错误"),
+    MESSAGE_TIME_ERROR("time_error", "数据模拟的起止时间超出项目验收项的起止时间"),
+    MESSAGE_DATA_LACK_ACCEPT("data_lack_accept", "请到-基础数据管理-验收清单项-进行维护"),
+    MESSAGE_DATA_LACK_PROJECT_ACCEPT("data_lack_project_accept", "请到-基础数据管理-项目管理-进行维护项目验收项"),
+    MESSAGE_DATA_LACK_PROJECT_ACCEPT_TIME("data_lack_project_accept_time", "请到-基础数据管理-项目管理-进行维护项目验收项的起止时间"),
+    MESSAGE_DATA_UNMOUNTED_DEVICE("data_unmounted_device", "缺失设备信息,请到安装点处绑定相应的设备"),
+    MESSAGE_DATA_COPY_COVER_SAME_MOUNTED("data_copy_cover_same_mounted", "安装点X,无法实现覆盖安装点X的数据"),
+    MESSAGE_DATA_NO_PERSON_ID_CARD("data_no_person_id_card", "没有检索到人员信息,请检查后重试"),
+    MESSAGE_DATA_NO_DRIVER("data_no_driver", "该安装点没有配置司机信息"),
+    MESSAGE_DATA_NO_SENSOR("data_no_sensor", "该安装点没有配置传感器信息"),
+    MESSAGE_DATA_NO_RECORD_FOR_WARNING("data_no_sensor_for_warning", "该安装点没有数据信息,无法模拟报警数据"),
+    MESSAGE_DATA_OVER_SOURCE_DATA("data_over_source_data", "迁移总数超出数据源总数,请检查后重试"),
+
+    EXECUTE_TYPE_INSERT("i","新增"),
+    EXECUTE_TYPE_DELETE("d","删除"),
+
+    DATA_SOURCE_INIT("0", "数据库中历史存在的数据"),
+    DATA_SOURCE_TRUE("1", "iot中复制过来的"),
+    DATA_SOURCE_MAKE("2", "造数规则造的假数据"),
+
+    PLAN_NODE_STATUS_ACTIVE("active", "进行中"),
+    PLAN_NODE_STATUS_INACTIVE("inactive", "待进行"),
+    PLAN_NODE_STATUS_DONE("done", "已完成"),
+
+    BLANK_TYPE_VALUE("-","空值"),
+
+    PLAN_BIND_TYPE_1("1", "申报资料"),
+
+    //platform_type
+    PLATFORM_TYPE_QINGDAO_MSP("qingdao_msp", "青岛市智慧工地监管平台"),
+    PLATFORM_TYPE_LAOSHAN_MSP("laoshan_msp", "崂山区智慧工地监管平台"),
+    PLATFORM_TYPE_SHIZHENG_MSP("shizheng_msp", "青岛市市政智慧工地监管平台"),
+    ;
+
+    MESEnum(String value, String comment) {
+        this.value = value;
+        this.comment = comment;
+    }
+
+    private static Map<String, MESEnum> typelookup = new HashMap<>();
+
+    static {
+        for (MESEnum mspEnum : MESEnum.values()) {
+            typelookup.put(mspEnum.value, mspEnum);
+        }
+    }
+
+    public static boolean isContainsValue(String type) {
+        return typelookup.containsKey(type);
+    }
+
+    public static MESEnum forType(String type) {
+        return typelookup.get(type);
+    }
+
+    private String value;
+    private String comment;
+
+    public String getValue() {
+        return value;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+
+}

+ 35 - 0
easydo-mes/src/main/java/easydo/technology/exception/BizException.java

@@ -0,0 +1,35 @@
+package easydo.technology.exception;
+
+import lombok.Getter;
+
+/**
+ * 自定义业务异常类
+ * @author qiguliuxing
+ * @since 1.0.0
+ */
+public class BizException extends RuntimeException {
+
+    @Getter
+    private int code;
+
+    @Getter
+    private String message;
+
+    public BizException(String message) {
+        super(message);
+        this.code = -999;
+        this.message = message;
+    }
+
+    public BizException(int code, String message) {
+        super(message);
+        this.code = code;
+        this.message = message;
+    }
+
+    public BizException(int code, String message, Throwable cause) {
+        super(message, cause);
+        this.code = code;
+        this.message = message;
+    }
+}

+ 42 - 0
easydo-mes/src/main/java/easydo/technology/listener/OpsTaskListener.java

@@ -0,0 +1,42 @@
+package easydo.technology.listener;
+
+import easydo.technology.components.JdbcClient;
+import easydo.technology.enums.MESEnum;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.List;
+
+@Profile(value = "prod")
+@Component
+public class OpsTaskListener {
+    @Resource
+    JdbcClient jdbcClient;
+    @Resource
+    DataSource dataSource;
+
+    @EventListener(ApplicationReadyEvent.class)
+    public void onApplicationReady() throws Exception {
+        System.out.println(">>>>>> 应用已完全就绪!执行任务初始化");
+        Connection connection = dataSource.getConnection();
+        List<String> statusList = new ArrayList<>();
+        statusList.add(MESEnum.TASK_STATUS_ACTIVE.getValue());
+        statusList.add(MESEnum.TASK_STATUS_INACTIVE.getValue());
+        try {
+
+            System.out.println(">>>>>> 执行任务初始化完成");
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            connection.close();
+        }
+
+    }
+
+}

+ 43 - 0
easydo-mes/src/main/java/easydo/technology/service/AsyncService.java

@@ -0,0 +1,43 @@
+package easydo.technology.service;
+
+import easydo.technology.components.JdbcClient;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+
+@EnableAsync
+@Service
+public class AsyncService {
+    @Resource
+    JdbcClient jdbcClient;
+    @Resource
+    DataSource dataSource;
+
+    private Map<String, CompletableFuture<String>> taskMap = new ConcurrentHashMap<>();
+
+
+    @Async
+    public void stopProcessingTask(String taskId) throws Exception {
+        CompletableFuture<String> future = taskMap.get(taskId);
+        if (future != null) {
+            future.cancel(true);
+            taskMap.remove(taskId);
+        }
+    }
+
+    public void putMapTask(String taskId, CompletableFuture<String> future) {
+        taskMap.put(taskId, future);
+    }
+
+    public void removeMapTask(String taskId) {
+        taskMap.remove(taskId);
+    }
+
+
+}

+ 129 - 0
easydo-mes/src/main/java/easydo/technology/service/MinioService.java

@@ -0,0 +1,129 @@
+package easydo.technology.service;
+
+import easydo.technology.utils.LocalDateUtil;
+import io.minio.*;
+import io.minio.errors.ErrorResponseException;
+import io.minio.errors.MinioException;
+import io.minio.http.Method;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.time.LocalDate;
+
+@Service
+public class MinioService {
+
+    @Resource
+    private MinioClient minioClient;
+
+    @Value("${minio.endpoint}")
+    private String endpoint;
+
+    /**
+     * 检查存储桶是否存在
+     */
+    public boolean bucketExists(String bucketName) throws Exception {
+        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
+    }
+
+    /**
+     * 创建存储桶
+     */
+    public void makeBucket(String bucketName) throws Exception {
+        if (!bucketExists(bucketName)) {
+            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+        }
+    }
+
+    /**
+     * 上传文件
+     */
+    public String uploadFile(String bucketName, InputStream inputStream, String objectName) throws Exception {
+        // 如果存储桶不存在则创建
+        makeBucket(bucketName);
+
+        String dir = LocalDateUtil.localDateTimeFormat(LocalDate.now(), "yyyy-MM-dd");
+        String object = dir + "/" + objectName;
+        minioClient.putObject(
+                PutObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(object)
+                        //分块上传10m一个区块
+                        .stream(inputStream, -1, 10485760)
+                        .contentType("application/octet-stream")
+                        .build());
+        return "/" + bucketName + "/" + object;
+    }
+
+    /**
+     * 上传文件
+     */
+    public String uploadFile(String bucketName, InputStream inputStream, String objectName, String dir) throws Exception {
+        // 如果存储桶不存在则创建
+        makeBucket(bucketName);
+
+        String object = dir + "/" + objectName;
+        minioClient.putObject(
+                PutObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(object)
+                        //分块上传10m一个区块
+                        .stream(inputStream, -1, 10485760)
+                        .contentType("application/octet-stream")
+                        .build());
+        return "/" + bucketName + "/" + object;
+    }
+
+
+    /**
+     * 获取文件URL
+     */
+    public String getFileUrl(String bucketName, String objectName) throws Exception {
+        return minioClient.getPresignedObjectUrl(
+                GetPresignedObjectUrlArgs.builder()
+                        .method(Method.GET)
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .build());
+    }
+
+    /**
+     * 下载文件
+     */
+    public InputStream downloadFile(String bucketName, String objectName) throws Exception {
+        return minioClient.getObject(
+                GetObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .build());
+    }
+
+    /**
+     * 删除文件
+     */
+    public void removeFile(String bucketName, String objectName) throws Exception {
+        minioClient.removeObject(
+                RemoveObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(objectName)
+                        .build());
+    }
+
+    public boolean isObjectExist(String bucketName, String objectName) {
+        try {
+            minioClient.statObject(
+                    StatObjectArgs.builder()
+                            .bucket(bucketName)
+                            .object(objectName)
+                            .build()
+            );
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}

+ 72 - 0
easydo-mes/src/main/java/easydo/technology/util/DataListener.java

@@ -0,0 +1,72 @@
+package easydo.technology.util;
+
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author liy
+ * @since 2022-03-31
+ **/
+public class DataListener<T> extends AnalysisEventListener<T> {
+    private final List<T> dataList = new ArrayList();
+
+    public DataListener() {
+    }
+
+    @Override
+    public void invoke(T data, AnalysisContext analysisContext) {
+        this.dataList.add(data);
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+    }
+
+    public List<T> getDataList() {
+        return this.dataList;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof DataListener)) {
+            return false;
+        } else {
+            DataListener<?> other = (DataListener)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else if (!super.equals(o)) {
+                return false;
+            } else {
+                Object this$dataList = this.getDataList();
+                Object other$dataList = other.getDataList();
+                if (this$dataList == null) {
+                    if (other$dataList != null) {
+                        return false;
+                    }
+                } else if (!this$dataList.equals(other$dataList)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(final Object other) {
+        return other instanceof DataListener;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        Object $dataList = this.getDataList();
+        result = result * 59 + ($dataList == null ? 43 : $dataList.hashCode());
+        return result;
+    }
+}
+  

+ 291 - 0
easydo-mes/src/main/java/easydo/technology/util/DateUtils.java

@@ -0,0 +1,291 @@
+package easydo.technology.util;
+
+
+import easydo.technology.utils.StringUtil;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.temporal.TemporalAmount;
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * @author liy
+ * @since 2022-04-01
+ **/
+public class DateUtils {
+    private static final ThreadLocal<Map<Integer, Boolean>> DATE_THREAD_LOCAL = new ThreadLocal();
+    private static final ThreadLocal<Map<String, SimpleDateFormat>> DATE_FORMAT_THREAD_LOCAL = new ThreadLocal();
+    private static final Pattern date_ptrn1 = Pattern.compile("^\\[\\$\\-.*?\\]");
+    private static final Pattern date_ptrn2 = Pattern.compile("^\\[[a-zA-Z]+\\]");
+    private static final Pattern date_ptrn3a = Pattern.compile("[yYmMdDhHsS]");
+    private static final Pattern date_ptrn3b = Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/年月日,. :\"\\\\]+0*[ampAMP/]*$");
+    private static final Pattern date_ptrn4 = Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)\\]");
+    private static final Pattern date_ptrn5 = Pattern.compile("^\\[DBNum(1|2|3)\\]");
+    private static final Pattern date_ptrn6 = Pattern.compile("(年|月|日|时|分|秒)+");
+    public static final String DATE_FORMAT_10 = "yyyy-MM-dd";
+    public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss";
+    public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss";
+    public static final String DATE_FORMAT_19 = "yyyy-MM-dd HH:mm:ss";
+    public static final String DATE_FORMAT_19_FORWARD_SLASH = "yyyy/MM/dd HH:mm:ss";
+    private static final String MINUS = "-";
+
+    private DateUtils() {
+    }
+
+    public static Date plusDays(Date date, long daysToAdd) {
+        return plus(date, Duration.ofDays(daysToAdd));
+    }
+
+    public static Date plus(Date date, TemporalAmount amount) {
+        Instant instant = date.toInstant();
+        return Date.from(instant.plus(amount));
+    }
+
+    public static Date parseDate(String dateString, String dateFormat) throws ParseException {
+        if (StringUtil.isEmpty(dateFormat)) {
+            dateFormat = switchDateFormat(dateString);
+        }
+
+        return getCacheDateFormat(dateFormat).parse(dateString);
+    }
+
+    public static Date parseDate(String dateString) throws ParseException {
+        return parseDate(dateString, switchDateFormat(dateString));
+    }
+
+    private static String switchDateFormat(String dateString) {
+        int length = dateString.length();
+        switch (length) {
+            case 10:
+                return "yyyy-MM-dd";
+            case 11:
+            case 12:
+            case 13:
+            case 15:
+            case 16:
+            case 18:
+            default:
+                throw new IllegalArgumentException("can not find date format for:" + dateString);
+            case 14:
+                return "yyyyMMddHHmmss";
+            case 17:
+                return "yyyyMMdd HH:mm:ss";
+            case 19:
+                return dateString.contains("-") ? "yyyy-MM-dd HH:mm:ss" : "yyyy/MM/dd HH:mm:ss";
+        }
+    }
+
+    public static String format(Date date) {
+        return format(date, (String) null);
+    }
+
+    public static String format(Date date, String dateFormat) {
+        if (date == null) {
+            return "";
+        } else {
+            if (StringUtil.isEmpty(dateFormat)) {
+                dateFormat = "yyyy-MM-dd HH:mm:ss";
+            }
+
+            return getCacheDateFormat(dateFormat).format(date);
+        }
+    }
+
+    private static DateFormat getCacheDateFormat(String dateFormat) {
+        Map<String, SimpleDateFormat> dateFormatMap = (Map) DATE_FORMAT_THREAD_LOCAL.get();
+        SimpleDateFormat dateFormatCached;
+        if (dateFormatMap == null) {
+            dateFormatMap = new HashMap();
+            DATE_FORMAT_THREAD_LOCAL.set(dateFormatMap);
+        } else {
+            dateFormatCached = (SimpleDateFormat) ((Map) dateFormatMap).get(dateFormat);
+            if (dateFormatCached != null) {
+                return dateFormatCached;
+            }
+        }
+
+        dateFormatCached = new SimpleDateFormat(dateFormat);
+        ((Map) dateFormatMap).put(dateFormat, dateFormatCached);
+        return dateFormatCached;
+    }
+
+    public static boolean isADateFormat(Integer formatIndex, String formatString) {
+        if (formatIndex == null) {
+            return false;
+        } else {
+            Map<Integer, Boolean> isDateCache = (Map) DATE_THREAD_LOCAL.get();
+            if (isDateCache == null) {
+                isDateCache = new HashMap();
+                DATE_THREAD_LOCAL.set(isDateCache);
+            } else {
+                Boolean isDateCachedData = (Boolean) ((Map) isDateCache).get(formatIndex);
+                if (isDateCachedData != null) {
+                    return isDateCachedData;
+                }
+            }
+
+            boolean isDate = isADateFormatUncached(formatIndex, formatString);
+            ((Map) isDateCache).put(formatIndex, isDate);
+            return isDate;
+        }
+    }
+
+    public static boolean isADateFormatUncached(Integer formatIndex, String formatString) {
+        if (isInternalDateFormat(formatIndex)) {
+            return true;
+        } else if (StringUtil.isEmpty(formatString)) {
+            return false;
+        } else {
+            String fs = formatString;
+            int length = formatString.length();
+            StringBuilder sb = new StringBuilder(length);
+
+            int separatorIndex;
+            for (separatorIndex = 0; separatorIndex < length; ++separatorIndex) {
+                char c = fs.charAt(separatorIndex);
+                if (separatorIndex < length - 1) {
+                    char nc = fs.charAt(separatorIndex + 1);
+                    if (c == '\\') {
+                        switch (nc) {
+                            case ' ':
+                            case ',':
+                            case '-':
+                            case '.':
+                            case '\\':
+                                continue;
+                        }
+                    } else if (c == ';' && nc == '@') {
+                        ++separatorIndex;
+                        continue;
+                    }
+                }
+
+                sb.append(c);
+            }
+
+            fs = sb.toString();
+            if (date_ptrn4.matcher(fs).matches()) {
+                return true;
+            } else {
+                fs = date_ptrn5.matcher(fs).replaceAll("");
+                fs = date_ptrn1.matcher(fs).replaceAll("");
+                fs = date_ptrn2.matcher(fs).replaceAll("");
+                separatorIndex = fs.indexOf(59);
+                if (0 < separatorIndex && separatorIndex < fs.length() - 1) {
+                    fs = fs.substring(0, separatorIndex);
+                }
+
+                if (!date_ptrn3a.matcher(fs).find()) {
+                    return false;
+                } else {
+                    boolean result = date_ptrn3b.matcher(fs).matches();
+                    if (result) {
+                        return true;
+                    } else {
+                        result = date_ptrn6.matcher(fs).find();
+                        return result;
+                    }
+                }
+            }
+        }
+    }
+
+    public static boolean isInternalDateFormat(int format) {
+        switch (format) {
+            case 14:
+            case 15:
+            case 16:
+            case 17:
+            case 18:
+            case 19:
+            case 20:
+            case 21:
+            case 22:
+            case 27:
+            case 28:
+            case 29:
+            case 30:
+            case 31:
+            case 32:
+            case 33:
+            case 34:
+            case 35:
+            case 36:
+            case 45:
+            case 46:
+            case 47:
+            case 50:
+            case 51:
+            case 52:
+            case 53:
+            case 54:
+            case 55:
+            case 56:
+            case 57:
+            case 58:
+                return true;
+            case 23:
+            case 24:
+            case 25:
+            case 26:
+            case 37:
+            case 38:
+            case 39:
+            case 40:
+            case 41:
+            case 42:
+            case 43:
+            case 44:
+            case 48:
+            case 49:
+            default:
+                return false;
+        }
+    }
+
+    public static void removeThreadLocalCache() {
+        DATE_THREAD_LOCAL.remove();
+        DATE_FORMAT_THREAD_LOCAL.remove();
+    }
+
+    /**
+     * date2比date1多的天数
+     *
+     * @param date1
+     * @param date2
+     * @return
+     */
+    public static int differentDays(Date date1, Date date2) {
+        Calendar cal1 = Calendar.getInstance();
+        cal1.setTime(date1);
+
+        Calendar cal2 = Calendar.getInstance();
+        cal2.setTime(date2);
+        int day1 = cal1.get(Calendar.DAY_OF_YEAR);
+        int day2 = cal2.get(Calendar.DAY_OF_YEAR);
+
+        int year1 = cal1.get(Calendar.YEAR);
+        int year2 = cal2.get(Calendar.YEAR);
+        if (year1 != year2) {//不同年
+            int timeDistance = 0;
+            for (int i = year1; i < year2; i++) {
+                if (i % 4 == 0 && i % 100 != 0 || i % 400 == 0) {    //闰年
+                    timeDistance += 366;
+                } else {  //不是闰年
+                    timeDistance += 365;
+                }
+            }
+
+            return timeDistance + (day2 - day1);
+        } else {// 同一年
+            System.out.println("判断day2 - day1 : " + (day2 - day1));
+            return day2 - day1;
+        }
+    }
+}
+  

+ 112 - 0
easydo-mes/src/main/java/easydo/technology/util/ExcelUtil.java

@@ -0,0 +1,112 @@
+package easydo.technology.util;
+
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.read.builder.ExcelReaderBuilder;
+import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
+import com.alibaba.excel.read.listener.ReadListener;
+import com.alibaba.excel.util.DateUtils;
+import easydo.technology.exception.BizException;
+import org.apache.commons.codec.Charsets;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLEncoder;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author liy
+ * @since 2022-03-31
+ **/
+public class ExcelUtil {
+    private static DecimalFormat df1 = new DecimalFormat("0");
+    private static DecimalFormat df2 = new DecimalFormat("0.00");
+
+    public static <T> List<T> read(MultipartFile excel, int sheetNo, Class<T> clazz) {
+        return read(excel, sheetNo, 1, clazz);
+    }
+
+    public static <T> List<T> read(MultipartFile excel, int sheetNo, int headRowNumber, Class<T> clazz) {
+        DataListener<T> dataListener = new DataListener();
+        ExcelReaderBuilder builder = getReaderBuilder(excel, dataListener, clazz);
+        if (builder == null) {
+            return null;
+        } else {
+            ((ExcelReaderSheetBuilder) builder.sheet(sheetNo).headRowNumber(headRowNumber)).doRead();
+            return dataListener.getDataList();
+        }
+    }
+
+    public static <T> ExcelReaderBuilder getReaderBuilder(MultipartFile excel, ReadListener<T> readListener, Class<T> clazz) {
+        String filename = excel.getOriginalFilename();
+        if (StringUtils.isEmpty(filename)) {
+            throw new BizException("请上传文件!");
+        } else if (!StringUtils.endsWithIgnoreCase(filename, ".xls") && !StringUtils.endsWithIgnoreCase(filename, ".xlsx")) {
+            throw new BizException("请上传正确的excel文件!");
+        } else {
+            try {
+                InputStream inputStream = new BufferedInputStream(excel.getInputStream());
+                return EasyExcel.read(inputStream, clazz, readListener);
+            } catch (IOException var6) {
+                var6.printStackTrace();
+                return null;
+            }
+        }
+    }
+
+    public static <T> void export(HttpServletResponse response, List<T> dataList, Class<T> clazz) throws IOException {
+        try {
+            export(response, DateUtils.format(new Date(), "yyyyMMddHHmmss"), "导出数据", dataList, clazz);
+        } catch (Throwable var4) {
+            throw var4;
+        }
+    }
+
+    public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> dataList, Class<T> clazz) throws IOException {
+        try {
+            response.setContentType("application/vnd.ms-excel");
+            response.setCharacterEncoding(Charsets.UTF_8.name());
+            fileName = URLEncoder.encode(fileName, Charsets.UTF_8.name());
+            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
+            EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(dataList);
+        } catch (Throwable var6) {
+            throw var6;
+        }
+    }
+
+    private static String getValue(XSSFCell xssfCell, boolean isString) {
+        if (xssfCell.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
+            return String.valueOf(xssfCell.getBooleanCellValue()).trim();
+        } else if (xssfCell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
+            if (isString) {
+                return df1.format(xssfCell.getNumericCellValue()).trim();
+            } else {
+                return df2.format(xssfCell.getNumericCellValue()).trim();
+            }
+        } else {
+            return String.valueOf(xssfCell.getStringCellValue()).trim();
+        }
+    }
+
+    private static String getValue(HSSFCell hssfCell, boolean isString) {
+        if (hssfCell.getCellType() == hssfCell.CELL_TYPE_BOOLEAN) {
+            return String.valueOf(hssfCell.getBooleanCellValue()).trim();
+        } else if (hssfCell.getCellType() == hssfCell.CELL_TYPE_NUMERIC) {
+            if (isString) {
+                return df1.format(hssfCell.getNumericCellValue()).trim();
+            } else {
+                return df2.format(hssfCell.getNumericCellValue()).trim();
+            }
+        } else {
+            return String.valueOf(hssfCell.getStringCellValue().trim());
+        }
+    }
+}
+  

+ 374 - 0
easydo-mes/src/main/java/easydo/technology/util/HttpsUtil.java

@@ -0,0 +1,374 @@
+/**
+ *
+ */
+package easydo.technology.util;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+import javax.net.ssl.*;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+/**
+ * 发送https请求的工具类
+ */
+@Component
+public class HttpsUtil {
+
+    private static final Logger logger = LogManager.getLogger(HttpsUtil.class);
+    public static final String charset = "UTF-8";
+
+    public static final String CONTENTTYPE_APPLICATION_JSON = "application/json;charset=UTF-8";
+    public static final String CONTENTTYPE_APPLICATION_XML = "application/xml;charset=UTF-8";
+    public static final String CONTENTTYPE_MULTIPART_FORM = "multipart/form-data;charset=UTF-8";
+    public static final String TEXTPLAIN = "text/plain;charset=UTF-8";
+    public static final String TEXTHTML = "text/html;charset=UTF-8";
+    /**
+     * 忽视证书HostName
+     */
+    private static HostnameVerifier ignoreHostnameVerifier = new HostnameVerifier() {
+        public boolean verify(String s, SSLSession sslsession) {
+            return true;
+        }
+    };
+
+    /**
+     * Ignore Certification
+     */
+    private static TrustManager ignoreCertificationTrustManger = new X509TrustManager() {
+        private X509Certificate[] certificates;
+
+        public void checkClientTrusted(X509Certificate certificates[], String authType) throws CertificateException {
+            if (this.certificates == null) {
+                this.certificates = certificates;
+            }
+        }
+
+        public void checkServerTrusted(X509Certificate[] ax509certificate, String s) throws CertificateException {
+            if (this.certificates == null) {
+                this.certificates = ax509certificate;
+            }
+        }
+
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+    };
+
+    public static byte[] getByteFromUrl(String filePath) {
+
+        byte[] data = null;
+        InputStream is = null;
+        HttpURLConnection conn = null;
+        HttpsURLConnection conns = null;
+        try {
+
+            URL url = new URL(filePath);
+            if (filePath.startsWith("https")) {
+                HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
+                conns = (HttpsURLConnection) url.openConnection();
+                // Prepare SSL Context
+                TrustManager[] tm = {ignoreCertificationTrustManger};
+                SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
+                sslContext.init(null, tm, new java.security.SecureRandom());
+
+                // 从上述SSLContext对象中得到SSLSocketFactory对象
+                SSLSocketFactory ssf = sslContext.getSocketFactory();
+                conns.setSSLSocketFactory(ssf);
+                if (conns.getResponseCode() != 200) {
+                    return null;
+                }
+                is = conns.getInputStream();
+                if (conns.getResponseCode() == 200) {
+                    data = readInputStream(is);
+                } else {
+                    data = null;
+                }
+            } else {
+                conn = (HttpURLConnection) url.openConnection();
+                conn.setDoInput(true);
+                conn.setConnectTimeout(6000);
+                is = conn.getInputStream();
+                if (conn.getResponseCode() == 200) {
+                    data = readInputStream(is);
+                } else {
+                    data = null;
+                }
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (is != null) {
+                    is.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            if (conn != null) {
+                conn.disconnect();
+            }
+        }
+        return data;
+    }
+
+    public static byte[] readInputStream(InputStream is) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int length = -1;
+        try {
+            while ((length = is.read(buffer)) != -1) {
+                baos.write(buffer, 0, length);
+            }
+            baos.flush();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        byte[] data = baos.toByteArray();
+        try {
+            is.close();
+            baos.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return data;
+    }
+
+    /**
+     * 发送Get请求
+     */
+    public static JSONObject sendSSLGetMethod(String urlString) throws Exception {
+        JSONObject jsonObject = null;
+        StringBuffer buffer = null;
+        InputStream is = null;
+        InputStreamReader isr = null;
+        BufferedReader bufferedReader = null;
+        HttpsURLConnection connection = null;
+        try {
+
+            URL url = new URL(urlString);
+            HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
+            connection = (HttpsURLConnection) url.openConnection();
+
+            // Prepare SSL Context
+            TrustManager[] tm = {ignoreCertificationTrustManger};
+            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
+            sslContext.init(null, tm, new java.security.SecureRandom());
+
+            // 从上述SSLContext对象中得到SSLSocketFactory对象
+            SSLSocketFactory ssf = sslContext.getSocketFactory();
+            connection.setSSLSocketFactory(ssf);
+            if (connection.getResponseCode() != 200) {
+                return null;
+            }
+
+            is = connection.getInputStream();
+            isr = new InputStreamReader(is, "UTF-8");
+            bufferedReader = new BufferedReader(isr);
+
+            String str = null;
+            while ((str = bufferedReader.readLine()) != null) {
+                if (buffer == null) {
+                    buffer = new StringBuffer();
+                }
+                buffer.append(str);
+            }
+
+            jsonObject = JSONObject.parseObject(buffer.toString());
+        } catch (Exception ex) {
+            logger.error(ex.getMessage());
+            ex.printStackTrace();
+        } finally {
+            if (bufferedReader != null) {
+                bufferedReader.close();
+                bufferedReader = null;
+            }
+            if (isr != null) {
+                isr.close();
+                isr = null;
+            }
+            if (is != null) {
+                is.close();
+                is = null;
+            }
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+        return jsonObject;
+    }
+
+    /**
+     * 发送Post请求
+     */
+    public static JSONObject sendSSLPostMethod(String urlString, String postData) throws Exception {
+        JSONObject jsonObject = null;
+        StringBuffer buffer = null;
+        InputStream is = null;
+        InputStreamReader isr = null;
+        BufferedReader bufferedReader = null;
+        HttpsURLConnection connection = null;
+        try {
+
+            URL url = new URL(urlString);
+            HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
+            connection = (HttpsURLConnection) url.openConnection();
+            connection.setDoInput(true);
+            connection.setDoOutput(true);
+            connection.setRequestMethod("POST");
+            connection.setRequestProperty("content-type", "text/json");
+            connection.setRequestProperty("content-length", String.valueOf(postData.getBytes().length));
+            connection.getOutputStream().write(postData.getBytes("utf-8"));
+            connection.getOutputStream().flush();
+            connection.getOutputStream().close();
+
+            // Prepare SSL Context
+            TrustManager[] tm = {ignoreCertificationTrustManger};
+            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
+            sslContext.init(null, tm, new java.security.SecureRandom());
+
+            // 从上述SSLContext对象中得到SSLSocketFactory对象
+            SSLSocketFactory ssf = sslContext.getSocketFactory();
+            connection.setSSLSocketFactory(ssf);
+            if (connection.getResponseCode() != 200) {
+                return null;
+            }
+
+            is = connection.getInputStream();
+            isr = new InputStreamReader(is, "UTF-8");
+            bufferedReader = new BufferedReader(isr);
+
+            String str = null;
+            while ((str = bufferedReader.readLine()) != null) {
+                if (buffer == null) {
+                    buffer = new StringBuffer();
+                }
+                buffer.append(str);
+            }
+
+            jsonObject = JSONObject.parseObject(buffer.toString());
+        } finally {
+            if (null != is) {
+                is.close();
+                is = null;
+            }
+            if (null != connection) {
+                connection.disconnect();
+            }
+        }
+        return jsonObject;
+    }
+
+    /**
+     * 发送Post请求
+     */
+/*    public static String postForm(String urlString, byte[] postData) throws Exception {
+        String buffer = "";
+        InputStream is = null;
+        InputStreamReader isr = null;
+        BufferedReader bufferedReader = null;
+        HttpsURLConnection connection = null;
+        try {
+
+            URL url = new URL(urlString);
+            HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
+            connection = (HttpsURLConnection) url.openConnection();
+            connection.setDoInput(true);
+            connection.setDoOutput(true);
+            connection.setRequestMethod("POST");
+            connection.setRequestProperty("content-type", "application/x-www-form-urlencoded");
+            // Prepare SSL Context
+            TrustManager[] tm = {ignoreCertificationTrustManger};
+            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
+            sslContext.init(null, tm, new java.security.SecureRandom());
+
+            // 从上述SSLContext对象中得到SSLSocketFactory对象
+            SSLSocketFactory ssf = sslContext.getSocketFactory();
+            connection.setSSLSocketFactory(ssf);
+            OutputStream outStrm = connection.getOutputStream();
+            outStrm.write(postData);
+            outStrm.flush();
+            outStrm.close();
+
+            if (connection.getResponseCode() != 200) {
+                return null;
+            }
+            is = connection.getInputStream();
+            buffer = IOUtils.toString(is, "utf-8");
+        } finally {
+            if (null != is) {
+                is.close();
+                is = null;
+            }
+            if (null != connection) {
+                connection.disconnect();
+            }
+        }
+        return buffer;
+    }*/
+    public static String downloadFile(String localFile, String fileUrl) throws Exception {
+        InputStream is = null;
+        HttpsURLConnection connection = null;
+        File file = new File(localFile);
+        FileOutputStream fos = new FileOutputStream(file);
+        try {
+            URL url = new URL(fileUrl);
+            HttpsURLConnection.setDefaultHostnameVerifier(ignoreHostnameVerifier);
+            connection = (HttpsURLConnection) url.openConnection();
+            connection.setDoInput(true);
+            connection.setDoOutput(true);
+            connection.setRequestMethod("GET");
+//            connection.setRequestProperty("content-type", "application/x-www-form-urlencoded");
+            // Prepare SSL Context
+            TrustManager[] tm = {ignoreCertificationTrustManger};
+            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
+            sslContext.init(null, tm, new java.security.SecureRandom());
+
+            // 从上述SSLContext对象中得到SSLSocketFactory对象
+            SSLSocketFactory ssf = sslContext.getSocketFactory();
+            connection.setSSLSocketFactory(ssf);
+
+            if (connection.getResponseCode() != 200) {
+                return null;
+            }
+            is = connection.getInputStream();
+            int bytesum = 0;
+            int byteread = 0;
+
+            byte[] buffer = new byte[1204];
+            while ((byteread = is.read(buffer)) != -1) {
+                bytesum += byteread;
+                System.out.println(bytesum);
+                fos.write(buffer, 0, byteread);
+            }
+            fos.close();
+            is.close();
+            connection.disconnect();
+            return localFile;
+        } finally {
+            if (null != is) {
+                fos.close();
+                fos = null;
+            }
+            if (null != is) {
+                is.close();
+                is = null;
+            }
+            if (null != connection) {
+                connection.disconnect();
+            }
+        }
+    }
+
+//    public static void main(String[] args) {
+//        byte[] byteFromUrl = getByteFromUrl("https://pmbimcloud-company.oss-cn-hangzhou.aliyuncs.com/af560077-7ead-2c94-b2dc-02170a833273.jpg?Expires=1672812245&OSSAccessKeyId=LTAI8PPrIEVEAQCK&Signature=9oy9s8HS%2FvIczLQJQUeWBqL7MII%3D");
+//        System.out.println(byteFromUrl.length);
+//    }
+}

+ 786 - 0
easydo-mes/src/main/java/easydo/technology/util/WeatherUtil.java

@@ -0,0 +1,786 @@
+package easydo.technology.util;
+
+import easydo.technology.model.WeatherData;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class WeatherUtil {
+
+    // 天气状态定义
+    public enum WeatherState {
+        SUNNY, CLOUDY, RAINY, STORMY, FOGGY
+    }
+
+    // 季节定义
+    public enum Season {
+        WINTER, SPRING, SUMMER, AUTUMN
+    }
+
+    // 风向定义(16方位)
+    public enum WindDirection {
+        N, NNE, NE, ENE, E, ESE, SE, SSE,
+        S, SSW, SW, WSW, W, WNW, NW, NNW,
+        OTHER
+    }
+
+
+    // 模拟参数常量
+    private static final int TIME_INTERVAL_MINUTES = 3;
+
+    // 污染物类型
+    private enum PollutantType {
+        PM2_5, PM10, PM100, NOISE
+    }
+
+    // 马尔可夫链状态转移概率矩阵(按季节调整)
+    private static final Map<Season, double[][]> TRANSITION_MATRICES;
+
+    static {
+        Map<Season, double[][]> tempMap = new EnumMap<>(Season.class);
+        tempMap.put(Season.WINTER, new double[][]{
+                {0.70, 0.20, 0.08, 0.00, 0.02},  // SUNNY
+                {0.25, 0.55, 0.15, 0.00, 0.05},  // CLOUDY
+                {0.10, 0.30, 0.55, 0.00, 0.05},  // RAINY
+                {0.00, 0.00, 0.00, 0.00, 0.00},  // STORMY (冬季无雷暴)
+                {0.15, 0.25, 0.10, 0.00, 0.50}   // FOGGY
+        });
+        tempMap.put(Season.SPRING, new double[][]{
+                {0.65, 0.25, 0.07, 0.01, 0.02},
+                {0.30, 0.50, 0.15, 0.03, 0.02},
+                {0.15, 0.35, 0.45, 0.03, 0.02},
+                {0.05, 0.15, 0.30, 0.45, 0.05},
+                {0.20, 0.30, 0.10, 0.00, 0.40}
+        });
+        tempMap.put(Season.SUMMER, new double[][]{
+                {0.60, 0.30, 0.05, 0.05, 0.00},
+                {0.35, 0.45, 0.10, 0.08, 0.02},
+                {0.20, 0.40, 0.30, 0.08, 0.02},
+                {0.10, 0.20, 0.20, 0.45, 0.05},
+                {0.25, 0.35, 0.05, 0.00, 0.35}
+        });
+        tempMap.put(Season.AUTUMN, new double[][]{
+                {0.55, 0.30, 0.10, 0.03, 0.02},
+                {0.25, 0.50, 0.20, 0.03, 0.02},
+                {0.10, 0.30, 0.55, 0.03, 0.02},
+                {0.05, 0.15, 0.30, 0.45, 0.05},
+                {0.15, 0.25, 0.10, 0.00, 0.50}
+        });
+        TRANSITION_MATRICES = Collections.unmodifiableMap(tempMap);
+    }
+
+    // 基础温度范围(摄氏度,按季节和天气状态)
+    private static final Map<Season, Map<WeatherState, int[]>> BASE_TEMPERATURES;
+
+    static {
+        Map<Season, Map<WeatherState, int[]>> tempMap = new EnumMap<>(Season.class);
+
+        Map<WeatherState, int[]> winterTemps = new EnumMap<>(WeatherState.class);
+        winterTemps.put(WeatherState.SUNNY, new int[]{-5, 5});
+        winterTemps.put(WeatherState.CLOUDY, new int[]{-8, 2});
+        winterTemps.put(WeatherState.RAINY, new int[]{-3, 3});
+        winterTemps.put(WeatherState.STORMY, new int[]{-5, 0});
+        winterTemps.put(WeatherState.FOGGY, new int[]{-10, 0});
+        tempMap.put(Season.WINTER, Collections.unmodifiableMap(winterTemps));
+
+        Map<WeatherState, int[]> springTemps = new EnumMap<>(WeatherState.class);
+        springTemps.put(WeatherState.SUNNY, new int[]{8, 18});
+        springTemps.put(WeatherState.CLOUDY, new int[]{5, 15});
+        springTemps.put(WeatherState.RAINY, new int[]{6, 12});
+        springTemps.put(WeatherState.STORMY, new int[]{10, 16});
+        springTemps.put(WeatherState.FOGGY, new int[]{3, 10});
+        tempMap.put(Season.SPRING, Collections.unmodifiableMap(springTemps));
+
+        Map<WeatherState, int[]> summerTemps = new EnumMap<>(WeatherState.class);
+        summerTemps.put(WeatherState.SUNNY, new int[]{20, 32});
+        summerTemps.put(WeatherState.CLOUDY, new int[]{18, 28});
+        summerTemps.put(WeatherState.RAINY, new int[]{16, 24});
+        summerTemps.put(WeatherState.STORMY, new int[]{22, 28});
+        summerTemps.put(WeatherState.FOGGY, new int[]{15, 22});
+        tempMap.put(Season.SUMMER, Collections.unmodifiableMap(summerTemps));
+
+        Map<WeatherState, int[]> autumnTemps = new EnumMap<>(WeatherState.class);
+        autumnTemps.put(WeatherState.SUNNY, new int[]{15, 25});
+        autumnTemps.put(WeatherState.CLOUDY, new int[]{13, 22});
+        autumnTemps.put(WeatherState.RAINY, new int[]{10, 20});
+        autumnTemps.put(WeatherState.STORMY, new int[]{13, 24});
+        autumnTemps.put(WeatherState.FOGGY, new int[]{10, 18});
+        tempMap.put(Season.AUTUMN, Collections.unmodifiableMap(autumnTemps));
+
+        BASE_TEMPERATURES = Collections.unmodifiableMap(tempMap);
+    }
+
+    // 基础湿度范围(百分比,按天气状态)
+    private static final Map<WeatherState, int[]> BASE_HUMIDITIES;
+
+    static {
+        Map<WeatherState, int[]> tempMap = new EnumMap<>(WeatherState.class);
+        tempMap.put(WeatherState.SUNNY, new int[]{30, 60});
+        tempMap.put(WeatherState.CLOUDY, new int[]{50, 80});
+        tempMap.put(WeatherState.RAINY, new int[]{75, 90});
+        tempMap.put(WeatherState.STORMY, new int[]{70, 88});
+        tempMap.put(WeatherState.FOGGY, new int[]{70, 85});
+        BASE_HUMIDITIES = Collections.unmodifiableMap(tempMap);
+    }
+
+    // 新增:基础污染物范围(按天气状态和季节)
+    private static final Map<Season, Map<WeatherState, Map<PollutantType, int[]>>> BASE_POLLUTANTS;
+
+    static {
+        Map<Season, Map<WeatherState, Map<PollutantType, int[]>>> tempMap = new EnumMap<>(Season.class);
+
+        // 冬季污染物范围
+        Map<WeatherState, Map<PollutantType, int[]>> winterPollutants = new EnumMap<>(WeatherState.class);
+        winterPollutants.put(WeatherState.SUNNY, createPollutantMap(25, 75, 30, 80, 40, 90, 45, 65));
+        winterPollutants.put(WeatherState.CLOUDY, createPollutantMap(20, 60, 25, 70, 35, 90, 40, 60));
+        winterPollutants.put(WeatherState.RAINY, createPollutantMap(5, 20, 10, 30, 20, 50, 35, 55));
+        winterPollutants.put(WeatherState.STORMY, createPollutantMap(10, 30, 15, 40, 25, 60, 50, 70));
+        winterPollutants.put(WeatherState.FOGGY, createPollutantMap(70, 90, 80, 100, 90, 120, 40, 60));
+        tempMap.put(Season.WINTER, Collections.unmodifiableMap(winterPollutants));
+
+        // 春季污染物范围
+        Map<WeatherState, Map<PollutantType, int[]>> springPollutants = new EnumMap<>(WeatherState.class);
+        springPollutants.put(WeatherState.SUNNY, createPollutantMap(15, 40, 20, 50, 30, 70, 50, 70));
+        springPollutants.put(WeatherState.CLOUDY, createPollutantMap(20, 50, 25, 60, 35, 80, 45, 65));
+        springPollutants.put(WeatherState.RAINY, createPollutantMap(5, 25, 10, 35, 20, 60, 40, 60));
+        springPollutants.put(WeatherState.STORMY, createPollutantMap(10, 35, 15, 45, 25, 70, 55, 75));
+        springPollutants.put(WeatherState.FOGGY, createPollutantMap(40, 80, 60, 100, 80, 110, 45, 65));
+        tempMap.put(Season.SPRING, Collections.unmodifiableMap(springPollutants));
+
+        // 夏季污染物范围
+        Map<WeatherState, Map<PollutantType, int[]>> summerPollutants = new EnumMap<>(WeatherState.class);
+        summerPollutants.put(WeatherState.SUNNY, createPollutantMap(10, 30, 15, 40, 25, 60, 55, 75));
+        summerPollutants.put(WeatherState.CLOUDY, createPollutantMap(15, 35, 20, 45, 30, 70, 50, 70));
+        summerPollutants.put(WeatherState.RAINY, createPollutantMap(5, 20, 10, 30, 20, 50, 45, 65));
+        summerPollutants.put(WeatherState.STORMY, createPollutantMap(8, 25, 12, 35, 22, 55, 60, 80));
+        summerPollutants.put(WeatherState.FOGGY, createPollutantMap(30, 80, 50, 100, 70, 90, 50, 70));
+        tempMap.put(Season.SUMMER, Collections.unmodifiableMap(summerPollutants));
+
+        // 秋季污染物范围
+        Map<WeatherState, Map<PollutantType, int[]>> autumnPollutants = new EnumMap<>(WeatherState.class);
+        autumnPollutants.put(WeatherState.SUNNY, createPollutantMap(20, 50, 25, 60, 35, 80, 50, 70));
+        autumnPollutants.put(WeatherState.CLOUDY, createPollutantMap(25, 60, 30, 70, 40, 90, 45, 65));
+        autumnPollutants.put(WeatherState.RAINY, createPollutantMap(8, 30, 12, 40, 25, 70, 40, 60));
+        autumnPollutants.put(WeatherState.STORMY, createPollutantMap(12, 40, 18, 50, 30, 80, 55, 75));
+        autumnPollutants.put(WeatherState.FOGGY, createPollutantMap(60, 80, 80, 100, 80, 110, 45, 65));
+        tempMap.put(Season.AUTUMN, Collections.unmodifiableMap(autumnPollutants));
+
+        BASE_POLLUTANTS = Collections.unmodifiableMap(tempMap);
+    }
+
+    // 新增:基础风速范围(米/秒,按季节和天气状态)
+    private static final Map<Season, Map<WeatherState, int[]>> BASE_WIND_SPEEDS;
+
+    static {
+        Map<Season, Map<WeatherState, int[]>> tempMap = new EnumMap<>(Season.class);
+
+        Map<WeatherState, int[]> winterSpeeds = new EnumMap<>(WeatherState.class);
+        winterSpeeds.put(WeatherState.SUNNY, new int[]{1, 5});
+        winterSpeeds.put(WeatherState.CLOUDY, new int[]{2, 8});
+        winterSpeeds.put(WeatherState.RAINY, new int[]{3, 10});
+        winterSpeeds.put(WeatherState.STORMY, new int[]{6, 10});
+//        winterSpeeds.put(WeatherState.STORMY, new int[]{10, 20});
+        winterSpeeds.put(WeatherState.FOGGY, new int[]{0, 3});
+        tempMap.put(Season.WINTER, Collections.unmodifiableMap(winterSpeeds));
+
+        Map<WeatherState, int[]> springSpeeds = new EnumMap<>(WeatherState.class);
+        springSpeeds.put(WeatherState.SUNNY, new int[]{2, 6});
+        springSpeeds.put(WeatherState.CLOUDY, new int[]{3, 8});
+//        springSpeeds.put(WeatherState.RAINY, new int[]{4, 12});
+        springSpeeds.put(WeatherState.RAINY, new int[]{4, 9});
+//        springSpeeds.put(WeatherState.STORMY, new int[]{12, 25});
+        springSpeeds.put(WeatherState.STORMY, new int[]{6, 10});
+        springSpeeds.put(WeatherState.FOGGY, new int[]{1, 4});
+        tempMap.put(Season.SPRING, Collections.unmodifiableMap(springSpeeds));
+
+        Map<WeatherState, int[]> summerSpeeds = new EnumMap<>(WeatherState.class);
+        summerSpeeds.put(WeatherState.SUNNY, new int[]{1, 4});
+        summerSpeeds.put(WeatherState.CLOUDY, new int[]{2, 6});
+        summerSpeeds.put(WeatherState.RAINY, new int[]{3, 9});
+//        summerSpeeds.put(WeatherState.STORMY, new int[]{15, 30});
+        summerSpeeds.put(WeatherState.STORMY, new int[]{6, 10});
+        summerSpeeds.put(WeatherState.FOGGY, new int[]{0, 2});
+        tempMap.put(Season.SUMMER, Collections.unmodifiableMap(summerSpeeds));
+
+        Map<WeatherState, int[]> autumnSpeeds = new EnumMap<>(WeatherState.class);
+        autumnSpeeds.put(WeatherState.SUNNY, new int[]{2, 7});
+//        autumnSpeeds.put(WeatherState.CLOUDY, new int[]{3, 10});
+        autumnSpeeds.put(WeatherState.CLOUDY, new int[]{3, 7});
+//        autumnSpeeds.put(WeatherState.RAINY, new int[]{4, 14});
+        autumnSpeeds.put(WeatherState.RAINY, new int[]{4, 9});
+//        autumnSpeeds.put(WeatherState.STORMY, new int[]{10, 25});
+        autumnSpeeds.put(WeatherState.STORMY, new int[]{6, 10});
+        autumnSpeeds.put(WeatherState.FOGGY, new int[]{1, 5});
+        tempMap.put(Season.AUTUMN, Collections.unmodifiableMap(autumnSpeeds));
+
+        BASE_WIND_SPEEDS = Collections.unmodifiableMap(tempMap);
+    }
+
+    // 新增:主导风向概率(按季节)
+    private static final Map<Season, Map<WindDirection, Double>> DOMINANT_WIND_DIRECTIONS;
+
+    static {
+        Map<Season, Map<WindDirection, Double>> tempMap = new EnumMap<>(Season.class);
+
+        // 冬季主导风向(北风、西北风)
+        Map<WindDirection, Double> winterDirs = new EnumMap<>(WindDirection.class);
+        winterDirs.put(WindDirection.N, 0.35);
+        winterDirs.put(WindDirection.NNW, 0.20);
+        winterDirs.put(WindDirection.NW, 0.15);
+        winterDirs.put(WindDirection.NNE, 0.10);
+        winterDirs.put(WindDirection.W, 0.05);
+        winterDirs.put(WindDirection.E, 0.05);
+        winterDirs.put(WindDirection.S, 0.05);
+        winterDirs.put(WindDirection.OTHER, 0.05);
+        tempMap.put(Season.WINTER, Collections.unmodifiableMap(winterDirs));
+
+        // 春季主导风向(东南风、南风)
+        Map<WindDirection, Double> springDirs = new EnumMap<>(WindDirection.class);
+        springDirs.put(WindDirection.SE, 0.30);
+        springDirs.put(WindDirection.S, 0.25);
+        springDirs.put(WindDirection.SSE, 0.15);
+        springDirs.put(WindDirection.ESE, 0.10);
+        springDirs.put(WindDirection.E, 0.05);
+        springDirs.put(WindDirection.N, 0.05);
+        springDirs.put(WindDirection.W, 0.05);
+        springDirs.put(WindDirection.OTHER, 0.05);
+        tempMap.put(Season.SPRING, Collections.unmodifiableMap(springDirs));
+
+        // 夏季主导风向(南风、西南风)
+        Map<WindDirection, Double> summerDirs = new EnumMap<>(WindDirection.class);
+        summerDirs.put(WindDirection.S, 0.30);
+        summerDirs.put(WindDirection.SSW, 0.20);
+        summerDirs.put(WindDirection.SW, 0.15);
+        summerDirs.put(WindDirection.SSE, 0.10);
+        summerDirs.put(WindDirection.W, 0.05);
+        summerDirs.put(WindDirection.N, 0.05);
+        summerDirs.put(WindDirection.E, 0.05);
+        summerDirs.put(WindDirection.OTHER, 0.10);
+        tempMap.put(Season.SUMMER, Collections.unmodifiableMap(summerDirs));
+
+        // 秋季主导风向(西北风、西风)
+        Map<WindDirection, Double> autumnDirs = new EnumMap<>(WindDirection.class);
+        autumnDirs.put(WindDirection.NW, 0.30);
+        autumnDirs.put(WindDirection.W, 0.25);
+        autumnDirs.put(WindDirection.WNW, 0.15);
+        autumnDirs.put(WindDirection.NNW, 0.10);
+        autumnDirs.put(WindDirection.N, 0.05);
+        autumnDirs.put(WindDirection.S, 0.05);
+        autumnDirs.put(WindDirection.E, 0.05);
+        autumnDirs.put(WindDirection.OTHER, 0.05);
+        tempMap.put(Season.AUTUMN, Collections.unmodifiableMap(autumnDirs));
+
+        DOMINANT_WIND_DIRECTIONS = Collections.unmodifiableMap(tempMap);
+    }
+
+    // 辅助方法:创建污染物范围映射
+    private static Map<PollutantType, int[]> createPollutantMap(int pm25Min, int pm25Max,
+                                                                int pm10Min, int pm10Max,
+                                                                int pm100Min, int pm100Max,
+                                                                int noiseMin, int noiseMax) {
+        Map<PollutantType, int[]> map = new EnumMap<>(PollutantType.class);
+        map.put(PollutantType.PM2_5, new int[]{pm25Min, pm25Max});
+        map.put(PollutantType.PM10, new int[]{pm10Min, pm10Max});
+        map.put(PollutantType.PM100, new int[]{pm100Min, pm100Max});
+        map.put(PollutantType.NOISE, new int[]{noiseMin, noiseMax});
+        return Collections.unmodifiableMap(map);
+    }
+
+    // 辅助方法:获取随机风向(考虑季节主导风向)
+    private static WindDirection getRandomWindDirection(Season season, Random random) {
+        Map<WindDirection, Double> probs = DOMINANT_WIND_DIRECTIONS.get(season);
+        double rand = random.nextDouble();
+        double cumulative = 0.0;
+
+        for (Map.Entry<WindDirection, Double> entry : probs.entrySet()) {
+            cumulative += entry.getValue();
+            if (rand < cumulative) {
+                if (entry.getKey() == WindDirection.OTHER) {
+                    while (true){
+                        List<WindDirection> allDirs = Arrays.asList(WindDirection.values());
+                        WindDirection direction = allDirs.get(random.nextInt(allDirs.size()));
+                        if(!direction.equals(WindDirection.OTHER)){
+                            return direction;
+                        }
+                    }
+
+                }
+                return entry.getKey();
+            }
+        }
+
+        // 默认返回北风
+        return WindDirection.N;
+    }
+
+    // 辅助方法:获取风向角度(0-360度)
+    private static int getDirectionAngle(WindDirection direction) {
+        return direction.ordinal() * 22; // 每个方位22.5度,近似为22度
+    }
+
+    // 辅助方法:计算两个风向之间的角度差(考虑圆形)
+    private static int getAngleDifference(int angle1, int angle2) {
+        int diff = Math.abs(angle1 - angle2);
+        return Math.min(diff, 360 - diff);
+    }
+
+    public static List<WeatherData> simulateYearlyWeather(LocalDateTime currentTime, LocalDateTime endTime) {
+        List<WeatherData> yearlyData = new ArrayList<>();
+        Random random = new Random();
+
+//        LocalDateTime currentTime = LocalDateTime.of(year, 1, 1, 0, 0);
+//        LocalDateTime endTime = LocalDateTime.of(year + 1, 1, 1, 0, 0);
+
+        // 初始状态
+        WeatherState currentState = WeatherState.CLOUDY;
+        Season currentSeason = getSeason(currentTime);
+
+        // 使用平均值初始化
+        double currentTemp = getAverageTemperature(currentSeason, currentState);
+        double currentHumidity = getAverageHumidity(currentState);
+        double currentPM25 = getAveragePollutant(currentSeason, currentState, PollutantType.PM2_5);
+        double currentPM10 = getAveragePollutant(currentSeason, currentState, PollutantType.PM10);
+        double currentPM100 = getAveragePollutant(currentSeason, currentState, PollutantType.PM100);
+        double currentNoise = getAveragePollutant(currentSeason, currentState, PollutantType.NOISE);
+        double currentWindSpeed = getAverageWindSpeed(currentSeason, currentState);
+        WindDirection currentWindDir = getRandomWindDirection(currentSeason, random);
+
+        // 状态切换时的过渡参数
+        WeatherState targetState = currentState;
+        int transitionSteps = 0;
+        final int TOTAL_TRANSITION_STEPS = 12; // 过渡时长 = 12*3min = 36分钟
+
+        while (currentTime.isBefore(endTime)) {
+            // 每8小时检查状态变化
+            if (currentTime.getMinute() == 0 && currentTime.getHour() % 8 == 0) {
+                currentSeason = getSeason(currentTime);
+                WeatherState newState = getNextWeatherState(currentState, currentSeason, random);
+
+                if (!newState.equals(currentState)) {
+                    targetState = newState;
+                    transitionSteps = 0;
+//                    System.out.println("开始状态过渡: " + currentState + " → " + targetState + " at " + currentTime);
+                }
+            }
+
+            // 计算过渡比例 (0.0=当前状态 → 1.0=目标状态)
+            double transitionRatio = transitionSteps < TOTAL_TRANSITION_STEPS ?
+                    (double) transitionSteps / TOTAL_TRANSITION_STEPS : 1.0;
+
+            // 获取混合状态下的参数范围
+            int[] tempRange = getTransitionTemperatureRange(currentSeason, currentState, targetState, transitionRatio);
+            int[] humidityRange = getTransitionHumidityRange(currentState, targetState, transitionRatio);
+            Map<PollutantType, int[]> pollutantRanges = getTransitionPollutantRanges(
+                    currentSeason, currentState, targetState, transitionRatio);
+            int[] windSpeedRange = getTransitionWindSpeedRange(currentSeason, currentState, targetState, transitionRatio);
+
+            // 获取目标风向(只在过渡开始时确定)
+            WindDirection targetWindDir = transitionSteps == 0 ?
+                    getRandomWindDirection(currentSeason, random) : currentWindDir;
+
+            // 生成数据
+            double timeFactor = calculateTimeFactor(currentTime);
+            double seasonProgress = getSeasonProgress(currentTime);
+
+            currentTemp = generateSmoothTemperature(
+                    tempRange, currentTime, timeFactor, seasonProgress,
+                    random, currentTemp, transitionRatio);
+
+            currentHumidity = generateSmoothHumidity(
+                    humidityRange, currentTime, timeFactor,
+                    random, currentHumidity, transitionRatio);
+
+            // 生成污染物数据
+            currentPM25 = generateSmoothPollutant(
+                    pollutantRanges.get(PollutantType.PM2_5),
+                    random, currentPM25, transitionRatio, 1.5, 0.3);
+
+            currentPM10 = generateSmoothPollutant(
+                    pollutantRanges.get(PollutantType.PM10),
+                    random, currentPM10, transitionRatio, 2.0, 0.4);
+
+            currentPM100 = generateSmoothPollutant(
+                    pollutantRanges.get(PollutantType.PM100),
+                    random, currentPM100, transitionRatio, 3.0, 0.5);
+
+            // 生成噪音
+            currentNoise = generateSmoothNoise(
+                    pollutantRanges.get(PollutantType.NOISE),
+                    currentTime, random, currentNoise, transitionRatio);
+
+            // 生成风速
+            currentWindSpeed = generateSmoothWindSpeed(
+                    windSpeedRange, random, currentWindSpeed, transitionRatio);
+
+            // 生成风向(平滑过渡)
+            currentWindDir = generateSmoothWindDirection(
+                    currentWindDir, targetWindDir, transitionRatio, random);
+
+            // 添加到结果集
+            yearlyData.add(new WeatherData(
+                    currentTime,
+                    currentTemp,
+                    currentHumidity,
+                    transitionRatio < 1.0 ? currentState : targetState, // 显示当前有效状态
+                    currentPM25,
+                    currentPM10,
+                    currentPM100,
+                    currentNoise,
+                    currentWindSpeed,
+                    currentWindDir
+            ));
+
+            // 更新状态过渡进度
+            if (transitionSteps < TOTAL_TRANSITION_STEPS) {
+                transitionSteps++;
+                if (transitionSteps == TOTAL_TRANSITION_STEPS) {
+                    currentState = targetState; // 过渡完成
+                }
+            }
+
+            currentTime = currentTime.plusMinutes(TIME_INTERVAL_MINUTES);
+        }
+
+        return yearlyData;
+    }
+
+    // 获取过渡期间的温度范围
+    private static int[] getTransitionTemperatureRange(Season season,
+                                                       WeatherState from,
+                                                       WeatherState to,
+                                                       double ratio) {
+        int[] fromRange = BASE_TEMPERATURES.get(season).get(from);
+        int[] toRange = BASE_TEMPERATURES.get(season).get(to);
+
+        return new int[]{
+                (int) Math.round(fromRange[0] * (1 - ratio) + toRange[0] * ratio),
+                (int) Math.round(fromRange[1] * (1 - ratio) + toRange[1] * ratio)
+        };
+    }
+
+    // 获取过渡期间的湿度范围
+    private static int[] getTransitionHumidityRange(WeatherState from,
+                                                    WeatherState to,
+                                                    double ratio) {
+        int[] fromRange = BASE_HUMIDITIES.get(from);
+        int[] toRange = BASE_HUMIDITIES.get(to);
+
+        return new int[]{
+                (int) Math.round(fromRange[0] * (1 - ratio) + toRange[0] * ratio),
+                (int) Math.round(fromRange[1] * (1 - ratio) + toRange[1] * ratio)
+        };
+    }
+
+    // 新增:获取过渡期间的污染物范围
+    private static Map<PollutantType, int[]> getTransitionPollutantRanges(Season season,
+                                                                          WeatherState from,
+                                                                          WeatherState to,
+                                                                          double ratio) {
+        Map<PollutantType, int[]> fromRanges = BASE_POLLUTANTS.get(season).get(from);
+        Map<PollutantType, int[]> toRanges = BASE_POLLUTANTS.get(season).get(to);
+
+        Map<PollutantType, int[]> result = new EnumMap<>(PollutantType.class);
+
+        for (PollutantType type : PollutantType.values()) {
+            int[] fromRange = fromRanges.get(type);
+            int[] toRange = toRanges.get(type);
+
+            int min = (int) Math.round(fromRange[0] * (1 - ratio) + toRange[0] * ratio);
+            int max = (int) Math.round(fromRange[1] * (1 - ratio) + toRange[1] * ratio);
+            result.put(type, new int[]{min, max});
+        }
+
+        return Collections.unmodifiableMap(result);
+    }
+
+    // 新增:获取过渡期间的风速范围
+    private static int[] getTransitionWindSpeedRange(Season season,
+                                                     WeatherState from,
+                                                     WeatherState to,
+                                                     double ratio) {
+        int[] fromRange = BASE_WIND_SPEEDS.get(season).get(from);
+        int[] toRange = BASE_WIND_SPEEDS.get(season).get(to);
+
+        return new int[]{
+                (int) Math.round(fromRange[0] * (1 - ratio) + toRange[0] * ratio),
+                (int) Math.round(fromRange[1] * (1 - ratio) + toRange[1] * ratio)
+        };
+    }
+
+    // 辅助方法:获取平均温度
+    private static double getAverageTemperature(Season season, WeatherState state) {
+        int[] range = BASE_TEMPERATURES.get(season).get(state);
+        return (range[0] + range[1]) / 2.0;
+    }
+
+    // 辅助方法:获取平均湿度
+    private static double getAverageHumidity(WeatherState state) {
+        int[] range = BASE_HUMIDITIES.get(state);
+        return (range[0] + range[1]) / 2.0;
+    }
+
+    // 辅助方法:获取平均污染物浓度
+    private static double getAveragePollutant(Season season, WeatherState state, PollutantType type) {
+        int[] range = BASE_POLLUTANTS.get(season).get(state).get(type);
+        return (range[0] + range[1]) / 2.0;
+    }
+
+    // 辅助方法:获取平均风速
+    private static double getAverageWindSpeed(Season season, WeatherState state) {
+        int[] range = BASE_WIND_SPEEDS.get(season).get(state);
+        return (range[0] + range[1]) / 2.0;
+    }
+
+    // 使用马尔可夫链获取下一个天气状态
+    private static WeatherState getNextWeatherState(WeatherState currentState, Season season, Random random) {
+        double[][] matrix = TRANSITION_MATRICES.get(season);
+        double rand = random.nextDouble();
+        double cumulativeProb = 0.0;
+
+        for (int i = 0; i < matrix[currentState.ordinal()].length; i++) {
+            cumulativeProb += matrix[currentState.ordinal()][i];
+            if (rand < cumulativeProb) {
+                return WeatherState.values()[i];
+            }
+        }
+
+        return currentState;
+    }
+
+    // 计算时间影响因子(昼夜节律)
+    private static double calculateTimeFactor(LocalDateTime time) {
+        double hour = time.getHour() + time.getMinute() / 60.0;
+        // 使用两个正弦波叠加模拟昼夜温度变化
+        return 0.6 * Math.sin((hour / 24) * 2 * Math.PI - Math.PI / 2) +
+                0.4 * Math.sin((hour / 12) * 2 * Math.PI);
+    }
+
+    // 获取季节进度(0.0到1.0)
+    private static double getSeasonProgress(LocalDateTime time) {
+        int dayOfYear = time.getDayOfYear();
+        if (dayOfYear < 80) return dayOfYear / 80.0;         // 冬季
+        else if (dayOfYear < 172) return (dayOfYear - 80) / 92.0; // 春季
+        else if (dayOfYear < 264) return (dayOfYear - 172) / 92.0;// 夏季
+        else if (dayOfYear < 355) return (dayOfYear - 264) / 91.0;// 秋季
+        else return (dayOfYear - 355) / 10.0;                    // 冬季
+    }
+
+    // 平滑温度生成(过渡感知)
+    private static double generateSmoothTemperature(int[] range, LocalDateTime time,
+                                                    double timeFactor, double seasonProgress,
+                                                    Random random, double previousTemp,
+                                                    double transitionRatio) {
+        // 基础温度计算(考虑过渡比例)
+        double baseTemp = range[0] + (range[1] - range[0]) * seasonProgress;
+
+        // 昼夜影响(过渡期间减弱)
+        double diurnalEffect = timeFactor * (range[1] - range[0]) *
+                (0.25 * (1 - transitionRatio) + 0.15 * transitionRatio);
+
+        // 随机波动(过渡期间减小)
+        double randomEffect = (random.nextDouble() - 0.5) *
+                (1.0 * (1 - transitionRatio) + 0.3 * transitionRatio);
+
+        // 计算目标温度
+        double targetTemp = baseTemp + diurnalEffect + randomEffect;
+
+        // 应用变化率限制(过渡期间更严格)
+        double maxDelta = 0.3 + 0.2 * (1 - transitionRatio); // 0.3~0.5℃
+        targetTemp = Math.max(previousTemp - maxDelta,
+                Math.min(previousTemp + maxDelta, targetTemp));
+
+        // 确保在合理范围内
+        return Math.max(range[0], Math.min(range[1], targetTemp));
+    }
+
+    // 平滑湿度生成(过渡感知)
+    private static double generateSmoothHumidity(int[] range, LocalDateTime time,
+                                                 double timeFactor, Random random,
+                                                 double previousHumidity,
+                                                 double transitionRatio) {
+        // 基础湿度(考虑过渡比例)
+        double baseHumidity = range[0] + (range[1] - range[0]) * random.nextDouble();
+
+        // 昼夜影响(过渡期间减弱)
+        double diurnalEffect = timeFactor * 9 * (1 - 0.5 * transitionRatio);
+
+        // 随机波动(过渡期间减小)
+        double randomEffect = (random.nextDouble() - 0.5) * 6 * (1 - 0.7 * transitionRatio);
+
+        // 计算目标湿度
+        double targetHumidity = baseHumidity - diurnalEffect + randomEffect;
+
+        // 应用变化率限制(过渡期间更严格)
+        double maxDelta = 0.8 + 0.7 * (1 - transitionRatio); // 0.8~1.5%
+        targetHumidity = Math.max(previousHumidity - maxDelta,
+                Math.min(previousHumidity + maxDelta, targetHumidity));
+
+        // 确保在合理范围内
+        return Math.max(range[0], Math.min(range[1], targetHumidity));
+    }
+
+    // 新增:平滑污染物生成(通用方法)
+    private static double generateSmoothPollutant(int[] range, Random random,
+                                                  double previousValue, double transitionRatio,
+                                                  double baseDelta, double fluctuationFactor) {
+        // 基础值(考虑过渡比例)
+        double baseValue = range[0] + (range[1] - range[0]) * random.nextDouble();
+
+        // 随机波动(过渡期间减小)
+        double randomEffect = (random.nextDouble() - 0.5) * fluctuationFactor * range[1] *
+                (1 - 0.7 * transitionRatio);
+
+        // 计算目标值
+        double targetValue = baseValue + randomEffect;
+
+        // 应用变化率限制(过渡期间更严格)
+        double maxDelta = baseDelta + baseDelta * (1 - transitionRatio);
+        targetValue = Math.max(previousValue - maxDelta,
+                Math.min(previousValue + maxDelta, targetValue));
+
+        // 确保在合理范围内
+        return Math.max(range[0], Math.min(range[1], targetValue));
+    }
+
+    // 新增:平滑噪音生成(考虑昼夜节律)
+    private static double generateSmoothNoise(int[] range, LocalDateTime time,
+                                              Random random, double previousNoise,
+                                              double transitionRatio) {
+        // 基础噪音
+        double baseNoise = range[0] + (range[1] - range[0]) * random.nextDouble();
+
+        // 昼夜影响(白天高,夜晚低)
+        double hour = time.getHour() + time.getMinute() / 60.0;
+        double diurnalEffect = 0;
+        if (hour >= 6 && hour <= 22) { // 白天(6:00-22:00)
+            // 早晚高峰
+            if ((hour >= 7 && hour <= 9) || (hour >= 17 && hour <= 19)) {
+                diurnalEffect = 10 + 5 * Math.sin((hour - 8) * Math.PI / 6);
+            } else {
+                diurnalEffect = 5 * Math.sin((hour - 12) * Math.PI / 12);
+            }
+        } else { // 夜间
+            diurnalEffect = -15;
+        }
+
+        // 随机波动
+        double randomEffect = (random.nextDouble() - 0.5) * 5;
+
+        // 计算目标噪音
+        double targetNoise = baseNoise + diurnalEffect + randomEffect;
+
+        // 应用变化率限制
+        double maxDelta = 2.0 + 1.5 * (1 - transitionRatio); // 2.0~3.5 dB
+        targetNoise = Math.max(previousNoise - maxDelta,
+                Math.min(previousNoise + maxDelta, targetNoise));
+
+        // 确保在合理范围内
+        return Math.max(range[0], Math.min(range[1], targetNoise));
+    }
+
+    // 新增:平滑风速生成
+    private static double generateSmoothWindSpeed(int[] range, Random random,
+                                                  double previousSpeed,
+                                                  double transitionRatio) {
+        // 基础风速(考虑过渡比例)
+        double baseSpeed = range[0] + (range[1] - range[0]) * random.nextDouble();
+
+        // 随机波动(过渡期间减小)
+        double randomEffect = (random.nextDouble() - 0.5) * 2.0 * (1 - 0.7 * transitionRatio);
+
+        // 计算目标风速
+        double targetSpeed = baseSpeed + randomEffect;
+
+        // 应用变化率限制(过渡期间更严格)
+        double maxDelta = 0.5 + 0.4 * (1 - transitionRatio); // 0.5~0.9 m/s
+        targetSpeed = Math.max(previousSpeed - maxDelta,
+                Math.min(previousSpeed + maxDelta, targetSpeed));
+
+        // 确保在合理范围内
+        return Math.max(range[0], Math.min(range[1], targetSpeed));
+    }
+
+    // 新增:平滑风向过渡
+    private static WindDirection generateSmoothWindDirection(WindDirection currentDir,
+                                                             WindDirection targetDir,
+                                                             double transitionRatio,
+                                                             Random random) {
+        // 如果已经是目标风向,或者过渡完成
+        if (currentDir == targetDir || transitionRatio >= 1.0) {
+            return targetDir;
+        }
+
+        // 有20%的概率在非过渡期间改变风向
+        if (transitionRatio == 0 && random.nextDouble() < 0.2) {
+            // 随机小幅变化(±1个方位)
+            int currentIndex = currentDir.ordinal();
+            int change = random.nextBoolean() ? 1 : -1;
+            int newIndex = (currentIndex + change + WindDirection.values().length) % WindDirection.values().length;
+            return WindDirection.values()[newIndex];
+        }
+
+        // 过渡期间:逐步转向目标风向
+        int currentAngle = getDirectionAngle(currentDir);
+        int targetAngle = getDirectionAngle(targetDir);
+
+        // 计算最短转向路径
+        int diff1 = (targetAngle - currentAngle + 360) % 360;
+        int diff2 = diff1 - 360;
+        int shortestDiff = Math.abs(diff1) < Math.abs(diff2) ? diff1 : diff2;
+
+        // 最大转向角度(每次最多22.5度)
+        int maxTurn = (int) (22 * transitionRatio);
+        int actualTurn = Math.min(Math.abs(shortestDiff), maxTurn);
+        if (shortestDiff < 0) actualTurn = -actualTurn;
+
+        // 计算新角度
+        int newAngle = (currentAngle + actualTurn + 360) % 360;
+
+        // 转换为最接近的风向
+        int index = (int) Math.round(newAngle / 22.5) % 16;
+        return WindDirection.values()[index];
+    }
+
+    // 获取当前季节
+    private static Season getSeason(LocalDateTime time) {
+        int month = time.getMonthValue();
+        if (month == 12 || month <= 2) return Season.WINTER;
+        if (month <= 5) return Season.SPRING;
+        if (month <= 8) return Season.SUMMER;
+        return Season.AUTUMN;
+    }
+
+//    public static void main(String[] args) {
+//        int year = 2024;
+//        List<WeatherData> yearlyData = simulateYearlyWeather(year);
+//
+//        // 打印前3天的数据(每小时间隔)
+//        System.out.println("时间戳\t\t\t\t温度\t湿度\t天气状态\tPM2.5\tPM10\tPM100\t噪音\t风速和风向");
+//        for (int i = 0; i < 480; i += 20) { // 每20个点 = 1小时 (3分钟间隔)
+//            WeatherData data = yearlyData.get(i);
+//            System.out.println(data);
+//        }
+//        System.out.println(yearlyData.size());
+
+        // 统计不同天气下的平均风速
+//        Map<WeatherState, Double> avgWindSpeedByWeather = yearlyData.stream()
+//                .collect(Collectors.groupingBy(WeatherData::getWeatherState,
+//                        Collectors.averagingDouble(WeatherData::getWindSpeed)));
+//
+//        System.out.println("\n不同天气状态下的平均风速:");
+//        avgWindSpeedByWeather.forEach((state, avg) ->
+//                System.out.printf("%s: %.1f m/s%n", state, avg));
+
+        // 统计主导风向分布
+//        Map<WindDirection, Long> windDirectionDistribution = yearlyData.stream()
+//                .collect(Collectors.groupingBy(WeatherData::getWindDirection, Collectors.counting()));
+
+//        System.out.println("\n风向分布:");
+//        windDirectionDistribution.entrySet().stream()
+//                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
+//                .forEach(entry ->
+//                        System.out.printf("%s: %.1f%%%n", entry.getKey(),
+//                                entry.getValue() * 100.0 / yearlyData.size()));
+//    }
+}

+ 8 - 0
easydo-mes/src/main/resources/banner.txt

@@ -0,0 +1,8 @@
+       _                 _           _
+      | |               | |         (_)
+   ___| |______ __ _  __| |_ __ ___  _ _ __
+  / _ | |______/ _` |/ _` | '_ ` _ \| | '_ \
+ |  __| |     | (_| | (_| | | | | | | | | | |
+  \___|_|      \__,_|\__,_|_| |_| |_|_|_| |_|
+
+ :: Spring Boot ::       (v2.1.0.RELEASE)

+ 104 - 0
easydo-mes/src/main/resources/config/application-owntest.yml

@@ -0,0 +1,104 @@
+#配置数据源
+spring:
+  datasource:
+    druid:
+      db-type: com.alibaba.druid.pool.DruidDataSource
+      driverClassName: com.mysql.cj.jdbc.Driver
+      url: jdbc:mysql://119.167.167.11:43307/mes?useSSL=false&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8
+      username: root
+      password: _Developer0532
+      # 初始连接数
+      initial-size: 1
+      # 最小连接数
+      min-idle: 10
+      # 最大连接数
+      max-active: 20
+      # 获取连接超时时间
+      max-wait: 5000
+      # 连接有效性检测时间
+      time-between-eviction-runs-millis: 60000
+      # 连接在池中最小生存的时间
+      min-evictable-idle-time-millis: 300000
+      # 连接在池中最大生存的时间
+      max-evictable-idle-time-millis: 900000
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      # 检测连接是否有效
+      validation-query: select 1
+      # 配置监控统计
+      webStatFilter:
+        enabled: true
+      stat-view-servlet:
+        enabled: true
+        url-pattern: /druid/*
+        reset-enable: false
+      filter:
+        stat:
+          enabled: true
+          # 记录慢SQL
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: false
+        wall:
+          config:
+            multi-statement-allow: true
+
+  redis:
+    #数据库索引
+    database: ${REDIS_DB:0}
+    host: ${REDIS_HOST:localhost}
+    port: ${REDIS_PORT:6379}
+    password:
+    #连接超时时间
+    timeout: 5000
+iot:
+  facerec:
+    pictureUri: https://www.qdeasydo.com/api/folder/
+# 登录相关配置
+login:
+  # 登录缓存
+  cache-enable: false
+  #  是否限制单用户登录
+  single: false
+  #  验证码
+  login-code:
+    #  验证码类型配置 查看 LoginProperties 类
+    code-type: arithmetic
+    #  登录图形验证码有效时间/分钟
+    expiration: 2
+    #  验证码高度
+    width: 111
+    #  验证码宽度
+    heigth: 36
+    # 内容长度
+    length: 2
+    # 字体名称,为空则使用默认字体
+    font-name:
+    # 字体大小
+    font-size: 25
+
+#jwt
+jwt:
+  header: Authorization
+  # 令牌前缀
+  token-start-with: Bearer
+  # 必须使用最少88位的Base64对该令牌进行编码
+  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
+  # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
+  token-validity-in-seconds: 14400000
+  # 在线用户key
+  online-key: online-token-
+  # 验证码
+  code-key: code-key-
+  # token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期
+  detect: 1800000
+  # 续期时间范围,默认1小时,单位毫秒
+  renew: 3600000
+
+
+
+# IP 本地解析
+ip:
+  local-parsing: true
+

+ 108 - 0
easydo-mes/src/main/resources/config/application-prod.yml

@@ -0,0 +1,108 @@
+#配置数据源
+spring:
+  datasource:
+    druid:
+      db-type: com.alibaba.druid.pool.DruidDataSource
+      driverClassName: com.mysql.jdbc.Driver
+      url: jdbc:mysql://172.16.100.151:3306/iot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&tinyInt1isBit=false&allowMultiQueries=true&serverTimezone=GMT%2B8
+      username: root
+      password: _Developer0532
+      # 初始连接数
+      initial-size: 5
+      # 最小连接数
+      min-idle: 5
+      # 最大连接数
+      max-active: 10
+      # 获取连接超时时间
+      max-wait: 5000
+      # 连接有效性检测时间
+      time-between-eviction-runs-millis: 60000
+      # 连接在池中最小生存的时间
+      min-evictable-idle-time-millis: 300000
+      # 连接在池中最大生存的时间
+      max-evictable-idle-time-millis: 900000
+      test-while-idle: true
+      test-on-borrow: false
+      test-on-return: false
+      # 检测连接是否有效
+      validation-query: select 1
+      # 配置监控统计
+      webStatFilter:
+        enabled: true
+      stat-view-servlet:
+        enabled: true
+        url-pattern: /druid/*
+        reset-enable: false
+      filter:
+        stat:
+          enabled: true
+          # 记录慢SQL
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: false
+        wall:
+          config:
+            multi-statement-allow: true
+
+  redis:
+    #数据库索引
+    database: 0
+    host: redis
+    port: 6379
+    password: _Developer0532
+    #连接超时时间
+    timeout: 5000
+
+iot:
+  facerec:
+    pictureUri: https://www.qdeasydo.com/api/folder/
+    
+# 登录相关配置
+login:
+  # 登录缓存
+  cache-enable: true
+  #  是否限制单用户登录
+  single: false
+  #  验证码
+  login-code:
+    #  验证码类型配置 查看 LoginProperties 类
+    code-type: arithmetic
+    #  登录图形验证码有效时间/分钟
+    expiration: 2
+    #  验证码高度
+    width: 111
+    #  验证码宽度
+    heigth: 36
+    # 内容长度
+    length: 2
+    # 字体名称,为空则使用默认字体,如遇到线上乱码,设置其他字体即可
+    font-name:
+    # 字体大小
+    font-size: 25
+
+#jwt
+jwt:
+  header: Authorization
+  # 令牌前缀
+  token-start-with: Bearer
+  # 必须使用最少88位的Base64对该令牌进行编码
+  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
+  # 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
+  token-validity-in-seconds: 7200000
+  # 在线用户key
+  online-key: online-token-
+  # 验证码
+  code-key: code-key-
+  # token 续期检查时间范围(默认30分钟,单位默认毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期
+  detect: 1800000
+  # 续期时间范围,默认 1小时,这里单位毫秒
+  renew: 3600000
+
+# IP 本地解析
+ip:
+  local-parsing: false
+
+#是否允许生成代码,生产环境设置为false
+generator:
+  enabled: false
+

+ 82 - 0
easydo-mes/src/main/resources/config/application.yml

@@ -0,0 +1,82 @@
+server:
+  port: 8200
+
+spring:
+  freemarker:
+    check-template-location: false
+  profiles:
+    active: @environment@
+  jackson:
+    time-zone: GMT+8
+  data:
+    redis:
+      repositories:
+        enabled: false
+  servlet:
+    multipart:
+      enabled: true
+      max-file-size: 50MB
+      max-request-size: 50MB
+      resolve-lazily: true
+
+  #配置 Jpa
+  jpa:
+    properties:
+      hibernate:
+        ddl-auto: none
+        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
+    open-in-view: true
+
+task:
+  pool:
+    # 核心线程池大小
+    core-pool-size: 10
+    # 最大线程数
+    max-pool-size: 30
+    # 活跃时间
+    keep-alive-seconds: 60
+    # 队列容量
+    queue-capacity: 50
+
+
+#密码加密传输,前端公钥加密,后端私钥解密
+rsa:
+  private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
+
+mybatis-plus:
+  configuration:
+    map-underscore-to-camel-case: true
+    auto-mapping-behavior: full
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  mapper-locations: classpath*:/mapper/*Mapper.xml
+  global-config:
+    db-config:
+      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
+      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+#MyBatis配置
+mybatis:
+  type-aliases-package: com.pjb.entity #别名定义
+  configuration:
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #指定 MyBatis 所用日志的具体实现,未指定时将自动查找
+    map-underscore-to-camel-case: true #开启自动驼峰命名规则(camel case)映射
+    lazy-loading-enabled: true #开启延时加载开关
+    aggressive-lazy-loading: false #将积极加载改为消极加载(即按需加载),默认值就是false
+    lazy-load-trigger-methods: "" #阻挡不相干的操作触发,实现懒加载
+    cache-enabled: true #打开全局缓存开关(二级环境),默认值就是true
+
+#MyBatis使用pageHelper分页
+pagehelper:
+  helper-dialect: mysql
+  reasonable: true
+  support-methods-arguments: true
+
+#邮箱验证码有效时间/秒
+code:
+  expiration: 300
+
+minio:
+  endpoint: http://minio.qdeasydo.com  # MinIO服务器地址
+  access-key: minioadm      # 访问密钥
+  secret-key: minio@123      # 秘密密钥
+  ai-bucket-name: airecord           # 默认存储桶名称
+  secure: false                    # 是否使用HTTPS

BIN
easydo-mes/src/main/resources/ip2region/ip2region.db


+ 21 - 0
easydo-mes/src/main/resources/log4j.properties

@@ -0,0 +1,21 @@
+#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
+log4j.rootLogger=DEBUG,console,file
+#控制台输出的相关设置
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.Target=System.out
+log4j.appender.console.Threshold=DEBUG
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
+#文件输出的相关设置
+log4j.appender.file=org.apache.log4j.RollingFileAppender
+log4j.appender.file.File=./log/kuang.log
+log4j.appender.file.MaxFileSize=10mb
+log4j.appender.file.Threshold=DEBUG
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
+#日志输出级别
+log4j.logger.org.mybatis=DEBUG
+log4j.logger.java.sql=DEBUG
+log4j.logger.java.sql.Statement=DEBUG
+log4j.logger.java.sql.ResultSet=DEBUG
+log4j.logger.java.sql.PreparedStatement=DEBUG

+ 45 - 0
easydo-mes/src/main/resources/logback.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="30 seconds" debug="false">
+    <contextName>elAdmin</contextName>
+    <property name="log.charset" value="UTF-8" />
+    <property name="log.pattern" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %gray(%msg%n)" />
+
+    <!--输出到控制台-->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+            <charset>${log.charset}</charset>
+        </encoder>
+    </appender>
+
+    <!--普通日志输出到控制台-->
+    <root level="INFO">
+        <appender-ref ref="console" />
+    </root>
+
+    <!--监控sql日志输出 -->
+    <logger name="jdbc.sqlonly" level="OFF" additivity="false">
+        <appender-ref ref="console" />
+    </logger>
+
+    <logger name="jdbc.resultset" level="OFF" additivity="false">
+        <appender-ref ref="console" />
+    </logger>
+
+    <!--  如想看到表格数据,将OFF改为INFO  -->
+    <logger name="jdbc.resultsettable" level="OFF" additivity="false">
+        <appender-ref ref="console" />
+    </logger>
+
+    <logger name="jdbc.connection" level="OFF" additivity="false">
+        <appender-ref ref="console" />
+    </logger>
+
+    <logger name="jdbc.sqltiming" level="OFF" additivity="false">
+        <appender-ref ref="console" />
+    </logger>
+
+    <logger name="jdbc.audit" level="OFF" additivity="false">
+        <appender-ref ref="console" />
+    </logger>
+</configuration>

+ 45 - 0
easydo-system/pom.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    
+    <parent>
+        <groupId>easydo.technology</groupId>
+        <artifactId>easydo</artifactId>
+        <version>v20220507</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>easydo-system</artifactId>
+
+    <name>用户系统</name>
+    <packaging>jar</packaging>
+
+    <properties>
+        <jjwt.version>0.11.1</jjwt.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>easydo.technology</groupId>
+            <artifactId>easydo-core</artifactId>
+            <version>v1.0</version>
+        </dependency>
+
+        <!-- jwt -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>${jjwt.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+            <version>${jjwt.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+            <version>${jjwt.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 0 - 0
easydo-system/src/main/java/easydo/technology/system/domain/Dept.java


Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov