renqianlong 2 anos atrás
pai
commit
b1cbe08777
100 arquivos alterados com 13817 adições e 206 exclusões
  1. 0 201
      LICENSE
  2. 23 4
      pom.xml
  3. 19 1
      zzbusiness-common/pom.xml
  4. 29 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/DataScope.java
  5. 24 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/DataSource.java
  6. 165 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/Excel.java
  7. 18 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/Excels.java
  8. 41 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/Log.java
  9. 18 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/RepeatSubmit.java
  10. 76 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/config/RuoYiConfig.java
  11. 74 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/config/WxSmallConfig.java
  12. 170 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/constant/Constants.java
  13. 114 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/constant/GenConstants.java
  14. 50 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/constant/ScheduleConstants.java
  15. 63 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/constant/UserConstants.java
  16. 144 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/bo/SysRoleBo.java
  17. 93 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/bo/SysUserEditBo.java
  18. 169 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/controller/BaseController.java
  19. 192 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/AjaxResult.java
  20. 49 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/BaseEntity.java
  21. 40 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/TreeEntity.java
  22. 51 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/TreeSelect.java
  23. 106 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysDept.java
  24. 127 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysDictData.java
  25. 99 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysDictType.java
  26. 127 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysMenu.java
  27. 146 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysRole.java
  28. 195 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysUser.java
  29. 111 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/TopSysRole.java
  30. 176 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/TopSysUser.java
  31. 49 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/model/LoginBody.java
  32. 143 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/model/LoginUser.java
  33. 142 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/model/TopLoginUser.java
  34. 40 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/page/PageDomain.java
  35. 47 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/page/TableDataInfo.java
  36. 49 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/page/TableSupport.java
  37. 344 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/redis/RedisCache.java
  38. 12 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/core/redis/RedisLockEntity.java
  39. 20 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/enums/BusinessStatus.java
  40. 59 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/enums/BusinessType.java
  41. 19 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/enums/DataSourceType.java
  42. 37 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/enums/HttpMethod.java
  43. 24 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/enums/OperatorType.java
  44. 30 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/enums/UserStatus.java
  45. 97 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/BaseException.java
  46. 43 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/CustomException.java
  47. 15 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/DemoModeException.java
  48. 26 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/UtilException.java
  49. 19 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/file/FileException.java
  50. 16 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/file/FileNameLengthLimitExceededException.java
  51. 16 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/file/FileSizeLimitExceededException.java
  52. 72 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/file/InvalidExtensionException.java
  53. 34 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/job/TaskException.java
  54. 16 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/user/CaptchaException.java
  55. 16 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/user/CaptchaExpireException.java
  56. 18 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/user/UserException.java
  57. 16 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/exception/user/UserPasswordNotMatchException.java
  58. 47 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/filter/RepeatableFilter.java
  59. 76 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/filter/RepeatedlyRequestWrapper.java
  60. 92 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/filter/XssFilter.java
  61. 105 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/filter/XssHttpServletRequestWrapper.java
  62. 80 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/type/EncryptHandler.java
  63. 159 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/AES.java
  64. 751 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/DateUtils.java
  65. 217 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/DictUtils.java
  66. 195 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/DoubleUtils.java
  67. 46 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/JavaMailUtils.java
  68. 26 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/MessageUtils.java
  69. 138 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/SecurityUtils.java
  70. 159 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/ServletUtils.java
  71. 52 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/SmsUtils.java
  72. 128 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/SnowflakeIdUtils.java
  73. 73 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/TelPhoneUtils.java
  74. 96 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/Threads.java
  75. 641 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/ToolsUtils.java
  76. 138 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/bank/DCCryptor.java
  77. 169 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/bank/DCHelper.java
  78. 59 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/bank/GENKey.java
  79. 77 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/FileTypeUtils.java
  80. 390 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/FileUploadUtils.java
  81. 569 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/FileUtils.java
  82. 131 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/ImageUtils.java
  83. 55 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/MimeTypeUtils.java
  84. 56 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/http/HttpHelper.java
  85. 727 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/http/HttpUtils.java
  86. 56 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/ip/AddressUtils.java
  87. 195 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/ip/IpUtils.java
  88. 174 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/poi/EasyPoiUtil.java
  89. 1478 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/poi/ExcelUtil.java
  90. 137 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/polyv/PolyvUtils.java
  91. 406 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/reflect/ReflectUtils.java
  92. 161 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/spring/SpringUtils.java
  93. 37 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/sql/SqlUtil.java
  94. 175 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wisdom/SignatureUtil.java
  95. 42 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/IWXPayDomain.java
  96. 690 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPay.java
  97. 103 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPayConfig.java
  98. 59 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPayConstants.java
  99. 265 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPayReport.java
  100. 259 0
      zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPayRequest.java

+ 0 - 201
LICENSE

@@ -1,201 +0,0 @@
-                                 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:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) 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
-
-      (d) 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 [yyyy] [name of copyright owner]
-
-   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.

+ 23 - 4
pom.xml

@@ -14,6 +14,7 @@
         <module>zzbusiness-common</module>
         <module>zzbusiness-gateway</module>
         <module>zzbusiness-user</module>
+        <module>zzbusiness-info</module>
     </modules>
 
     <!-- 统一管理版本管理 -->
@@ -25,11 +26,11 @@
 
         <blade.tool.version>3.7.1</blade.tool.version>
         <blade.project.version>3.7.1</blade.project.version>
-
+        <mybatis.version>3.4.5</mybatis.version>
         <mybatis-plus.version>3.4.2</mybatis-plus.version>
         <mysql.connector.version>8.0.32</mysql.connector.version>
-
         <captcha.version>1.6.2</captcha.version>
+        <hutool.version>5.5.8</hutool.version>
     </properties>
 
     <!-- 作用:锁定版本 子module不用写groupId和version -->
@@ -51,7 +52,6 @@
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
-
             <dependency>
                 <groupId>com.fasterxml.jackson.core</groupId>
                 <artifactId>jackson-annotations</artifactId>
@@ -62,7 +62,26 @@
                 <artifactId>springfox-swagger2</artifactId>
                 <version>2.9.2</version>
             </dependency>
-
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.mybatis</groupId>
+                <artifactId>mybatis</artifactId>
+                <version>${mybatis.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>repository.org.apache.tomcat.embed</groupId>
+                <artifactId>tomcat-embed-core</artifactId>
+                <version>9.0.45</version>
+            </dependency>
+            <dependency>
+                <groupId>repository.com.github.pagehelper</groupId>
+                <artifactId>pagehelper</artifactId>
+                <version>5.2.0</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

+ 19 - 1
zzbusiness-common/pom.xml

@@ -37,7 +37,10 @@
             <artifactId>mysql-connector-java</artifactId>
             <version>${mysql.connector.version}</version>
         </dependency>
-
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
         <dependency>
             <groupId>io.springfox</groupId>
             <artifactId>springfox-swagger2</artifactId>
@@ -50,6 +53,21 @@
             <artifactId>jackson-databind</artifactId>
             <version>2.9.1</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.75</version>
+            <scope>compile</scope>
+        </dependency>
+
 
     </dependencies>
 

+ 29 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/DataScope.java

@@ -0,0 +1,29 @@
+package org.zhongzheng.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限过滤注解
+ *
+ * @author zhongzheng
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataScope
+{
+    /**
+     * 部门表的别名
+     */
+    public String deptAlias() default "";
+
+    /**
+     * 用户表的别名
+     */
+    public String userAlias() default "";
+
+    /**
+     * 业务层次表的别名
+     */
+    public String businessAlias() default "";
+}

+ 24 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/DataSource.java

@@ -0,0 +1,24 @@
+package org.zhongzheng.common.annotation;
+
+import com.zhongzheng.common.enums.DataSourceType;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义多数据源切换注解
+ *
+ * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
+ *
+ * @author zhongzheng
+ */
+@Target({ ElementType.METHOD, ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface DataSource
+{
+    /**
+     * 切换数据源名称
+     */
+    public DataSourceType value() default DataSourceType.MASTER;
+}

+ 165 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/Excel.java

@@ -0,0 +1,165 @@
+package org.zhongzheng.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+
+/**
+ * 自定义导出Excel数据注解
+ * 
+ * @author zhongzheng
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Excel
+{
+    /**
+     * 导出时在excel中排序
+     */
+    public int sort() default Integer.MAX_VALUE;
+
+    /**
+     * 导出到Excel中的名字.
+     */
+    public String name() default "";
+
+    /**
+     * 日期格式, 如: yyyy-MM-dd
+     */
+    public String dateFormat() default "";
+
+    /**
+     * 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
+     */
+    public String dictType() default "";
+
+    /**
+     * 读取内容转表达式 (如: 0=男,1=女,2=未知)
+     */
+    public String readConverterExp() default "";
+
+    /**
+     * 分隔符,读取字符串组内容
+     */
+    public String separator() default ",";
+
+    /**
+     * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
+     */
+    public int scale() default -1;
+
+    /**
+     * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
+     */
+    public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
+
+    /**
+     * 导出类型(0数字 1字符串)
+     */
+    public ColumnType cellType() default ColumnType.STRING;
+
+    /**
+     * 导出时在excel中每个列的高度 单位为字符
+     */
+    public double height() default 14;
+
+    /**
+     * 导出时在excel中每个列的宽 单位为字符
+     */
+    public double width() default 16;
+
+    /**
+     * 文字后缀,如% 90 变成90%
+     */
+    public String suffix() default "";
+
+    /**
+     * 当值为空时,字段的默认值
+     */
+    public String defaultValue() default "";
+
+    /**
+     * 提示信息
+     */
+    public String prompt() default "";
+
+    /**
+     * 设置只能选择不能输入的列内容.
+     */
+    public String[] combo() default {};
+
+    /**
+     * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
+     */
+    public boolean isExport() default true;
+
+    /**
+     * 另一个类中的属性名称,支持多级获取,以小数点隔开
+     */
+    public String targetAttr() default "";
+
+    /**
+     * 是否自动统计数据,在最后追加一行统计数据总和
+     */
+    public boolean isStatistics() default false;
+
+    /**
+     * 导出字段对齐方式(0:默认;1:靠左;2:居中;3:靠右)
+     */
+    Align align() default Align.AUTO;
+
+    public enum Align
+    {
+        AUTO(0), LEFT(1), CENTER(2), RIGHT(3);
+        private final int value;
+
+        Align(int value)
+        {
+            this.value = value;
+        }
+
+        public int value()
+        {
+            return this.value;
+        }
+    }
+
+    /**
+     * 字段类型(0:导出导入;1:仅导出;2:仅导入)
+     */
+    Type type() default Type.ALL;
+
+    public enum Type
+    {
+        ALL(0), EXPORT(1), IMPORT(2);
+        private final int value;
+
+        Type(int value)
+        {
+            this.value = value;
+        }
+
+        public int value()
+        {
+            return this.value;
+        }
+    }
+
+    public enum ColumnType
+    {
+        NUMERIC(0), STRING(1), IMAGE(2);
+        private final int value;
+
+        ColumnType(int value)
+        {
+            this.value = value;
+        }
+
+        public int value()
+        {
+            return this.value;
+        }
+    }
+}

+ 18 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/Excels.java

@@ -0,0 +1,18 @@
+package org.zhongzheng.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Excel注解集
+ * 
+ * @author zhongzheng
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Excels
+{
+    Excel[] value();
+}

+ 41 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/Log.java

@@ -0,0 +1,41 @@
+package org.zhongzheng.common.annotation;
+
+import com.zhongzheng.common.enums.BusinessType;
+import com.zhongzheng.common.enums.OperatorType;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义操作日志记录注解
+ *
+ * @author zhongzheng
+ *
+ */
+@Target({ ElementType.PARAMETER, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Log
+{
+    /**
+     * 模块
+     */
+    public String title() default "";
+
+    /**
+     * 功能
+     */
+    public BusinessType businessType() default BusinessType.OTHER;
+
+    /**
+     * 操作人类别
+     */
+    public OperatorType operatorType() default OperatorType.MANAGE;
+
+    /**
+     * 是否保存请求的参数
+     */
+    public boolean isSaveRequestData() default true;
+
+
+
+}

+ 18 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/annotation/RepeatSubmit.java

@@ -0,0 +1,18 @@
+package org.zhongzheng.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义注解防止表单重复提交
+ * 
+ * @author zhongzheng
+ *
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit
+{
+
+}

+ 76 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/config/RuoYiConfig.java

@@ -0,0 +1,76 @@
+package org.zhongzheng.common.config;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取项目相关配置
+ * 
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@Component
+@ConfigurationProperties(prefix = "zhongzheng")
+public class RuoYiConfig
+{
+    /** 项目名称 */
+    private String name;
+
+    /** 版本 */
+    private String version;
+
+    /** 版权年份 */
+    private String copyrightYear;
+
+    /** 实例演示开关 */
+    private boolean demoEnabled;
+
+    /** 上传路径 */
+    @Getter
+    private static String profile;
+
+    /** 获取地址开关 */
+    @Getter
+    private static boolean addressEnabled;
+
+    public void setProfile(String profile)
+    {
+        RuoYiConfig.profile = profile;
+    }
+
+    public void setAddressEnabled(boolean addressEnabled)
+    {
+        RuoYiConfig.addressEnabled = addressEnabled;
+    }
+
+    /**
+     * 获取头像上传路径
+     */
+    public static String getAvatarPath()
+    {
+        return getProfile() + "/avatar";
+    }
+
+    /**
+     * 获取下载路径
+     */
+    public static String getDownloadPath()
+    {
+        return getProfile() + "/download/";
+    }
+
+    /**
+     * 获取上传路径
+     */
+    public static String getUploadPath()
+    {
+        return getProfile() + "/upload";
+    }
+}

+ 74 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/config/WxSmallConfig.java

@@ -0,0 +1,74 @@
+package org.zhongzheng.common.config;
+
+import com.zhongzheng.common.utils.wxpay.IWXPayDomain;
+import com.zhongzheng.common.utils.wxpay.WXPayConfig;
+import com.zhongzheng.common.utils.wxpay.WXPayConstants;
+import org.apache.commons.compress.utils.IOUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+public class WxSmallConfig extends WXPayConfig {
+
+    private String appid;
+
+    private String mchid;
+
+    private String key;
+
+    private byte[] certData;
+
+    public WxSmallConfig(String appid,String mchid,String key)  {
+      this.appid = appid;
+      this.mchid = mchid;
+      this.key = key;
+    }
+    @Override
+    public String getAppID() {
+        return appid;
+    }
+
+    @Override
+    public String getMchID() {
+        return mchid;
+    }
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+
+
+    @Override
+    public IWXPayDomain getWXPayDomain() {
+
+        // 这个方法需要这样实现, 否则无法正常初始化WXPay
+        IWXPayDomain iwxPayDomain = new IWXPayDomain() {
+
+            public void report(String domain, long elapsedTimeMillis, Exception ex) {
+
+            }
+
+            public DomainInfo getDomain(WXPayConfig config) {
+                return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
+            }
+        };
+        return iwxPayDomain;
+
+    }
+
+    @Override
+    public InputStream getCertStream() {
+        try {
+            InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("config/"+getMchID()+"/apiclient_cert.p12");
+            this.certData = IOUtils.toByteArray(certStream);
+            certStream.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
+        return certBis;
+    }
+
+}

+ 170 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/constant/Constants.java

@@ -0,0 +1,170 @@
+package org.zhongzheng.common.constant;
+
+/**
+ * 通用常量信息
+ *
+ * @author zhongzheng
+ */
+public class Constants
+{
+    /**
+     * UTF-8 字符集
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * GBK 字符集
+     */
+    public static final String GBK = "GBK";
+
+    /**
+     * http请求
+     */
+    public static final String HTTP = "http://";
+
+    /**
+     * https请求
+     */
+    public static final String HTTPS = "https://";
+
+    /**
+     * 通用成功标识
+     */
+    public static final String SUCCESS = "0";
+
+    /**
+     * 通用失败标识
+     */
+    public static final String FAIL = "1";
+
+    /**
+     * 登录成功
+     */
+    public static final String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 注销
+     */
+    public static final String LOGOUT = "Logout";
+
+    /**
+     * 登录失败
+     */
+    public static final String LOGIN_FAIL = "Error";
+
+    /**
+     * 验证码 redis key
+     */
+    public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
+
+    /**
+     * 登录用户 redis key
+     */
+    public static final String LOGIN_TOKEN_KEY = "login_tokens:";
+
+    public static final String WX_LOGIN_TOKEN_KEY = "wx_login_tokens:";
+
+    public static final String BS_LOGIN_TOKEN_KEY = "bs_login_tokens:";
+
+
+    /**
+     * 防重提交 redis key
+     */
+    public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
+
+    /**
+     * 验证码有效期(分钟)
+     */
+    public static final Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 令牌
+     */
+    public static final String TOKEN = "token";
+
+
+
+    /**
+     * 令牌前缀
+     */
+    public static final String TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 超级管理员令牌前缀
+     */
+    public static final String SUPER_TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 微信令牌前缀
+     */
+    public static final String WX_TOKEN_PREFIX = "WX ";
+
+
+    public static final String BS_TOKEN_PREFIX = "BS ";
+
+
+    public static final String REGISTER_SMS = "REGISTER-";
+    public static final String FORGET_SMS = "FORGET-";
+    public static final String LOGIN_SMS = "LOGIN-";
+
+    //默认头像
+    public static final String DEFAULT_AVATAR = "oss/images/avatar/20210623/1624414559368_44562477.png";
+
+    /**
+     * 令牌前缀
+     */
+    public static final String LOGIN_USER_KEY = "login_user_key";
+
+    public static final String WX_LOGIN_USER_KEY = "wx_login_user_key";
+
+    public static final String BS_LOGIN_USER_KEY = "bs_login_user_key";
+
+
+
+
+
+    /**
+     * 用户ID
+     */
+    public static final String JWT_USERID = "userid";
+
+    /**
+     * 用户名称
+     */
+    public static final String JWT_USERNAME = "sub";
+
+    /**
+     * 用户头像
+     */
+    public static final String JWT_AVATAR = "avatar";
+
+    /**
+     * 创建时间
+     */
+    public static final String JWT_CREATED = "created";
+
+    /**
+     * 用户权限
+     */
+    public static final String JWT_AUTHORITIES = "authorities";
+
+    /**
+     * 参数管理 cache key
+     */
+    public static final String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 字典管理 cache key
+     */
+    public static final String SYS_DICT_KEY = "sys_dict:";
+
+    /**
+     * 字典管理 cache key
+     */
+    public static final String SYS_TOP_DICT_KEY = "sys_top_dict:";
+
+    /**
+     * 资源映射路径 前缀
+     */
+    public static final String RESOURCE_PREFIX = "/profile";
+}

+ 114 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/constant/GenConstants.java

@@ -0,0 +1,114 @@
+package org.zhongzheng.common.constant;
+
+/**
+ * 代码生成通用常量
+ * 
+ * @author zhongzheng
+ */
+public class GenConstants
+{
+    /** 单表(增删改查) */
+    public static final String TPL_CRUD = "crud";
+
+    /** 树表(增删改查) */
+    public static final String TPL_TREE = "tree";
+
+    /** 主子表(增删改查) */
+    public static final String TPL_SUB = "sub";
+
+    /** 树编码字段 */
+    public static final String TREE_CODE = "treeCode";
+
+    /** 树父编码字段 */
+    public static final String TREE_PARENT_CODE = "treeParentCode";
+
+    /** 树名称字段 */
+    public static final String TREE_NAME = "treeName";
+
+    /** 上级菜单ID字段 */
+    public static final String PARENT_MENU_ID = "parentMenuId";
+
+    /** 上级菜单名称字段 */
+    public static final String PARENT_MENU_NAME = "parentMenuName";
+
+    /** 数据库字符串类型 */
+    public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };
+
+    /** 数据库文本类型 */
+    public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" };
+
+    /** 数据库时间类型 */
+    public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" };
+
+    /** 数据库数字类型 */
+    public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
+            "bit", "bigint", "float", "double", "decimal" };
+
+    /** 页面不需要编辑字段 */
+    public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" };
+
+    /** 页面不需要显示的列表字段 */
+    public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by",
+            "update_time" };
+
+    /** 页面不需要查询字段 */
+    public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by",
+            "update_time", "remark" };
+
+    /** Entity基类字段 */
+    public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" };
+
+    /** Tree基类字段 */
+    public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" };
+
+    /** 文本框 */
+    public static final String HTML_INPUT = "input";
+
+    /** 文本域 */
+    public static final String HTML_TEXTAREA = "textarea";
+
+    /** 下拉框 */
+    public static final String HTML_SELECT = "select";
+
+    /** 单选框 */
+    public static final String HTML_RADIO = "radio";
+
+    /** 复选框 */
+    public static final String HTML_CHECKBOX = "checkbox";
+
+    /** 日期控件 */
+    public static final String HTML_DATETIME = "datetime";
+
+    /** 图片上传控件 */
+    public static final String HTML_IMAGE_UPLOAD = "imageUpload";
+
+    /** 文件上传控件 */
+    public static final String HTML_FILE_UPLOAD = "fileUpload";
+
+    /** 富文本控件 */
+    public static final String HTML_EDITOR = "editor";
+
+    /** 字符串类型 */
+    public static final String TYPE_STRING = "String";
+
+    /** 整型 */
+    public static final String TYPE_INTEGER = "Integer";
+
+    /** 长整型 */
+    public static final String TYPE_LONG = "Long";
+
+    /** 浮点型 */
+    public static final String TYPE_DOUBLE = "Double";
+
+    /** 高精度计算类型 */
+    public static final String TYPE_BIGDECIMAL = "BigDecimal";
+
+    /** 时间类型 */
+    public static final String TYPE_DATE = "Date";
+
+    /** 模糊查询 */
+    public static final String QUERY_LIKE = "LIKE";
+
+    /** 需要 */
+    public static final String REQUIRE = "1";
+}

+ 50 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/constant/ScheduleConstants.java

@@ -0,0 +1,50 @@
+package org.zhongzheng.common.constant;
+
+/**
+ * 任务调度通用常量
+ * 
+ * @author zhongzheng
+ */
+public class ScheduleConstants
+{
+    public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";
+
+    /** 执行目标key */
+    public static final String TASK_PROPERTIES = "TASK_PROPERTIES";
+
+    /** 默认 */
+    public static final String MISFIRE_DEFAULT = "0";
+
+    /** 立即触发执行 */
+    public static final String MISFIRE_IGNORE_MISFIRES = "1";
+
+    /** 触发一次执行 */
+    public static final String MISFIRE_FIRE_AND_PROCEED = "2";
+
+    /** 不触发立即执行 */
+    public static final String MISFIRE_DO_NOTHING = "3";
+
+    public enum Status
+    {
+        /**
+         * 正常
+         */
+        NORMAL("0"),
+        /**
+         * 暂停
+         */
+        PAUSE("1");
+
+        private String value;
+
+        private Status(String value)
+        {
+            this.value = value;
+        }
+
+        public String getValue()
+        {
+            return value;
+        }
+    }
+}

+ 63 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/constant/UserConstants.java

@@ -0,0 +1,63 @@
+package org.zhongzheng.common.constant;
+
+/**
+ * 用户常量信息
+ * 
+ * @author zhongzheng
+ */
+public class UserConstants
+{
+    /**
+     * 平台内系统用户的唯一标志
+     */
+    public static final String SYS_USER = "SYS_USER";
+
+    /** 正常状态 */
+    public static final String NORMAL = "0";
+
+    /** 异常状态 */
+    public static final String EXCEPTION = "1";
+
+    /** 用户封禁状态 */
+    public static final String USER_DISABLE = "1";
+
+    /** 角色封禁状态 */
+    public static final String ROLE_DISABLE = "1";
+
+    /** 部门正常状态 */
+    public static final String DEPT_NORMAL = "0";
+
+    /** 部门停用状态 */
+    public static final String DEPT_DISABLE = "1";
+
+    /** 字典正常状态 */
+    public static final String DICT_NORMAL = "0";
+
+    /** 是否为系统默认(是) */
+    public static final String YES = "Y";
+
+    /** 是否菜单外链(是) */
+    public static final String YES_FRAME = "0";
+
+    /** 是否菜单外链(否) */
+    public static final String NO_FRAME = "1";
+
+    /** 菜单类型(目录) */
+    public static final String TYPE_DIR = "M";
+
+    /** 菜单类型(菜单) */
+    public static final String TYPE_MENU = "C";
+
+    /** 菜单类型(按钮) */
+    public static final String TYPE_BUTTON = "F";
+
+    /** Layout组件标识 */
+    public final static String LAYOUT = "Layout";
+
+    /** ParentView组件标识 */
+    public final static String PARENT_VIEW = "ParentView";
+
+    /** 校验返回结果码 */
+    public final static String UNIQUE = "0";
+    public final static String NOT_UNIQUE = "1";
+}

+ 144 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/bo/SysRoleBo.java

@@ -0,0 +1,144 @@
+package org.zhongzheng.common.core.bo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.zhongzheng.common.annotation.Excel;
+import com.zhongzheng.common.annotation.Excel.ColumnType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.springframework.beans.factory.annotation.Value;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 角色表 sys_role
+ *
+ * @author zhongzheng
+ */
+@ApiModel("角色")
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class SysRoleBo implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 角色ID */
+    @Excel(name = "角色序号", cellType = ColumnType.NUMERIC)
+    @TableId(value = "role_id",type = IdType.AUTO)
+    private Long roleId;
+
+    /** 角色名称 */
+    @ApiModelProperty(value ="角色名称")
+    @Excel(name = "角色名称")
+    @NotBlank(message = "角色名称不能为空")
+    @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符")
+    private String roleName;
+
+    /** 角色权限 */
+    @ApiModelProperty(value ="权限字符")
+    @Excel(name = "角色权限")
+    @NotBlank(message = "权限字符不能为空")
+    @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符")
+    private String roleKey;
+
+    /** 角色排序 */
+    @ApiModelProperty(value ="角色排序")
+    @Excel(name = "角色排序")
+    @NotBlank(message = "显示顺序不能为空")
+    private String roleSort;
+
+    /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限) */
+    @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限")
+    private String dataScope;
+
+    @Value("true")
+    /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */
+    private boolean menuCheckStrictly;
+
+    @Value("true")
+    /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */
+    private boolean deptCheckStrictly;
+
+    /** 角色状态(0正常 1停用) */
+    @ApiModelProperty(value ="状态 1=正常,0=停用")
+    @Value("1")
+    @Excel(name = "角色状态", readConverterExp = "1=正常,0=停用")
+    private String status;
+
+    /** 删除标志(0代表存在 2代表删除) */
+    @TableLogic
+    private String delFlag;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    @ApiModelProperty(value ="备注")
+    private String remark;
+
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+    /** 用户是否存在此角色标识 默认不存在 */
+    @TableField(exist = false)
+    private boolean flag = false;
+
+    /** 菜单组 */
+    @ApiModelProperty(value ="菜单数组")
+    @TableField(exist = false)
+    private Long[] menuIds;
+
+    /** 部门组(数据权限) */
+    @TableField(exist = false)
+    private Long[] deptIds;
+
+    @TableField(exist = false)
+    private List<String> statusArray;
+    /** 业务层次ID */
+    @TableField(exist = false)
+    private List<Long> businessIds;
+
+    public SysRoleBo(Long roleId)
+    {
+        this.roleId = roleId;
+    }
+
+    public boolean isAdmin()
+    {
+        return isAdmin(this.roleId);
+    }
+
+    public static boolean isAdmin(Long roleId)
+    {
+        return roleId != null && 1L == roleId;
+    }
+
+}

+ 93 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/bo/SysUserEditBo.java

@@ -0,0 +1,93 @@
+package org.zhongzheng.common.core.bo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.zhongzheng.common.annotation.Excel;
+import com.zhongzheng.common.annotation.Excel.ColumnType;
+import com.zhongzheng.common.annotation.Excel.Type;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+
+/**
+ * 用户对象 SysUserEditBo
+ *
+ * @author zhongzheng
+ */
+
+@Data
+@ApiModel("考试安排,包含考试指引编辑对象")
+public class SysUserEditBo
+{
+
+    /** 用户ID */
+    @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号")
+    @TableId(value = "user_id",type = IdType.AUTO)
+    @ApiModelProperty("用户编号")
+    private Long userId;
+
+    /** 部门ID */
+    @Excel(name = "部门编号", type = Type.IMPORT)
+    @ApiModelProperty("部门编号")
+    private Long deptId;
+
+    /** 用户账号 */
+    @NotBlank(message = "用户账号不能为空")
+    @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
+    @Excel(name = "登录名称")
+    @ApiModelProperty("用户账号")
+    private String userName;
+
+    /** 用户昵称 */
+    @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
+    @Excel(name = "用户名称")
+    @ApiModelProperty("用户昵称")
+    private String nickName;
+
+    /** 用户邮箱 */
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    @Excel(name = "用户邮箱")
+    @ApiModelProperty("用户邮箱")
+    private String email;
+
+    /** 手机号码 */
+    @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
+    @Excel(name = "手机号码")
+    @ApiModelProperty("手机号码")
+    private String phonenumber;
+
+    /** 用户性别 */
+    @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
+    @ApiModelProperty("用户性别")
+    private String sex;
+
+    /** 用户头像 */
+    @ApiModelProperty("用户头像")
+    private String avatar;
+
+    /** 密码 */
+    @ApiModelProperty("密码")
+    private String password;
+
+    /** 旧密码 */
+    @ApiModelProperty("旧密码")
+    private String oldPassword;
+
+
+    /** 帐号状态(1正常 0停用) */
+    @Excel(name = "帐号状态", readConverterExp = "1=正常,0=停用")
+    @ApiModelProperty("帐号状态(1正常 0停用)")
+    private String status;
+    @ApiModelProperty("角色组")
+    private Long[] roleIds;
+    @ApiModelProperty("岗位组")
+    private Long[] postIds;
+    /** 身份证号 */
+    @ApiModelProperty("身份证号")
+    private String idCard;
+}

+ 169 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/controller/BaseController.java

@@ -0,0 +1,169 @@
+package org.zhongzheng.common.core.controller;
+
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpStatus;
+import com.github.pagehelper.Page;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import com.zhongzheng.common.core.domain.AjaxResult;
+import com.zhongzheng.common.core.page.PageDomain;
+import com.zhongzheng.common.core.page.TableDataInfo;
+import com.zhongzheng.common.core.page.TableSupport;
+import com.zhongzheng.common.utils.DateUtils;
+import com.zhongzheng.common.utils.sql.SqlUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+
+import java.beans.PropertyEditorSupport;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * web层通用数据处理
+ *
+ * @author zhongzheng
+ */
+public class BaseController
+{
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * 将前台传递过来的日期格式的字符串,自动转化为Date类型
+     */
+    @InitBinder
+    public void initBinder(WebDataBinder binder)
+    {
+        // Date 类型转换
+        binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
+        {
+            @Override
+            public void setAsText(String text)
+            {
+                setValue(DateUtils.parseDate(text));
+            }
+        });
+    }
+
+    /**
+     * 设置请求分页数据
+     */
+    protected void startPage()
+    {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+        if (Validator.isNotNull(pageNum) && Validator.isNotNull(pageSize))
+        {
+            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
+            PageHelper.startPage(pageNum, pageSize, orderBy);
+        }
+    }
+
+    /**
+     * pagehelper   手动分页
+     * @param currentPage 当前页
+     * @param pageSize
+     * @param list
+     * @param <T>
+     * @return
+     */
+    public static <T> PageInfo<T> getPageInfo(int currentPage, int pageSize, List<T> list) {
+        int total = list.size();
+        if (total > pageSize) {
+            int toIndex = pageSize * currentPage;
+            if (toIndex > total) {
+                toIndex = total;
+            }
+            list = list.subList(pageSize * (currentPage - 1), toIndex);
+        }
+        Page<T> page = new Page<>(currentPage, pageSize);
+        page.addAll(list);
+        page.setPages((total + pageSize - 1) / pageSize);
+        page.setTotal(total);
+
+        PageInfo<T> pageInfo = new PageInfo<>(page);
+        return pageInfo;
+    }
+
+    /**
+     * 响应请求分页数据
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    protected <T> TableDataInfo<T> getDataTable(List<T> list)
+    {
+        TableDataInfo<T> rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(new PageInfo(list).getTotal());
+        return rspData;
+    }
+
+    /**
+     * 响应返回结果
+     *
+     * @param rows 影响行数
+     * @return 操作结果
+     */
+    protected AjaxResult<Void> toAjax(int rows)
+    {
+        return rows > 0 ? AjaxResult.success() : AjaxResult.error();
+    }
+
+
+
+    /**
+     * 响应返回结果
+     *
+     * @param result 结果
+     * @return 操作结果
+     */
+    protected AjaxResult<Void> toAjax(boolean result)
+    {
+        return result ? success() : error();
+    }
+
+    /**
+     * 返回成功
+     */
+    public AjaxResult<Void> success()
+    {
+        return AjaxResult.success();
+    }
+
+    /**
+     * 返回失败消息
+     */
+    public AjaxResult<Void> error()
+    {
+        return AjaxResult.error();
+    }
+
+    /**
+     * 返回成功消息
+     */
+    public AjaxResult<Void> success(String message)
+    {
+        return AjaxResult.success(message);
+    }
+
+    /**
+     * 返回失败消息
+     */
+    public AjaxResult<Void> error(String message)
+    {
+        return AjaxResult.error(message);
+    }
+
+    /**
+     * 页面跳转
+     */
+    public String redirect(String url)
+    {
+        return StrUtil.format("redirect:{}", url);
+    }
+}

+ 192 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/AjaxResult.java

@@ -0,0 +1,192 @@
+package org.zhongzheng.common.core.domain;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.http.HttpStatus;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 操作消息提醒
+ *
+ * @author zhongzheng
+ */
+public class AjaxResult<T> extends HashMap<String, Object>
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 状态码 */
+    public static final String CODE_TAG = "code";
+
+    /** 返回内容 */
+    public static final String MSG_TAG = "msg";
+
+    /** 数据对象 */
+    public static final String DATA_TAG = "data";
+
+    public Integer getCode(){
+        return (Integer) super.get(CODE_TAG);
+    }
+
+    public String getMsg(){
+        return (String) super.get(MSG_TAG);
+    }
+    public T getData(){
+        return (T) super.get(DATA_TAG);
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
+     */
+    public AjaxResult()
+    {
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg 返回内容
+     */
+    public AjaxResult(int code, String msg)
+    {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResult 对象
+     *
+     * @param code 状态码
+     * @param msg 返回内容
+     * @param data 数据对象
+     */
+    public AjaxResult(int code, String msg, T data)
+    {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+        if (Validator.isNotNull(data))
+        {
+            super.put(DATA_TAG, data);
+        }
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @return 成功消息
+     */
+    public static AjaxResult<Void> success()
+    {
+        return AjaxResult.success("操作成功");
+    }
+
+    /**
+     * 返回成功拦截日志消息
+     *
+     * @return 成功拦截日志消息
+     */
+    public static AjaxResult<Void> success_log(Long primary_key_id,String oper_content)
+    {
+        Map<String,Object> logMap = new HashMap<>();
+        logMap.put("primary_key_id", primary_key_id);
+        logMap.put("oper_content", oper_content);
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("log", logMap);
+        return ajax;
+    }
+
+    /**
+     * 返回成功数据
+     *
+     * @return 成功消息
+     */
+    public static <T> AjaxResult<T> success(T data)
+    {
+        return AjaxResult.success("操作成功", data);
+    }
+
+    /**
+     * 返回成功数据
+     *
+     * @return 成功消息
+     */
+    public static <T> AjaxResult<T> successNoMsg(T data)
+    {
+        return AjaxResult.success("", data);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg 返回内容
+     * @return 成功消息
+     */
+    public static AjaxResult<Void> success(String msg)
+    {
+        return AjaxResult.success(msg, null);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 成功消息
+     */
+    public static <T> AjaxResult<T> success(String msg, T data)
+    {
+        return new AjaxResult(HttpStatus.HTTP_OK, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @return
+     */
+    public static AjaxResult<Void> error()
+    {
+        return AjaxResult.error("操作失败");
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static AjaxResult<Void> error(String msg)
+    {
+        return AjaxResult.error(msg, null);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 警告消息
+     */
+    public static <T> AjaxResult<T> error(String msg, T data)
+    {
+        return new AjaxResult(HttpStatus.HTTP_INTERNAL_ERROR, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param code 状态码
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static AjaxResult<Void> error(int code, String msg)
+    {
+        return new AjaxResult(code, msg, null);
+    }
+
+
+    public static <T> AjaxResult<T> error(int code, String msg, T data)
+    {
+        return new AjaxResult(code, msg, data);
+    }
+}

+ 49 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/BaseEntity.java

@@ -0,0 +1,49 @@
+package org.zhongzheng.common.core.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity基类
+ * 
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class BaseEntity implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 搜索值 */
+    private String searchValue;
+
+    /** 创建者 */
+    private String createBy;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+
+    /** 请求参数 */
+    private Map<String, Object> params = new HashMap<>();
+
+}

+ 40 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/TreeEntity.java

@@ -0,0 +1,40 @@
+package org.zhongzheng.common.core.domain;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tree基类
+ * 
+ * @author zhongzheng
+ */
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class TreeEntity extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 父菜单名称 */
+    private String parentName;
+
+    /** 父菜单ID */
+    private Long parentId;
+
+    /** 显示顺序 */
+    private Integer orderNum;
+
+    /** 祖级列表 */
+    private String ancestors;
+
+    /** 子部门 */
+    private List<?> children = new ArrayList<>();
+
+}

+ 51 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/TreeSelect.java

@@ -0,0 +1,51 @@
+package org.zhongzheng.common.core.domain;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.zhongzheng.common.core.domain.entity.SysDept;
+import com.zhongzheng.common.core.domain.entity.SysMenu;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Treeselect树结构实体类
+ * 
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class TreeSelect implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 节点ID */
+    private Long id;
+
+    /** 节点名称 */
+    private String label;
+
+    /** 子节点 */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private List<TreeSelect> children;
+
+    public TreeSelect(SysDept dept)
+    {
+        this.id = dept.getDeptId();
+        this.label = dept.getDeptName();
+        this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
+    }
+
+    public TreeSelect(SysMenu menu)
+    {
+        this.id = menu.getMenuId();
+        this.label = menu.getMenuName();
+        this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
+    }
+
+}

+ 106 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysDept.java

@@ -0,0 +1,106 @@
+package org.zhongzheng.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * 部门表 sys_dept
+ *
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class SysDept implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 部门ID */
+    @TableId(value = "dept_id",type = IdType.AUTO)
+    private Long deptId;
+
+    /** 父部门ID */
+    @ApiModelProperty(value ="父部门ID")
+    private Long parentId;
+
+    /** 祖级列表 */
+    private String ancestors;
+
+    /** 部门名称 */
+    @ApiModelProperty(value ="部门名称")
+    @NotBlank(message = "部门名称不能为空")
+    @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符")
+    private String deptName;
+
+    /** 显示顺序 */
+    @ApiModelProperty(value ="显示顺序")
+    @NotBlank(message = "显示顺序不能为空")
+    private String orderNum;
+
+    /** 负责人 */
+    private String leader;
+
+    /** 联系电话 */
+    @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符")
+    private String phone;
+
+    /** 邮箱 */
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    private String email;
+
+    /** 部门状态:0正常,1停用 */
+    @ApiModelProperty(value ="部门状态:1正常,0停用")
+    private String status;
+
+    /** 删除标志(0代表存在 2代表删除) */
+    @TableLogic
+    private String delFlag;
+
+    /** 父部门名称 */
+    @TableField(exist = false)
+    private String parentName;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 子部门 */
+    @TableField(exist = false)
+    private List<SysDept> children = new ArrayList<SysDept>();
+
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+    @TableField(exist = false)
+    private List<String> statusArray;
+
+}

+ 127 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysDictData.java

@@ -0,0 +1,127 @@
+package org.zhongzheng.common.core.domain.entity;
+
+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.fasterxml.jackson.annotation.JsonFormat;
+import com.zhongzheng.common.annotation.Excel;
+import com.zhongzheng.common.annotation.Excel.ColumnType;
+import com.zhongzheng.common.constant.UserConstants;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 字典数据表 sys_dict_data
+ *
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class SysDictData implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 字典编码 */
+    @Excel(name = "字典编码", cellType = ColumnType.NUMERIC)
+    @TableId(value = "dict_code",type = IdType.AUTO)
+    private Long dictCode;
+
+    /** 字典排序 */
+    @ApiModelProperty(value ="字典排序")
+    @Excel(name = "字典排序", cellType = ColumnType.NUMERIC)
+    private Long dictSort;
+
+    /** 字典标签 */
+    @ApiModelProperty(value ="字典标签")
+    @Excel(name = "字典标签")
+    @NotBlank(message = "字典标签不能为空")
+    @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符")
+    private String dictLabel;
+
+    /** 字典键值 */
+    @ApiModelProperty(value ="字典键值")
+    @Excel(name = "字典键值")
+    @NotBlank(message = "字典键值不能为空")
+    @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符")
+    private String dictValue;
+
+    /** 字典类型 */
+    @ApiModelProperty(value ="字典类型")
+    @Excel(name = "字典类型")
+    @NotBlank(message = "字典类型不能为空")
+    @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符")
+    private String dictType;
+
+    /** 样式属性(其他样式扩展) */
+    @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符")
+    private String cssClass;
+
+    /** 表格字典样式 */
+    private String listClass;
+
+    @TableField(value = "tenant_id")
+    private String tenantId;
+
+    /** 是否默认(Y是 N否) */
+    @Excel(name = "是否默认", readConverterExp = "Y=是,N=否")
+    private String isDefault;
+
+    /** 状态(0正常 1停用) */
+    @ApiModelProperty(value ="状态(1正常 0停用)")
+    @Excel(name = "状态", readConverterExp = "1正常 0停用")
+    private String status;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+
+    @ApiModelProperty(value ="字典类型ID")
+    @Excel(name = "字典类型ID", cellType = ColumnType.NUMERIC)
+    @NotNull(message = "字典类型ID不能为空")
+    private Long dictTypeId;
+
+    @TableField(exist = false)
+    private List<String> statusArray;
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+    public boolean getDefault()
+    {
+        return UserConstants.YES.equals(this.isDefault) ? true : false;
+    }
+
+}

+ 99 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysDictType.java

@@ -0,0 +1,99 @@
+package org.zhongzheng.common.core.domain.entity;
+
+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.fasterxml.jackson.annotation.JsonFormat;
+import com.zhongzheng.common.annotation.Excel;
+import com.zhongzheng.common.annotation.Excel.ColumnType;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 字典类型表 sys_dict_type
+ *
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class SysDictType implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 字典主键 */
+    @Excel(name = "字典主键", cellType = ColumnType.NUMERIC)
+    @TableId(value = "dict_id",type = IdType.AUTO)
+    private Long dictId;
+
+    /** 字典名称 */
+    @ApiModelProperty(value ="字典名称")
+    @Excel(name = "字典名称")
+    @NotBlank(message = "字典名称不能为空")
+    @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符")
+    private String dictName;
+
+    /** 字典类型 */
+    @ApiModelProperty(value ="字典类型")
+    @Excel(name = "字典类型")
+    @NotBlank(message = "字典类型不能为空")
+    @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符")
+    private String dictType;
+
+    /** 状态(1正常 0停用) */
+    @Excel(name = "状态", readConverterExp = "状态(1正常 0停用)")
+    private String status;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+
+    @TableField(exist = false)
+    private List<String> statusArray;
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+    @TableField(value = "tenant_id")
+    private Long tenantId;
+
+    /** 分页大小 */
+    @ApiModelProperty("分页大小")
+    @TableField(exist = false)
+    private Integer pageSize;
+    /** 当前页数 */
+    @ApiModelProperty("当前页数")
+    @TableField(exist = false)
+    private Integer pageNum;
+}

+ 127 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysMenu.java

@@ -0,0 +1,127 @@
+package org.zhongzheng.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * 菜单权限表 sys_menu
+ *
+ * @author zhongzheng
+ */
+@ApiModel("菜单")
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class SysMenu implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 菜单ID */
+    @ApiModelProperty(value ="菜单ID")
+    @TableId(value = "menu_id",type = IdType.AUTO)
+    private Long menuId;
+
+    /** 菜单名称 */
+    @ApiModelProperty(value ="菜单名称")
+    @NotBlank(message = "菜单名称不能为空")
+    @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符")
+    private String menuName;
+
+    /** 父菜单名称 */
+    @TableField(exist = false)
+    private String parentName;
+
+    /** 父菜单ID */
+    @ApiModelProperty(value ="父菜单ID")
+    private Long parentId;
+
+    /** 显示顺序 */
+    @ApiModelProperty(value ="显示顺序")
+    @NotBlank(message = "显示顺序不能为空")
+    private String orderNum;
+
+    /** 路由地址 */
+    @ApiModelProperty(value ="路由地址")
+    @Size(min = 0, max = 200, message = "路由地址不能超过200个字符")
+    private String path;
+
+    /** 组件路径 */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    @ApiModelProperty(value ="组件路径")
+    @Size(min = 0, max = 200, message = "组件路径不能超过255个字符")
+    private String component;
+
+    /** 是否为外链(0是 1否) */
+    @ApiModelProperty(value ="是否为外链(0是 1否)")
+    private String isFrame;
+
+    /** 是否缓存(0缓存 1不缓存) */
+    @ApiModelProperty(value ="是否缓存(0缓存 1不缓存)")
+    private String isCache;
+
+    /** 类型(M目录 C菜单 F按钮) */
+    @ApiModelProperty(value ="类型(M目录 C菜单 F按钮)")
+    @NotBlank(message = "菜单类型不能为空")
+    private String menuType;
+
+    /** 显示状态(0显示 1隐藏) */
+    @ApiModelProperty(value ="显示状态(1显示 0隐藏)")
+    private String visible;
+
+    /** 菜单状态(0显示 1隐藏) */
+    @ApiModelProperty(value ="菜单状态(1显示 0隐藏)")
+    private String status;
+
+    /** 权限字符串 */
+    @ApiModelProperty(value ="权限标识")
+    @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符")
+    private String perms;
+
+    /** 菜单图标 */
+    @ApiModelProperty(value ="菜单图标")
+    private String icon;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+
+    /** 备注 */
+//    @TableField(exist = false)
+    private Long tenantId;
+
+    /** 请求参数 */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+    /** 子菜单 */
+    @TableField(exist = false)
+    private List<SysMenu> children = new ArrayList<SysMenu>();
+
+}

+ 146 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysRole.java

@@ -0,0 +1,146 @@
+package org.zhongzheng.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.zhongzheng.common.annotation.Excel;
+import com.zhongzheng.common.annotation.Excel.ColumnType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.springframework.beans.factory.annotation.Value;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 角色表 sys_role
+ *
+ * @author zhongzheng
+ */
+@ApiModel("角色")
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class SysRole implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 角色ID */
+    @Excel(name = "角色序号", cellType = ColumnType.NUMERIC)
+    @TableId(value = "role_id",type = IdType.AUTO)
+    private Long roleId;
+
+    /** 角色名称 */
+    @ApiModelProperty(value ="角色名称")
+    @Excel(name = "角色名称")
+    @NotBlank(message = "角色名称不能为空")
+    @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符")
+    private String roleName;
+
+    /** 角色权限 */
+    @ApiModelProperty(value ="权限字符")
+    @Excel(name = "角色权限")
+    @NotBlank(message = "权限字符不能为空")
+    @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符")
+    private String roleKey;
+
+    /** 角色排序 */
+    @ApiModelProperty(value ="角色排序")
+    @Excel(name = "角色排序")
+    @NotBlank(message = "显示顺序不能为空")
+    private String roleSort;
+
+    /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限) */
+    @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限")
+    private String dataScope;
+
+    @Value("true")
+    /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */
+    private boolean menuCheckStrictly;
+
+    @Value("true")
+    /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */
+    private boolean deptCheckStrictly;
+
+    /** 角色状态(0正常 1停用) */
+    @ApiModelProperty(value ="状态 1=正常,0=停用")
+    @Value("1")
+    @Excel(name = "角色状态", readConverterExp = "1=正常,0=停用")
+    private String status;
+
+    /** 删除标志(0代表存在 2代表删除) */
+    @TableLogic
+    private String delFlag;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    @ApiModelProperty(value ="备注")
+    private String remark;
+
+    private String tenantId;
+
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+    /** 用户是否存在此角色标识 默认不存在 */
+    @TableField(exist = false)
+    private boolean flag = false;
+
+    /** 菜单组 */
+    @ApiModelProperty(value ="菜单数组")
+    @TableField(exist = false)
+    private Long[] menuIds;
+
+    /** 部门组(数据权限) */
+    @TableField(exist = false)
+    private Long[] deptIds;
+
+    @TableField(exist = false)
+    private List<String> statusArray;
+    /** 业务层次ID */
+    @TableField(exist = false)
+    private List<Long> businessIds;
+
+    public SysRole(Long roleId)
+    {
+        this.roleId = roleId;
+    }
+
+    public boolean isAdmin()
+    {
+        return isAdmin(this.roleId);
+    }
+
+    public static boolean isAdmin(Long roleId)
+    {
+        return roleId != null && 1L == roleId;
+    }
+
+}

+ 195 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/SysUser.java

@@ -0,0 +1,195 @@
+package org.zhongzheng.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.zhongzheng.common.annotation.Excel;
+import com.zhongzheng.common.annotation.Excel.ColumnType;
+import com.zhongzheng.common.annotation.Excel.Type;
+import com.zhongzheng.common.annotation.Excels;
+import com.zhongzheng.common.type.EncryptHandler;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 用户对象 sys_user
+ *
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@TableName(value ="sys_user",autoResultMap = true)
+public class SysUser implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 用户ID */
+    @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号")
+    @TableId(value = "user_id",type = IdType.AUTO)
+    private Long userId;
+
+    /** 部门ID */
+    @Excel(name = "部门编号", type = Type.IMPORT)
+    private Long deptId;
+
+    /** 用户账号 */
+    @NotBlank(message = "用户账号不能为空")
+    @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
+    @Excel(name = "登录名称")
+    private String userName;
+
+    /** 用户昵称 */
+    @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
+    @Excel(name = "用户名称")
+    private String nickName;
+
+    /** 用户邮箱 */
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    @Excel(name = "用户邮箱")
+    private String email;
+
+    /** 手机号码 */
+    @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
+    @Excel(name = "手机号码")
+    private String phonenumber;
+
+    /** 用户性别 */
+    @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
+    private String sex;
+
+    /** 用户头像 */
+    private String avatar;
+
+    /** 密码 */
+    private String password;
+
+    @JsonIgnore
+    @JsonProperty
+    public String getPassword() {
+        return password;
+    }
+
+    /** 盐加密 */
+    @TableField(exist = false)
+    private String salt;
+
+    /** 帐号状态(1正常 0停用) */
+    @Excel(name = "帐号状态", readConverterExp = "1=正常,0=停用")
+    private Long status;
+
+    /** 删除标志(0代表存在 2代表删除) */
+    @TableLogic
+    private String delFlag;
+
+    /** 最后登录IP */
+    @Excel(name = "最后登录IP", type = Type.EXPORT)
+    private String loginIp;
+
+    /** 最后登录时间 */
+    @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
+    private Date loginDate;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 编码 */
+    private String code;
+
+    /** 更新时间 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+
+    @TableField(exist = false)
+    private List<String> statusArray;
+
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+    /** 部门对象 */
+    @Excels({
+        @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
+        @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
+    })
+    @TableField(exist = false)
+    private SysDept dept;
+
+    /** 角色对象 */
+    @TableField(exist = false)
+    private List<SysRole> roles;
+
+    /** 角色组 */
+    @TableField(exist = false)
+    private Long[] roleIds;
+
+    /** 岗位组 */
+    @TableField(exist = false)
+    private Long[] postIds;
+
+    /** 角色名称 */
+    @TableField(exist = false)
+    private String roleName;
+
+    private Long tenantId;
+
+    /** 上一次登录IP */
+    private String preLoginIp;
+    /** 上一次登录时间 */
+    private Date preLoginDate;
+    /** 密码最后修改时间 */
+    private Long pwdTime;
+    /** 身份证号 */
+    @TableField(typeHandler = EncryptHandler.class)
+    private String idCard;
+
+    public SysUser(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public boolean isAdmin()
+    {
+        return isAdmin(this.userName);
+    }
+
+    public static boolean isAdmin(String userName)
+    {
+        return userName != null && "admin".equals(userName) ;
+    }
+
+  /*  public static boolean isAdmin(Long userId)
+    {
+        return userId != null && 1L == userId;
+    }*/
+  private Long sellerId;
+}

+ 111 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/TopSysRole.java

@@ -0,0 +1,111 @@
+package org.zhongzheng.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.zhongzheng.common.annotation.Excel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.springframework.beans.factory.annotation.Value;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 角色信息对象 top_sys_role
+ *
+ * @author ruoyi
+ * @date 2023-03-10
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@TableName("top_sys_role")
+public class TopSysRole implements Serializable {
+
+private static final long serialVersionUID=1L;
+
+    /** 角色ID */
+    @TableId(value = "role_id")
+    private Long roleId;
+    /** 角色名称 */
+    private String roleName;
+    /** 角色权限字符串 */
+    private String roleKey;
+    /** 显示顺序 */
+    private Long roleSort;
+    /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限) */
+    @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限")
+    private String dataScope;
+
+    @Value("true")
+    /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */
+    private boolean menuCheckStrictly;
+
+    @Value("true")
+    /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */
+    private boolean deptCheckStrictly;
+    /** 角色状态(1正常 0停用) */
+    private String status;
+    /** 删除标志(0代表存在 2代表删除) */
+    @TableLogic
+    private String delFlag;
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+    /** 创建时间 */
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+    /** 更新时间 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+    /** 备注 */
+    private String remark;
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+    /** 用户是否存在此角色标识 默认不存在 */
+    @TableField(exist = false)
+    private boolean flag = false;
+
+    /** 菜单组 */
+    @ApiModelProperty(value ="菜单数组")
+    @TableField(exist = false)
+    private Long[] menuIds;
+
+
+
+    /** 部门组(数据权限) */
+    @TableField(exist = false)
+    private Long[] deptIds;
+
+    @TableField(exist = false)
+    private List<String> statusArray;
+    /** 业务层次ID */
+    @TableField(exist = false)
+    private List<Long> businessIds;
+
+    public TopSysRole(Long roleId)
+    {
+        this.roleId = roleId;
+    }
+
+    public boolean isAdmin()
+    {
+        return isAdmin(this.roleId);
+    }
+
+    public static boolean isAdmin(Long roleId)
+    {
+        return roleId != null && 1L == roleId;
+    }
+}

+ 176 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/entity/TopSysUser.java

@@ -0,0 +1,176 @@
+package org.zhongzheng.common.core.domain.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.zhongzheng.common.annotation.Excel;
+import com.zhongzheng.common.annotation.Excel.ColumnType;
+import com.zhongzheng.common.annotation.Excel.Type;
+import com.zhongzheng.common.annotation.Excels;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 用户对象 super_sys_user
+ *
+ * @author hjl
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class TopSysUser implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 用户ID */
+    @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号")
+    @TableId(value = "user_id",type = IdType.AUTO)
+    private Long userId;
+
+    /** 部门ID */
+    @Excel(name = "部门编号", type = Type.IMPORT)
+    private Long deptId;
+
+    /** 用户账号 */
+    @NotBlank(message = "用户账号不能为空")
+    @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
+    @Excel(name = "登录名称")
+    private String userName;
+
+    /** 用户昵称 */
+    @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
+    @Excel(name = "用户名称")
+    private String nickName;
+
+    /** 用户邮箱 */
+    @Email(message = "邮箱格式不正确")
+    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
+    @Excel(name = "用户邮箱")
+    private String email;
+
+    /** 手机号码 */
+    @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
+    @Excel(name = "手机号码")
+    private String phonenumber;
+
+    /** 用户性别 */
+    @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
+    private String sex;
+
+    /** 用户头像 */
+    private String avatar;
+
+    /** 用户编码 */
+    private String code;
+
+    /** 密码 */
+    private String password;
+
+    @JsonIgnore
+    @JsonProperty
+    public String getPassword() {
+        return password;
+    }
+
+    /** 盐加密 */
+    @TableField(exist = false)
+    private String salt;
+
+    /** 帐号状态(1正常 0停用) */
+    @Excel(name = "帐号状态", readConverterExp = "1=正常,0=停用")
+    private String status;
+
+    /** 删除标志(0代表存在 2代表删除) */
+    @TableLogic
+    private String delFlag;
+
+    /** 最后登录IP */
+    @Excel(name = "最后登录IP", type = Type.EXPORT)
+    private String loginIp;
+
+    /** 最后登录时间 */
+    @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
+    private Date loginDate;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+
+    @TableField(exist = false)
+    private List<String> statusArray;
+
+    /**
+     * 请求参数
+     */
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+    /** 部门对象 */
+    @Excels({
+        @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
+        @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
+    })
+    @TableField(exist = false)
+    private SysDept dept;
+
+    /** 角色对象 */
+    @TableField(exist = false)
+    private List<TopSysRole> roles;
+
+    /** 角色组 */
+    @TableField(exist = false)
+    private List<Long> roleIds;
+
+    /** 角色名称 */
+    @TableField(exist = false)
+    private String roleName;
+
+    /** 岗位组 */
+    @TableField(exist = false)
+    private Long[] postIds;
+
+    public TopSysUser(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public boolean isAdmin()
+    {
+        return isAdmin(this.userName);
+    }
+
+    public static boolean isAdmin(String userName)
+    {
+        return userName != null && "admin".equals(userName) ;
+    }
+
+}

+ 49 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/model/LoginBody.java

@@ -0,0 +1,49 @@
+package org.zhongzheng.common.core.domain.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * 用户登录对象
+ *
+ * @author zhongzheng
+ */
+@ApiModel("登录用户")
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class LoginBody
+{
+    /**
+     * 用户名
+     */
+    @ApiModelProperty(value ="用户名",required = true)
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    @ApiModelProperty(value ="用户密码",required = true)
+    private String password;
+
+    /**
+     * 验证码
+     */
+    @ApiModelProperty(value ="验证码",required = true)
+    private String code;
+
+    /**
+     * 唯一标识
+     */
+    @ApiModelProperty(value ="验证码uuid",required = true)
+    private String uuid = "";
+
+    @ApiModelProperty(value ="短信验证码")
+    private String smsCode;
+
+    @ApiModelProperty(value ="手机号码")
+    private String phonenumber;
+}

+ 143 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/model/LoginUser.java

@@ -0,0 +1,143 @@
+package org.zhongzheng.common.core.domain.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.zhongzheng.common.core.domain.entity.SysUser;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class LoginUser implements UserDetails
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 权限列表
+     */
+    private Set<String> permissions;
+
+    /**
+     * 用户信息
+     */
+    private SysUser user;
+
+
+    public LoginUser(SysUser user, Set<String> permissions)
+    {
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    @JsonIgnore
+    @Override
+    public String getPassword()
+    {
+        return user.getPassword();
+    }
+
+    @Override
+    public String getUsername()
+    {
+        return user.getUserName();
+    }
+
+    /**
+     * 账户是否未过期,过期无法验证
+     */
+    @JsonIgnore
+    @Override
+    public boolean isAccountNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 指定用户是否解锁,锁定的用户无法进行身份验证
+     *
+     * @return
+     */
+    @JsonIgnore
+    @Override
+    public boolean isAccountNonLocked()
+    {
+        return true;
+    }
+
+    /**
+     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
+     *
+     * @return
+     */
+    @JsonIgnore
+    @Override
+    public boolean isCredentialsNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 是否可用 ,禁用的用户不能身份验证
+     *
+     * @return
+     */
+    @JsonIgnore
+    @Override
+    public boolean isEnabled()
+    {
+        return true;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities()
+    {
+        return null;
+    }
+}

+ 142 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/domain/model/TopLoginUser.java

@@ -0,0 +1,142 @@
+package org.zhongzheng.common.core.domain.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.zhongzheng.common.core.domain.entity.TopSysUser;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author hjl
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class TopLoginUser implements UserDetails
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 权限列表
+     */
+    private Set<String> permissions;
+
+    /**
+     * 用户信息
+     */
+    private TopSysUser user;
+
+    public TopLoginUser(TopSysUser user, Set<String> permissions)
+    {
+        this.user = user;
+        this.permissions = permissions;
+    }
+
+    @JsonIgnore
+    @Override
+    public String getPassword()
+    {
+        return user.getPassword();
+    }
+
+    @Override
+    public String getUsername()
+    {
+        return user.getUserName();
+    }
+
+    /**
+     * 账户是否未过期,过期无法验证
+     */
+    @JsonIgnore
+    @Override
+    public boolean isAccountNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 指定用户是否解锁,锁定的用户无法进行身份验证
+     *
+     * @return
+     */
+    @JsonIgnore
+    @Override
+    public boolean isAccountNonLocked()
+    {
+        return true;
+    }
+
+    /**
+     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
+     *
+     * @return
+     */
+    @JsonIgnore
+    @Override
+    public boolean isCredentialsNonExpired()
+    {
+        return true;
+    }
+
+    /**
+     * 是否可用 ,禁用的用户不能身份验证
+     *
+     * @return
+     */
+    @JsonIgnore
+    @Override
+    public boolean isEnabled()
+    {
+        return true;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities()
+    {
+        return null;
+    }
+}

+ 40 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/page/PageDomain.java

@@ -0,0 +1,40 @@
+package org.zhongzheng.common.core.page;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * 分页数据
+ * 
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class PageDomain
+{
+    /** 当前记录起始索引 */
+    private Integer pageNum;
+
+    /** 每页显示记录数 */
+    private Integer pageSize;
+
+    /** 排序列 */
+    private String orderByColumn;
+
+    /** 排序的方向desc或者asc */
+    private String isAsc = "asc";
+
+    public String getOrderBy()
+    {
+        if (StrUtil.isEmpty(orderByColumn))
+        {
+            return "";
+        }
+        return StrUtil.toUnderlineCase(orderByColumn) + " " + isAsc;
+    }
+
+}

+ 47 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/page/TableDataInfo.java

@@ -0,0 +1,47 @@
+package org.zhongzheng.common.core.page;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 表格分页数据对象
+ * 
+ * @author zhongzheng
+ */
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class TableDataInfo<T> implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 总记录数 */
+    private long total;
+
+    /** 列表数据 */
+    private List<T> rows;
+
+    /** 消息状态码 */
+    private int code;
+
+    /** 消息内容 */
+    private String msg;
+
+    /**
+     * 分页
+     * 
+     * @param list 列表数据
+     * @param total 总记录数
+     */
+    public TableDataInfo(List<T> list, int total)
+    {
+        this.rows = list;
+        this.total = total;
+    }
+
+}

+ 49 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/page/TableSupport.java

@@ -0,0 +1,49 @@
+package org.zhongzheng.common.core.page;
+
+import com.zhongzheng.common.utils.ServletUtils;
+
+/**
+ * 表格数据处理
+ * 
+ * @author zhongzheng
+ */
+public class TableSupport
+{
+    /**
+     * 当前记录起始索引
+     */
+    public static final String PAGE_NUM = "pageNum";
+
+    /**
+     * 每页显示记录数
+     */
+    public static final String PAGE_SIZE = "pageSize";
+
+    /**
+     * 排序列
+     */
+    public static final String ORDER_BY_COLUMN = "orderByColumn";
+
+    /**
+     * 排序的方向 "desc" 或者 "asc".
+     */
+    public static final String IS_ASC = "isAsc";
+
+    /**
+     * 封装分页对象
+     */
+    public static PageDomain getPageDomain()
+    {
+        PageDomain pageDomain = new PageDomain();
+        pageDomain.setPageNum(ServletUtils.getParameterToInt(PAGE_NUM));
+        pageDomain.setPageSize(ServletUtils.getParameterToInt(PAGE_SIZE));
+        pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
+        pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
+        return pageDomain;
+    }
+
+    public static PageDomain buildPageRequest()
+    {
+        return getPageDomain();
+    }
+}

+ 344 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/redis/RedisCache.java

@@ -0,0 +1,344 @@
+package org.zhongzheng.common.core.redis;
+
+import cn.hutool.core.lang.Validator;
+import com.zhongzheng.common.utils.ServletUtils;
+import com.zhongzheng.common.utils.ToolsUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.data.redis.core.BoundSetOperations;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * spring redis 工具类
+ *
+ * @author zhongzheng
+ **/
+@SuppressWarnings(value = { "unchecked", "rawtypes" })
+@Component
+public class RedisCache
+{
+    @Autowired
+    public RedisTemplate redisTemplate;
+
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key 缓存的键值
+     * @param value 缓存的值
+     */
+    public <T> void setCacheObject(final String key, final T value)
+    {
+        String unionKey = key;
+        if(Validator.isNotEmpty(ServletUtils.getRequest())){
+            String tenantId = ServletUtils.getRequest().getHeader("TenantId");
+            if(Validator.isNotEmpty(tenantId)){
+                unionKey = tenantId + key;
+            }
+        }
+        redisTemplate.opsForValue().set(unionKey, value);
+    }
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key 缓存的键值
+     * @param value 缓存的值
+     * @param timeout 时间
+     * @param timeUnit 时间颗粒度
+     */
+    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
+    {
+        String unionKey = key;
+        if(Validator.isNotEmpty(ServletUtils.getRequest())){
+            String tenantId = ToolsUtils.getTenantId();
+            if(Validator.isNotEmpty(tenantId)){
+                unionKey = tenantId + key;
+            }
+        }
+        redisTemplate.opsForValue().set(unionKey, value, timeout, timeUnit);
+
+    }
+
+    public <T> void setCacheObjectTenant(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
+    {
+        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout)
+    {
+        return expire(key, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key Redis键
+     * @param timeout 超时时间
+     * @param unit 时间单位
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout, final TimeUnit unit)
+    {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObject(final String key)
+    {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        String unionKey = key;
+        if(Validator.isNotEmpty(ServletUtils.getRequest())){
+            String tenantId = ToolsUtils.getTenantId();
+            if(Validator.isNotEmpty(tenantId)){
+                unionKey = tenantId + key;
+            }
+        }
+        return operation.get(unionKey);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObjectNoTenant(final String key)
+    {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        return operation.get(key);
+    }
+
+
+    /**
+     * 删除单个对象
+     *
+     * @param key
+     */
+    public boolean deleteObject(final String key)
+    {
+        String unionKey = key;
+        if(Validator.isNotEmpty(ServletUtils.getRequest())){
+            String tenantId = ServletUtils.getRequest().getHeader("TenantId");
+            if(Validator.isNotEmpty(tenantId)){
+                unionKey = tenantId + key;
+            }
+        }
+        return redisTemplate.delete(unionKey);
+    }
+
+    /**
+     * 删除集合对象
+     *
+     * @param collection 多个对象
+     * @return
+     */
+    public long deleteObject(final Collection collection)
+    {
+        return redisTemplate.delete(collection);
+    }
+
+    /**
+     * 缓存List数据
+     *
+     * @param key 缓存的键值
+     * @param dataList 待缓存的List数据
+     * @return 缓存的对象
+     */
+    public <T> long setCacheList(final String key, final List<T> dataList)
+    {
+        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
+        return count == null ? 0 : count;
+    }
+
+    /**
+     * 获得缓存的list对象
+     *
+     * @param key 缓存的键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> List<T> getCacheList(final String key)
+    {
+        return redisTemplate.opsForList().range(key, 0, -1);
+    }
+
+    /**
+     * 缓存Set
+     *
+     * @param key 缓存键值
+     * @param dataSet 缓存的数据
+     * @return 缓存数据的对象
+     */
+    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
+    {
+        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
+        Iterator<T> it = dataSet.iterator();
+        while (it.hasNext())
+        {
+            setOperation.add(it.next());
+        }
+        return setOperation;
+    }
+
+    /**
+     * 获得缓存的set
+     *
+     * @param key
+     * @return
+     */
+    public <T> Set<T> getCacheSet(final String key)
+    {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 缓存Map
+     *
+     * @param key
+     * @param dataMap
+     */
+    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
+    {
+        if (dataMap != null) {
+            redisTemplate.opsForHash().putAll(key, dataMap);
+        }
+    }
+
+    /**
+     * 获得缓存的Map
+     *
+     * @param key
+     * @return
+     */
+    public <T> Map<String, T> getCacheMap(final String key)
+    {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 往Hash中存入数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @param value 值
+     */
+    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
+    {
+        redisTemplate.opsForHash().put(key, hKey, value);
+    }
+
+    /**
+     * 获取Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKey Hash键
+     * @return Hash中的对象
+     */
+    public <T> T getCacheMapValue(final String key, final String hKey)
+    {
+        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
+        return opsForHash.get(key, hKey);
+    }
+
+    /**
+     * 获取多个Hash中的数据
+     *
+     * @param key Redis键
+     * @param hKeys Hash键集合
+     * @return Hash对象集合
+     */
+    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
+    {
+        return redisTemplate.opsForHash().multiGet(key, hKeys);
+    }
+
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+    public Collection<String> keys(final String pattern)
+    {
+        return redisTemplate.keys(pattern);
+    }
+
+    /**
+     * 加锁,自旋重试三次
+     *
+     * @param redisLockEntity 锁实体
+     * @return
+     */
+    public boolean lock(RedisLockEntity redisLockEntity) {
+        boolean locked = false;
+        int tryCount = 10;
+        while (!locked && tryCount > 0) {
+            locked = redisTemplate.opsForValue().setIfAbsent(redisLockEntity.getLockKey(), redisLockEntity.getRequestId(), 5, TimeUnit.SECONDS);
+            tryCount--;
+            try {
+                Thread.sleep(400);
+            } catch (InterruptedException e) {
+                logger.error("线程被中断" + Thread.currentThread().getId());
+            }
+        }
+        return locked;
+    }
+
+    /**
+     * 非原子解锁,可能解别人锁,不安全
+     *
+     * @param redisLockEntity
+     * @return
+     */
+    public boolean unlock(RedisLockEntity redisLockEntity) {
+        if (redisLockEntity == null || redisLockEntity.getLockKey() == null || redisLockEntity.getRequestId() == null)
+            return false;
+        boolean releaseLock = false;
+        String requestId = (String) redisTemplate.opsForValue().get(redisLockEntity.getLockKey());
+        if (redisLockEntity.getRequestId().equals(requestId)) {
+            releaseLock = redisTemplate.delete(redisLockEntity.getLockKey());
+        }
+        return releaseLock;
+    }
+
+    /**
+     * 使用lua脚本解锁,不会解除别人锁
+     *
+     * @param redisLockEntity
+     * @return
+     */
+    public boolean unlockLua(RedisLockEntity redisLockEntity) {
+        if (redisLockEntity == null || redisLockEntity.getLockKey() == null || redisLockEntity.getRequestId() == null)
+            return false;
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript();
+        //用于解锁的lua脚本位置
+        redisScript.setLocation(new ClassPathResource("unlock.lua"));
+        redisScript.setResultType(Long.class);
+        //没有指定序列化方式,默认使用上面配置的
+        Object result = redisTemplate.execute(redisScript, Arrays.asList(redisLockEntity.getLockKey()), redisLockEntity.getRequestId());
+        return result.equals(Long.valueOf(1));
+    }
+}

+ 12 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/core/redis/RedisLockEntity.java

@@ -0,0 +1,12 @@
+package org.zhongzheng.common.core.redis;
+
+import lombok.Data;
+
+@Data
+public class RedisLockEntity {
+    public static final String KEY_LOCK_GRADE ="lock_grade";  //锁班级
+
+
+    private String lockKey;
+    private String requestId;
+}

+ 20 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/enums/BusinessStatus.java

@@ -0,0 +1,20 @@
+package org.zhongzheng.common.enums;
+
+/**
+ * 操作状态
+ * 
+ * @author zhongzheng
+ *
+ */
+public enum BusinessStatus
+{
+    /**
+     * 成功
+     */
+    SUCCESS,
+
+    /**
+     * 失败
+     */
+    FAIL,
+}

+ 59 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/enums/BusinessType.java

@@ -0,0 +1,59 @@
+package org.zhongzheng.common.enums;
+
+/**
+ * 业务操作类型
+ * 
+ * @author zhongzheng
+ */
+public enum BusinessType
+{
+    /**
+     * 其它
+     */
+    OTHER,
+
+    /**
+     * 新增
+     */
+    INSERT,
+
+    /**
+     * 修改
+     */
+    UPDATE,
+
+    /**
+     * 删除
+     */
+    DELETE,
+
+    /**
+     * 授权
+     */
+    GRANT,
+
+    /**
+     * 导出
+     */
+    EXPORT,
+
+    /**
+     * 导入
+     */
+    IMPORT,
+
+    /**
+     * 强退
+     */
+    FORCE,
+
+    /**
+     * 生成代码
+     */
+    GENCODE,
+    
+    /**
+     * 清空数据
+     */
+    CLEAN,
+}

+ 19 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/enums/DataSourceType.java

@@ -0,0 +1,19 @@
+package org.zhongzheng.common.enums;
+
+/**
+ * 数据源
+ * 
+ * @author zhongzheng
+ */
+public enum DataSourceType
+{
+    /**
+     * 主库
+     */
+    MASTER,
+
+    /**
+     * 从库
+     */
+    SLAVE
+}

+ 37 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/enums/HttpMethod.java

@@ -0,0 +1,37 @@
+package org.zhongzheng.common.enums;
+
+import org.springframework.lang.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 请求方式
+ *
+ * @author zhongzheng
+ */
+public enum HttpMethod
+{
+    GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
+
+    private static final Map<String, HttpMethod> mappings = new HashMap<>(16);
+
+    static
+    {
+        for (HttpMethod httpMethod : values())
+        {
+            mappings.put(httpMethod.name(), httpMethod);
+        }
+    }
+
+    @Nullable
+    public static HttpMethod resolve(@Nullable String method)
+    {
+        return (method != null ? mappings.get(method) : null);
+    }
+
+    public boolean matches(String method)
+    {
+        return (this == resolve(method));
+    }
+}

+ 24 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/enums/OperatorType.java

@@ -0,0 +1,24 @@
+package org.zhongzheng.common.enums;
+
+/**
+ * 操作人类别
+ * 
+ * @author zhongzheng
+ */
+public enum OperatorType
+{
+    /**
+     * 其它
+     */
+    OTHER,
+
+    /**
+     * 后台用户
+     */
+    MANAGE,
+
+    /**
+     * 手机端用户
+     */
+    MOBILE
+}

+ 30 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/enums/UserStatus.java

@@ -0,0 +1,30 @@
+package org.zhongzheng.common.enums;
+
+/**
+ * 用户状态
+ *
+ * @author zhongzheng
+ */
+public enum UserStatus
+{
+    OK("1", "正常"), DISABLE("0", "停用"), DELETED("-1", "删除");
+
+    private final String code;
+    private final String info;
+
+    UserStatus(String code, String info)
+    {
+        this.code = code;
+        this.info = info;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public String getInfo()
+    {
+        return info;
+    }
+}

+ 97 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/BaseException.java

@@ -0,0 +1,97 @@
+package org.zhongzheng.common.exception;
+
+import cn.hutool.core.lang.Validator;
+import com.zhongzheng.common.utils.MessageUtils;
+
+/**
+ * 基础异常
+ * 
+ * @author zhongzheng
+ */
+public class BaseException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 所属模块
+     */
+    private String module;
+
+    /**
+     * 错误码
+     */
+    private String code;
+
+    /**
+     * 错误码对应的参数
+     */
+    private Object[] args;
+
+    /**
+     * 错误消息
+     */
+    private String defaultMessage;
+
+    public BaseException(String module, String code, Object[] args, String defaultMessage)
+    {
+        this.module = module;
+        this.code = code;
+        this.args = args;
+        this.defaultMessage = defaultMessage;
+    }
+
+    public BaseException(String module, String code, Object[] args)
+    {
+        this(module, code, args, null);
+    }
+
+    public BaseException(String module, String defaultMessage)
+    {
+        this(module, null, null, defaultMessage);
+    }
+
+    public BaseException(String code, Object[] args)
+    {
+        this(null, code, args, null);
+    }
+
+    public BaseException(String defaultMessage)
+    {
+        this(null, null, null, defaultMessage);
+    }
+
+    @Override
+    public String getMessage()
+    {
+        String message = null;
+        if (!Validator.isEmpty(code))
+        {
+            message = MessageUtils.message(code, args);
+        }
+        if (message == null)
+        {
+            message = defaultMessage;
+        }
+        return message;
+    }
+
+    public String getModule()
+    {
+        return module;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public Object[] getArgs()
+    {
+        return args;
+    }
+
+    public String getDefaultMessage()
+    {
+        return defaultMessage;
+    }
+}

+ 43 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/CustomException.java

@@ -0,0 +1,43 @@
+package org.zhongzheng.common.exception;
+
+/**
+ * 自定义异常
+ * 
+ * @author zhongzheng
+ */
+public class CustomException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    private Integer code;
+
+    private String message;
+
+    public CustomException(String message)
+    {
+        this.message = message;
+    }
+
+    public CustomException(String message, Integer code)
+    {
+        this.message = message;
+        this.code = code;
+    }
+
+    public CustomException(String message, Throwable e)
+    {
+        super(message, e);
+        this.message = message;
+    }
+
+    @Override
+    public String getMessage()
+    {
+        return message;
+    }
+
+    public Integer getCode()
+    {
+        return code;
+    }
+}

+ 15 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/DemoModeException.java

@@ -0,0 +1,15 @@
+package org.zhongzheng.common.exception;
+
+/**
+ * 演示模式异常
+ * 
+ * @author zhongzheng
+ */
+public class DemoModeException extends RuntimeException
+{
+    private static final long serialVersionUID = 1L;
+
+    public DemoModeException()
+    {
+    }
+}

+ 26 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/UtilException.java

@@ -0,0 +1,26 @@
+package org.zhongzheng.common.exception;
+
+/**
+ * 工具类异常
+ * 
+ * @author zhongzheng
+ */
+public class UtilException extends RuntimeException
+{
+    private static final long serialVersionUID = 8247610319171014183L;
+
+    public UtilException(Throwable e)
+    {
+        super(e.getMessage(), e);
+    }
+
+    public UtilException(String message)
+    {
+        super(message);
+    }
+
+    public UtilException(String message, Throwable throwable)
+    {
+        super(message, throwable);
+    }
+}

+ 19 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/file/FileException.java

@@ -0,0 +1,19 @@
+package org.zhongzheng.common.exception.file;
+
+import com.zhongzheng.common.exception.BaseException;
+
+/**
+ * 文件信息异常类
+ * 
+ * @author zhongzheng
+ */
+public class FileException extends BaseException
+{
+    private static final long serialVersionUID = 1L;
+
+    public FileException(String code, Object[] args)
+    {
+        super("file", code, args, null);
+    }
+
+}

+ 16 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/file/FileNameLengthLimitExceededException.java

@@ -0,0 +1,16 @@
+package org.zhongzheng.common.exception.file;
+
+/**
+ * 文件名称超长限制异常类
+ * 
+ * @author zhongzheng
+ */
+public class FileNameLengthLimitExceededException extends FileException
+{
+    private static final long serialVersionUID = 1L;
+
+    public FileNameLengthLimitExceededException(int defaultFileNameLength)
+    {
+        super("upload.filename.exceed.length", new Object[] { defaultFileNameLength });
+    }
+}

+ 16 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/file/FileSizeLimitExceededException.java

@@ -0,0 +1,16 @@
+package org.zhongzheng.common.exception.file;
+
+/**
+ * 文件名大小限制异常类
+ * 
+ * @author zhongzheng
+ */
+public class FileSizeLimitExceededException extends FileException
+{
+    private static final long serialVersionUID = 1L;
+
+    public FileSizeLimitExceededException(long defaultMaxSize)
+    {
+        super("upload.exceed.maxSize", new Object[] { defaultMaxSize });
+    }
+}

+ 72 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/file/InvalidExtensionException.java

@@ -0,0 +1,72 @@
+package org.zhongzheng.common.exception.file;
+
+import org.apache.commons.fileupload.FileUploadException;
+
+import java.util.Arrays;
+
+/**
+ * 文件上传 误异常类
+ * 
+ * @author zhongzheng
+ */
+public class InvalidExtensionException extends FileUploadException
+{
+    private static final long serialVersionUID = 1L;
+
+    private String[] allowedExtension;
+    private String extension;
+    private String filename;
+
+    public InvalidExtensionException(String[] allowedExtension, String extension, String filename)
+    {
+        super("filename : [" + filename + "], extension : [" + extension + "], allowed extension : [" + Arrays.toString(allowedExtension) + "]");
+        this.allowedExtension = allowedExtension;
+        this.extension = extension;
+        this.filename = filename;
+    }
+
+    public String[] getAllowedExtension()
+    {
+        return allowedExtension;
+    }
+
+    public String getExtension()
+    {
+        return extension;
+    }
+
+    public String getFilename()
+    {
+        return filename;
+    }
+
+    public static class InvalidImageExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidFlashExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+
+    public static class InvalidMediaExtensionException extends InvalidExtensionException
+    {
+        private static final long serialVersionUID = 1L;
+
+        public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename)
+        {
+            super(allowedExtension, extension, filename);
+        }
+    }
+}

+ 34 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/job/TaskException.java

@@ -0,0 +1,34 @@
+package org.zhongzheng.common.exception.job;
+
+/**
+ * 计划策略异常
+ * 
+ * @author zhongzheng
+ */
+public class TaskException extends Exception
+{
+    private static final long serialVersionUID = 1L;
+
+    private Code code;
+
+    public TaskException(String msg, Code code)
+    {
+        this(msg, code, null);
+    }
+
+    public TaskException(String msg, Code code, Exception nestedEx)
+    {
+        super(msg, nestedEx);
+        this.code = code;
+    }
+
+    public Code getCode()
+    {
+        return code;
+    }
+
+    public enum Code
+    {
+        TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE
+    }
+}

+ 16 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/user/CaptchaException.java

@@ -0,0 +1,16 @@
+package org.zhongzheng.common.exception.user;
+
+/**
+ * 验证码错误异常类
+ * 
+ * @author zhongzheng
+ */
+public class CaptchaException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public CaptchaException()
+    {
+        super("user.jcaptcha.error", null);
+    }
+}

+ 16 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/user/CaptchaExpireException.java

@@ -0,0 +1,16 @@
+package org.zhongzheng.common.exception.user;
+
+/**
+ * 验证码失效异常类
+ * 
+ * @author zhongzheng
+ */
+public class CaptchaExpireException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public CaptchaExpireException()
+    {
+        super("user.jcaptcha.expire", null);
+    }
+}

+ 18 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/user/UserException.java

@@ -0,0 +1,18 @@
+package org.zhongzheng.common.exception.user;
+
+import com.zhongzheng.common.exception.BaseException;
+
+/**
+ * 用户信息异常类
+ * 
+ * @author zhongzheng
+ */
+public class UserException extends BaseException
+{
+    private static final long serialVersionUID = 1L;
+
+    public UserException(String code, Object[] args)
+    {
+        super("user", code, args, null);
+    }
+}

+ 16 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/exception/user/UserPasswordNotMatchException.java

@@ -0,0 +1,16 @@
+package org.zhongzheng.common.exception.user;
+
+/**
+ * 用户密码不正确或不符合规范异常类
+ * 
+ * @author zhongzheng
+ */
+public class UserPasswordNotMatchException extends UserException
+{
+    private static final long serialVersionUID = 1L;
+
+    public UserPasswordNotMatchException()
+    {
+        super("user.password.not.match", null);
+    }
+}

+ 47 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/filter/RepeatableFilter.java

@@ -0,0 +1,47 @@
+package org.zhongzheng.common.filter;
+
+import cn.hutool.core.util.StrUtil;
+import org.springframework.http.MediaType;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * Repeatable 过滤器
+ * 
+ * @author zhongzheng
+ */
+public class RepeatableFilter implements Filter
+{
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException
+    {
+        ServletRequest requestWrapper = null;
+        if (request instanceof HttpServletRequest
+                && StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE))
+        {
+            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
+        }
+        if (null == requestWrapper)
+        {
+            chain.doFilter(request, response);
+        }
+        else
+        {
+            chain.doFilter(requestWrapper, response);
+        }
+    }
+
+    @Override
+    public void destroy()
+    {
+
+    }
+}

+ 76 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/filter/RepeatedlyRequestWrapper.java

@@ -0,0 +1,76 @@
+package org.zhongzheng.common.filter;
+
+import com.zhongzheng.common.utils.http.HttpHelper;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 构建可重复读取inputStream的request
+ * 
+ * @author zhongzheng
+ */
+public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper
+{
+    private final byte[] body;
+
+    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException
+    {
+        super(request);
+        request.setCharacterEncoding("UTF-8");
+        response.setCharacterEncoding("UTF-8");
+
+        body = HttpHelper.getBodyString(request).getBytes("UTF-8");
+    }
+
+    @Override
+    public BufferedReader getReader() throws IOException
+    {
+        return new BufferedReader(new InputStreamReader(getInputStream()));
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+        return new ServletInputStream()
+        {
+            @Override
+            public int read() throws IOException
+            {
+                return bais.read();
+            }
+
+            @Override
+            public int available() throws IOException
+            {
+                return body.length;
+            }
+
+            @Override
+            public boolean isFinished()
+            {
+                return false;
+            }
+
+            @Override
+            public boolean isReady()
+            {
+                return false;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener)
+            {
+
+            }
+        };
+    }
+}

+ 92 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/filter/XssFilter.java

@@ -0,0 +1,92 @@
+package org.zhongzheng.common.filter;
+
+import cn.hutool.core.util.StrUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 防止XSS攻击的过滤器
+ * 
+ * @author zhongzheng
+ */
+public class XssFilter implements Filter
+{
+    /**
+     * 排除链接
+     */
+    public List<String> excludes = new ArrayList<>();
+
+    /**
+     * xss过滤开关
+     */
+    public boolean enabled = false;
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        String tempExcludes = filterConfig.getInitParameter("excludes");
+        String tempEnabled = filterConfig.getInitParameter("enabled");
+        if (StrUtil.isNotEmpty(tempExcludes))
+        {
+            String[] url = tempExcludes.split(",");
+            for (int i = 0; url != null && i < url.length; i++)
+            {
+                excludes.add(url[i]);
+            }
+        }
+        if (StrUtil.isNotEmpty(tempEnabled))
+        {
+            enabled = Boolean.valueOf(tempEnabled);
+        }
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException
+    {
+        HttpServletRequest req = (HttpServletRequest) request;
+        HttpServletResponse resp = (HttpServletResponse) response;
+        if (handleExcludeURL(req, resp))
+        {
+            chain.doFilter(request, response);
+            return;
+        }
+        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
+        chain.doFilter(xssRequest, response);
+    }
+
+    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response)
+    {
+        if (!enabled)
+        {
+            return true;
+        }
+        if (excludes == null || excludes.isEmpty())
+        {
+            return false;
+        }
+        String url = request.getServletPath();
+        for (String pattern : excludes)
+        {
+            Pattern p = Pattern.compile("^" + pattern);
+            Matcher m = p.matcher(url);
+            if (m.find())
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void destroy()
+    {
+
+    }
+}

+ 105 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/filter/XssHttpServletRequestWrapper.java

@@ -0,0 +1,105 @@
+package org.zhongzheng.common.filter;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.http.HtmlUtil;
+import org.apache.commons.io.IOUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * XSS过滤处理
+ * 
+ * @author zhongzheng
+ */
+public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
+{
+    /**
+     * @param request
+     */
+    public XssHttpServletRequestWrapper(HttpServletRequest request)
+    {
+        super(request);
+    }
+
+    @Override
+    public String[] getParameterValues(String name)
+    {
+        String[] values = super.getParameterValues(name);
+        if (values != null)
+        {
+            int length = values.length;
+            String[] escapseValues = new String[length];
+            for (int i = 0; i < length; i++)
+            {
+                // 防xss攻击和过滤前后空格
+                escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
+            }
+            return escapseValues;
+        }
+        return super.getParameterValues(name);
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException
+    {
+        // 非json类型,直接返回
+        if (!isJsonRequest())
+        {
+            return super.getInputStream();
+        }
+
+        // 为空,直接返回
+        String json = IOUtils.toString(super.getInputStream(), "utf-8");
+        if (Validator.isEmpty(json))
+        {
+            return super.getInputStream();
+        }
+
+        // xss过滤
+        json = HtmlUtil.cleanHtmlTag(json).trim();
+        final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes("utf-8"));
+        return new ServletInputStream()
+        {
+            @Override
+            public boolean isFinished()
+            {
+                return true;
+            }
+
+            @Override
+            public boolean isReady()
+            {
+                return true;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener)
+            {
+            }
+
+            @Override
+            public int read() throws IOException
+            {
+                return bis.read();
+            }
+        };
+    }
+
+    /**
+     * 是否是Json请求
+     * 
+     * @param request
+     */
+    public boolean isJsonRequest()
+    {
+        String header = super.getHeader(HttpHeaders.CONTENT_TYPE);
+        return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header);
+    }
+}

+ 80 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/type/EncryptHandler.java

@@ -0,0 +1,80 @@
+package org.zhongzheng.common.type;
+
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.symmetric.AES;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.TypeHandler;
+import org.springframework.util.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+@MappedJdbcTypes(JdbcType.VARCHAR)
+public class EncryptHandler  implements TypeHandler<String> {
+
+    private static final byte[] KEYS = "base20230213zzkj".getBytes(StandardCharsets.UTF_8);
+
+    public static final boolean ENCRYPT_STATUS = true; //是否开启字段加密
+
+
+
+    public static String decrypt(String value) {
+        if (null == value) {
+            return null;
+        }
+        if (value.length()<=20 ) {
+            return value;
+        }
+        return SecureUtil.aes(KEYS).decryptStr(value);
+    }
+
+    public static String encrypt(String value){
+        if (null == value) {
+            return null;
+        }
+        if(ENCRYPT_STATUS){
+            AES aes = SecureUtil.aes(KEYS);
+            String encrypt = aes.encryptHex(value);
+            return encrypt;
+        }else{
+            return value;
+        }
+
+    }
+
+    @Override
+    public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
+        if (StringUtils.isEmpty(s)) {
+            preparedStatement.setString(i, null);
+            return;
+        }
+        String encrypt = encrypt(s);
+        preparedStatement.setString(i, encrypt);
+    }
+
+    @Override
+    public String getResult(ResultSet resultSet, String s) throws SQLException {
+        if(ENCRYPT_STATUS){
+            return decrypt(resultSet.getString(s));
+        }else{
+            return resultSet.getString(s);
+        }
+    }
+
+    @Override
+    public String getResult(ResultSet resultSet, int i) throws SQLException {
+        return null;
+    }
+
+    @Override
+    public String getResult(CallableStatement callableStatement, int i) throws SQLException {
+        return null;
+    }
+
+
+}

+ 159 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/AES.java

@@ -0,0 +1,159 @@
+package org.zhongzheng.common.utils;
+
+import org.apache.commons.codec.binary.Base64;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.security.*;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+
+public class AES {
+    public static boolean initialized = false;
+
+    /**
+     * AES解密
+     * @param content 密文
+     * @return
+     * @throws InvalidAlgorithmParameterException
+     * @throws NoSuchProviderException
+     */
+    public static byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
+        initialize();
+        try {
+//            Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
+            Key sKeySpec = new SecretKeySpec(keyByte, "AES");
+
+            cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
+            byte[] result = cipher.doFinal(content);
+            return result;
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        } catch (NoSuchPaddingException e) {
+            e.printStackTrace();
+        } catch (InvalidKeyException e) {
+            e.printStackTrace();
+        } catch (IllegalBlockSizeException e) {
+            e.printStackTrace();
+        } catch (BadPaddingException e) {
+            e.printStackTrace();
+        } catch (NoSuchProviderException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static void initialize(){
+        if (initialized) return;
+        Security.addProvider(new BouncyCastleProvider());
+        initialized = true;
+    }
+    //生成iv
+    public static AlgorithmParameters generateIV(byte[] iv) throws Exception{
+        AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
+        params.init(new IvParameterSpec(iv));
+        return params;
+    }
+
+    public static String SHA1(String decript) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-1");
+            digest.update(decript.getBytes());
+            byte messageDigest[] = digest.digest();
+            // Create Hex String
+            StringBuffer hexString = new StringBuffer();
+            // 字节数组转换为 十六进制 数
+            for (int i = 0; i < messageDigest.length; i++) {
+                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
+                if (shaHex.length() < 2) {
+                    hexString.append(0);
+                }
+                hexString.append(shaHex);
+            }
+            return hexString.toString();
+
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    /**
+     * RSA公钥加密
+     *
+     * @param str
+     *            加密字符串
+     * @param publicKey
+     *            公钥
+     * @return 密文
+     * @throws Exception
+     *             加密过程中的异常信息
+     */
+    public static String encrypt( String str, String publicKey ) throws Exception{
+        //base64编码的公钥
+        byte[] decoded = Base64.decodeBase64(publicKey);
+        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
+        //RSA加密
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
+        String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
+        return outStr;
+    }
+
+    /**
+     * RSA私钥解密
+     *
+     * @param str
+     *            加密字符串
+     * @param privateKey
+     *            私钥
+     * @return 铭文
+     * @throws Exception
+     *             解密过程中的异常信息
+     */
+    public static String decrypt(String str, String privateKey) throws Exception{
+        //64位解码加密后的字符串
+        byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
+        //base64编码的私钥
+        byte[] decoded = Base64.decodeBase64(privateKey);
+        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
+        //RSA解密
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, priKey);
+        String outStr = new String(cipher.doFinal(inputByte));
+        return outStr;
+    }
+
+    public static String getStringByInputStream_1(InputStream inputStream){
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            byte[] b = new byte[10240];
+            int n;
+            while ((n = inputStream.read(b)) != -1) {
+                outputStream.write(b, 0, n);
+            }
+        } catch (Exception e) {
+            try {
+                inputStream.close();
+                outputStream.close();
+            } catch (Exception e1) {
+            }
+        }
+        return outputStream.toString();
+    }
+}

+ 751 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/DateUtils.java

@@ -0,0 +1,751 @@
+package org.zhongzheng.common.utils;
+
+
+import cn.hutool.core.lang.Validator;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.time.DateFormatUtils;
+import org.apache.http.util.Args;
+
+import java.lang.management.ManagementFactory;
+import java.lang.ref.SoftReference;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * 时间工具类
+ *
+ * @author zhongzheng
+ */
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils
+{
+    public static String YYYY = "yyyy";
+
+    public static String YYYY_MM = "yyyy-MM";
+
+    public static String YYYY_MM_DD = "yyyy-MM-dd";
+
+    public static String YYYYMMDD = "yyyyMMdd";
+
+    public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+    public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+    private static String[] parsePatterns = {
+            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+    static final class DateFormatHolder {
+        private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>();
+
+        DateFormatHolder() {
+        }
+
+        public static SimpleDateFormat formatFor(String pattern) {
+            SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
+            Map<String, SimpleDateFormat> formats = ref == null ? null : ref.get();
+            if (formats == null) {
+                formats = new HashMap<String, SimpleDateFormat>();
+                THREADLOCAL_FORMATS.set(new SoftReference(formats));
+            }
+
+            SimpleDateFormat format = formats.get(pattern);
+
+            if (format == null) {
+                format = new SimpleDateFormat(pattern);
+                format.setTimeZone(TimeZone.getTimeZone("GMT+8"));
+                ((Map) formats).put(pattern, format);
+            }
+
+            return format;
+        }
+
+        public static void clearThreadLocal() {
+            THREADLOCAL_FORMATS.remove();
+        }
+    }
+
+    public static String formatDateV3(Date date, String pattern) {
+        Args.notNull(date, "Date");
+        Args.notNull(pattern, "Pattern");
+        SimpleDateFormat formatFor = DateFormatHolder.formatFor(pattern);
+        return formatFor.format(date);
+    }
+
+    /**
+     * 获取当前Date型日期
+     *
+     * @return Date() 当前日期
+     */
+    public static Date getNowDate()
+    {
+        return new Date();
+    }
+
+    /**
+     * 获取当前日期, 默认格式为yyyy-MM-dd
+     *
+     * @return String
+     */
+    public static String getDate()
+    {
+        return dateTimeNow(YYYY_MM_DD);
+    }
+
+    public static final String getTime()
+    {
+        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+    }
+
+    public static final String dateTimeNow()
+    {
+        return dateTimeNow(YYYYMMDDHHMMSS);
+    }
+
+    public static final String dateTimeNow(final String format)
+    {
+        return parseDateToStr(format, new Date());
+    }
+
+    public static final String dateTime(final Date date)
+    {
+        return parseDateToStr(YYYY_MM_DD, date);
+    }
+
+    public static final String parseDateToStr(final String format, final Date date)
+    {
+        return new SimpleDateFormat(format).format(date);
+    }
+
+    public static final Date dateTimeThrow(final String format, final String ts) throws ParseException {
+        return new SimpleDateFormat(format).parse(ts);
+    }
+
+    public static final Date dateTime(final String format, final String ts)
+    {
+        try
+        {
+            return new SimpleDateFormat(format).parse(ts);
+        }
+        catch (ParseException e)
+        {
+
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static final Long dateTimeSec(final String format, final String ts)
+    {
+        try
+        {
+            return (new SimpleDateFormat(format).parse(ts)).getTime()/1000;
+        }
+        catch (ParseException e)
+        {
+
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static String timestampToDateFormat(Long times){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return DateFormatUtils.format(date, "yyyy/MM/dd");
+    }
+
+    public static Date timeToDate(Long times){
+        long t = times.longValue();
+        t = t * 1000;
+        return new Date(t);
+    }
+
+    public static String timestampToDateFormatMonth(Long times){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return DateFormatUtils.format(date, "MM月dd号");
+    }
+
+    public static String timestampToDateFormatMonthTwo(Long times){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return DateFormatUtils.format(date, "MM.dd");
+    }
+
+    public static String timestampToDateFormat(Long times,String patternStr){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return DateFormatUtils.format(date, patternStr);
+    }
+
+
+    /**
+     * 将秒转为时分秒格式【01:01:01】
+     * @param second 需要转化的秒数
+     * @return
+     */
+    public static String secondConvertHourMinSecond(Long second) {
+        String str = "";
+        if (second == null || second < 0) {
+            return str;
+        }
+
+        // 得到小时
+        long h = second / 3600;
+        str = h > 0 ? ((h < 10 ? ("0" + h) : h) + "时") : "";
+
+        // 得到分钟
+        long m = (second % 3600) / 60;
+        str += m > 0? (m < 10 ? ("0" + m) : m) + "分":"";
+
+        //得到剩余秒
+        long s = second % 60;
+        str += s > 0?(s < 10 ? ("0" + s) : s)+"秒":"";
+        return str;
+    }
+
+    public static String timestampToDate(Long times){
+        if(Validator.isEmpty(times)){
+            return "";
+        }
+        long t = times.longValue();
+        t = t * 1000;
+        Date date = new Date(t);
+        return dateTime(date);
+    }
+
+    /**
+     * 日期路径 即年/月/日 如2018/08/08
+     */
+    public static final String datePath()
+    {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyy/MM/dd");
+    }
+
+    /**
+     * 日期路径 即年/月/日 如20180808
+     */
+    public static final String dateTime()
+    {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyyMMdd");
+    }
+
+    /**
+     * 日期型字符串转化为日期 格式
+     */
+    public static Date parseDate(Object str)
+    {
+        if (str == null)
+        {
+            return null;
+        }
+        try
+        {
+            return parseDate(str.toString(), parsePatterns);
+        }
+        catch (ParseException e)
+        {
+            return null;
+        }
+    }
+
+    /**
+     * 获取服务器启动时间
+     */
+    public static Date getServerStartDate()
+    {
+        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+        return new Date(time);
+    }
+
+    /**
+     * 计算两个时间差
+     */
+    public static String getDatePoor(Date endDate, Date nowDate)
+    {
+        long nd = 1000 * 24 * 60 * 60;
+        long nh = 1000 * 60 * 60;
+        long nm = 1000 * 60;
+        // long ns = 1000;
+        // 获得两个时间的毫秒时间差异
+        long diff = endDate.getTime() - nowDate.getTime();
+        // 计算差多少天
+        long day = diff / nd;
+        // 计算差多少小时
+        long hour = diff % nd / nh;
+        // 计算差多少分钟
+        long min = diff % nd % nh / nm;
+        // 计算差多少秒//输出结果
+        // long sec = diff % nd % nh % nm / ns;
+        return day + "天" + hour + "小时" + min + "分钟";
+    }
+
+    public static Long getNowTime()
+    {
+        return System.currentTimeMillis()/1000;
+    }
+
+    /**
+     * 获取今天凌晨时间戳
+     */
+    public static Long getTodayZeroTime()
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
+        return cal.getTimeInMillis() / 1000;  //今天凌晨
+    }
+
+    /**
+     * 获取指定时间的凌晨时间戳
+     */
+    public static Long getScheduleTimeStrZeroTime(String timeStr,String patternStr)
+    {
+        Long time = dateTimeSec(patternStr,timeStr);
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(timeToDate(time));
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
+        return cal.getTimeInMillis() / 1000;  //今天凌晨
+    }
+
+    /**
+     * 获取指定时间的凌晨时间戳
+     */
+    public static Long getScheduleTimeZeroTime(Long scheduleTime)
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(timeToDate(scheduleTime));
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
+        return cal.getTimeInMillis() / 1000;  //今天凌晨
+    }
+
+    public static String formatDate(Date time,String str)
+    {
+       SimpleDateFormat sdf = new SimpleDateFormat(str);
+        return sdf.format(time);
+    }
+
+    /**
+     * 获取明天凌晨时间戳
+     */
+    public static Long getTomorrowZeroTime()
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
+        Calendar cal1 = Calendar.getInstance();
+        cal1.setTime(cal.getTime());
+        cal1.add(Calendar.DAY_OF_MONTH , +1);
+        return cal1.getTimeInMillis() / 1000; //明天凌晨
+    }
+
+    /**
+     * 获取当月第一天的凌晨时间戳
+     */
+    public static Long getToMonthZeroTime()
+    {
+        Calendar cal = Calendar.getInstance();
+        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 1, 0, 0, 0);
+        return cal.getTime().getTime()/1000;
+    }
+
+    /**
+     * 获取本年第一天的凌晨时间戳
+     */
+    public static Long getToYearZeroTime()
+    {
+        Calendar calendar = Calendar.getInstance();
+        // 设置当前时间为年初的第一天
+        calendar.set(Calendar.DAY_OF_YEAR, 1);
+        Date startOfCurrentYear = calendar.getTime();
+        return startOfCurrentYear.getTime()/1000;
+    }
+
+    /**
+     * 根据当前日期获得所在周的日期区间(周一和周日日期)
+     */
+    public static Map<String, Long> getTimeInterval(Date date){
+        Map<String, Long> map = new HashMap<>();
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        // 判断要计算的日期是否是周日,如果是则减一天计算周六的,否则会出问题,计算到下一周去了
+        int dayWeek = cal.get(Calendar.DAY_OF_WEEK);// 获得当前日期是一个星期的第几天
+        if(1 == dayWeek){
+            cal.add(Calendar.DAY_OF_MONTH,-1);
+        }
+        // 设置一个星期的第一天,按中国的习惯一个星期的第一天是星期一
+        cal.setFirstDayOfWeek(Calendar.MONDAY);
+        // 获得当前日期是一个星期的第几天
+        int day = cal.get(Calendar.DAY_OF_WEEK);
+        // 根据日历的规则,给当前日期减去星期几与一个星期第一天的差值
+        cal.add(Calendar.DATE, cal.getFirstDayOfWeek() - day);
+        Long imptimeBegin = cal.getTime().getTime();
+        cal.add(Calendar.DATE,6);
+        Long imptimeEnd = cal.getTime().getTime();
+        map.put("start", imptimeBegin/1000);
+        map.put("end", imptimeEnd/1000);
+        return map;
+    }
+
+
+    /**
+     * 根据当前日期获得上周的日期区间(上周周一和周日日期)
+     */
+    public static Map<String, Long> getLastTimeInterval(Date date){
+        Map<String, Long> map = new HashMap<>();
+        Calendar calendar1 = Calendar.getInstance();
+        Calendar calendar2 = Calendar.getInstance();
+        calendar1.setTime(date);
+        calendar2.setTime(date);
+        int dayOfWeek = calendar1.get(Calendar.DAY_OF_WEEK) - 1;
+        if(dayOfWeek <= 0){
+            dayOfWeek = 7;
+        }
+        int offset1 = 1 - dayOfWeek;
+        int offset2 = 7 - dayOfWeek;
+        calendar1.add(Calendar.DATE, offset1 - 7);
+        calendar2.add(Calendar.DATE, offset2 - 7);
+        // last Monday
+        Long lastBeginDate = calendar1.getTime().getTime();
+        // last Sunday
+        Long lastEndDate = calendar2.getTime().getTime();
+        map.put("laststart", lastBeginDate/1000);
+        map.put("lastend", lastEndDate/1000);
+        return map;
+    }
+
+    /**
+     * 获取日期格式订单号
+     * @return
+     */
+    public static String getDateOrderSn()
+    {
+        DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+        String localDate = (LocalDateTime.now().format(ofPattern)).substring(2);
+        //随机数
+        String randomNumeric = RandomStringUtils.randomNumeric(8);
+        return localDate+randomNumeric;
+    }
+
+    public static String getDateInputOrderSn()
+    {
+        return "LD"+getDateOrderSn();
+    }
+
+
+    public static String getPayOrderSn()
+    {
+        return "P"+getDateOrderSn();
+    }
+
+    public static String getInvoiceOrderSn()
+    {
+        DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+        String localDate = (LocalDateTime.now().format(ofPattern)).substring(2);
+        //随机数
+        String randomNumeric = RandomStringUtils.randomNumeric(4);
+        return "IN"+localDate+randomNumeric;
+
+    }
+
+    public static String getTagOrderSn(String tag)
+    {
+        return tag+getDateOrderSn();
+    }
+
+    public static String secToTime(int time) {
+        String timeStr = null;
+        int hour = 0;
+        int minute = 0;
+        int second = 0;
+        if (time <= 0)
+            return "00:00";
+        else {
+            minute = time / 60;
+            if (minute < 60) {
+                second = time % 60;
+                timeStr = "00:"+unitFormat(minute) + ":" + unitFormat(second);
+            } else {
+                hour = minute / 60;
+                if (hour > 99){
+                    return "99:59:59";
+                }
+                minute = minute % 60;
+                second = time - hour * 3600 - minute * 60;
+                timeStr = unitFormat(hour) + ":" + unitFormat(minute) + ":" + unitFormat(second);
+            }
+        }
+        return timeStr;
+    }
+
+    public static String unitFormat(int i) {
+        String retStr = null;
+        if (i >= 0 && i < 10)
+            retStr = "0" + Integer.toString(i);
+        else
+            retStr = "" + i;
+        return retStr;
+    }
+
+    public static Integer durationFormat(String duration) {
+        int index1=duration.indexOf(":");
+        int index2=duration.indexOf(":",index1+1);
+        int hh=Integer.parseInt(duration.substring(0,index1));
+        int mi=Integer.parseInt(duration.substring(index1+1,index2));
+        int ss=Integer.parseInt(duration.substring(index2+1));
+        return hh*60*60+mi*60+ss;
+    }
+
+    public static Integer dayBetween(Long s1,Long s2) {
+        String date1str = timestampToDateFormat(s1);
+        String date2str = timestampToDateFormat(s2);
+        int count = 0;
+        if("".equals(date1str) || date1str == null || "".equals(date2str) || date2str == null) {
+            return count;
+        }
+        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
+       try {
+           Date date1 = format.parse(date1str);
+           Date date2 = format.parse(date2str);
+           count = ((int) ((date1.getTime() - date2.getTime()) / (1000*3600*24)));
+           return count;
+        }catch (Exception e){
+           return null;
+       }
+    }
+
+    public static  Integer getTodayWeek(){
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new Date());
+        int weekIdx = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+        switch (weekIdx) {
+            case 1:
+                return 1;
+            case 2:
+                return 2;
+            case 3:
+                return 3;
+            case 4:
+                return 4;
+            case 5:
+                return 5;
+            case 6:
+                return 6;
+            default:
+                return 7;
+        }
+    }
+
+
+    public static LocalDate[] getDateArray() {
+        // 创建一个长度为30的数组
+        LocalDate[] dates = new LocalDate[30];
+        // 获取今天的日期
+        LocalDate today = LocalDate.now();
+        // 用循环给数组赋值
+        for (int i = 0; i < dates.length; i++) {
+            // 用today.plusDays(i)得到第i天的日期
+            dates[i] = today.plusDays(i);
+        }
+        // 返回数组
+        return dates;
+    }
+
+    static List<String> holiday =new ArrayList<>();
+    static List<String> extraWorkDay =new ArrayList<>();
+    public static Boolean isWorkingDay(long time) {
+        LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.of("+8"));
+        String formatTime = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        initHoliday();
+        initExtraWorkDay();
+        //是否加班日
+        if(extraWorkDay.contains(formatTime)){
+            return true;
+        }
+        //是否节假日
+        if(holiday.contains(formatTime)){
+            return false;
+        }
+        //如果是1-5表示周一到周五  是工作日
+        DayOfWeek week = dateTime.getDayOfWeek();
+        if(week==DayOfWeek.SATURDAY||week==DayOfWeek.SUNDAY){
+            return false;
+        }
+        return true;
+
+    }
+
+    /**
+     *  初始化节假日
+     */
+    public static void initHoliday(){
+        holiday.add("2023-06-22");
+        holiday.add("2023-06-23");
+        holiday.add("2023-09-29");
+        holiday.add("2023-09-30");
+        holiday.add("2023-10-01");
+        holiday.add("2023-10-02");
+        holiday.add("2023-10-03");
+        holiday.add("2023-10-04");
+        holiday.add("2023-10-05");
+        holiday.add("2023-10-06");
+    }
+    /**
+     *  初始化额外加班日
+     */
+    public static void initExtraWorkDay(){
+        extraWorkDay.add("2023-06-25");
+        extraWorkDay.add("2023-10-07");
+        extraWorkDay.add("2023-10-08");
+    }
+
+
+    public static Long getAppointTime(Long millisecond, Integer day) {
+        for (Integer i = 0; i < day; i++) {
+            Long dayAfter = getDayBefore(millisecond, 1);
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(timeToDate(dayAfter));
+            int index = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+            String[] weeks = new String[]{"星期天", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
+
+            if (weeks[index].equals("星期六") || weeks[index].equals("星期天")) {
+                day += 1;
+
+            }
+            //判断当前是否为工作日
+            if (!isWorkingDay(dayAfter)) {
+                day += 1;
+            }
+            millisecond = dayAfter;
+        }
+
+        return millisecond;
+    }
+
+    /**
+     * 指定时间往前或往后推n天
+     *
+     * @param dateTime 指定时间
+     * @param x 指定天数
+     * @return
+     */
+    public static Long getDayBefore(Long dateTime, int x) {
+        Calendar c = Calendar.getInstance();
+        Date date = new Date(dateTime*1000);
+        c.setTime(date);
+        int day = c.get(Calendar.DATE);
+        c.set(Calendar.DATE, day - x);    //往前推几天
+        //c.set(Calendar.DATE, day + x);  往后推几天
+        return c.getTime().getTime()/1000;
+    }
+
+    /**
+     * 指定时间往前或往后推n天
+     *
+     * @param dateTime 指定时间
+     * @param x 指定天数
+     * @return
+     */
+    public static Long getDayAfter(Long dateTime, int x) {
+        Calendar c = Calendar.getInstance();
+        Date date = new Date(dateTime*1000);
+        c.setTime(date);
+        int day = c.get(Calendar.DATE);
+//        c.set(Calendar.DATE, day - x);    //往前推几天
+        c.set(Calendar.DATE, day + x);  //往后推几天
+        return c.getTime().getTime()/1000;
+    }
+
+
+    public static List<Long> getWeekData(Long dataTime){
+        List<Long> week = new ArrayList();
+        Calendar calendar = Calendar.getInstance();
+        Long zeroTime = DateUtils.getScheduleTimeZeroTime(dataTime);
+        calendar.setTime(timeToDate(zeroTime));
+        // 如果是周日
+        if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
+            calendar.add(Calendar.DAY_OF_YEAR,-1);
+        }
+        // 获取当前日期是当周的第i天
+        int i = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+         // 获取当前日期所在周的第一天
+        calendar.add(Calendar.DATE , -i);
+
+        for (int j = 0; j < 7; j++) {
+            if(j >0){
+                calendar.add(Calendar.DATE , 1);
+            }
+            Long time = calendar.getTime().getTime()/1000;
+            if (j == 6){
+                time = time + 86400L;
+            }
+            week.add(time);
+        }
+        return week;
+    }
+
+
+    public static Map<Long,Long> getWeekTime(Long startTime,Long endTime){
+        Map<Long,Long> mapList = new LinkedHashMap<>();
+        Long zeroTime = DateUtils.getScheduleTimeZeroTime(startTime);
+        //一天的时间戳
+        Long time = 86400L;
+        for (Long i = zeroTime; i <= endTime; i = i+time) {
+            List<Long> weekData = DateUtils.getWeekData(i);
+            mapList.put(weekData.get(0),weekData.get(weekData.size()-1));
+        }
+        return mapList;
+    }
+
+    public static Map<Long,Long> getMonthTime(Long startTime,Long endTime){
+        //一天的时间戳
+        Long time = 86400L;
+        Map<Long,Long> map = new  LinkedHashMap<>();
+        try{
+            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM");
+            Date d1 = DateUtils.timeToDate(startTime);
+            Date d2 = DateUtils.timeToDate(endTime);
+            Calendar dd = Calendar.getInstance();//定义日期实例
+            dd.setTime(d1);//设置日期起始时间
+            while (dd.getTime().before(d2)) {//判断是否到结束日期
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
+                String str = sdf.format(dd.getTime());
+                Calendar c = Calendar.getInstance();
+                c.setTime(format.parse(str));
+                c.add(Calendar.MONTH, 0);
+                c.set(Calendar.DAY_OF_MONTH,1);//设置为1号,当前日期既为本月第一天
+                Long start = c.getTime().getTime()/1000;
+                dd.setTime(DateUtils.timeToDate(start));
+                //获取当前月最后一天
+                Calendar ca = Calendar.getInstance();
+                ca.setTime(format.parse(str));
+                ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
+                Long end = ca.getTime().getTime()/1000 + time;
+                map.put(start,end);
+                dd.add(Calendar.MONTH, 1);//进行当前日期月份加1
+            }
+        }catch (Exception e){
+            System.out.println("异常"+e.getMessage());
+        }
+        return map;
+    }
+
+}

+ 217 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/DictUtils.java

@@ -0,0 +1,217 @@
+package org.zhongzheng.common.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.constant.Constants;
+import com.zhongzheng.common.core.domain.entity.SysDictData;
+import com.zhongzheng.common.core.redis.RedisCache;
+import com.zhongzheng.common.utils.spring.SpringUtils;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 字典工具类
+ * 
+ * @author zhongzheng
+ */
+public class DictUtils
+{
+    /**
+     * 分隔符
+     */
+    public static final String SEPARATOR = ",";
+
+    /**
+     * 设置字典缓存
+     * 
+     * @param key 参数键
+     * @param dictDatas 字典数据列表
+     */
+    public static void setDictCache(String key, List<SysDictData> dictDatas)
+    {
+        SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas);
+    }
+
+    /**
+     * 设置字典缓存
+     *
+     * @param key 参数键
+     * @param dictDatas 字典数据列表
+     */
+    public static void setTopDictCache(String key, List<SysDictData> dictDatas)
+    {
+        SpringUtils.getBean(RedisCache.class).setCacheObject(getTopCacheKey(key), dictDatas);
+    }
+
+    /**
+     * 获取字典缓存
+     * 
+     * @param key 参数键
+     * @return dictDatas 字典数据列表
+     */
+    public static List<SysDictData> getDictCache(String key)
+    {
+        Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
+        if (Validator.isNotNull(cacheObj))
+        {
+            List<SysDictData> dictDatas = (List<SysDictData>)cacheObj;
+            return dictDatas;
+        }
+        return null;
+    }
+
+    /**
+     * 获取字典缓存
+     *
+     * @param key 参数键
+     * @return dictDatas 字典数据列表
+     */
+    public static List<SysDictData> getTopDictCache(String key)
+    {
+        Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getTopCacheKey(key));
+        if (Validator.isNotNull(cacheObj))
+        {
+            List<SysDictData> dictDatas = (List<SysDictData>)cacheObj;
+            return dictDatas;
+        }
+        return null;
+    }
+
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     * 
+     * @param dictType 字典类型
+     * @param dictValue 字典值
+     * @return 字典标签
+     */
+    public static String getDictLabel(String dictType, String dictValue)
+    {
+        return getDictLabel(dictType, dictValue, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     * 
+     * @param dictType 字典类型
+     * @param dictLabel 字典标签
+     * @return 字典值
+     */
+    public static String getDictValue(String dictType, String dictLabel)
+    {
+        return getDictValue(dictType, dictLabel, SEPARATOR);
+    }
+
+    /**
+     * 根据字典类型和字典值获取字典标签
+     * 
+     * @param dictType 字典类型
+     * @param dictValue 字典值
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    public static String getDictLabel(String dictType, String dictValue, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+
+        if (StrUtil.containsAny(separator, dictValue) && CollUtil.isNotEmpty(datas))
+        {
+            for (SysDictData dict : datas)
+            {
+                for (String value : dictValue.split(separator))
+                {
+                    if (value.equals(dict.getDictValue()))
+                    {
+                        propertyString.append(dict.getDictLabel() + separator);
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            for (SysDictData dict : datas)
+            {
+                if (dictValue.equals(dict.getDictValue()))
+                {
+                    return dict.getDictLabel();
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null, separator);
+    }
+
+    /**
+     * 根据字典类型和字典标签获取字典值
+     * 
+     * @param dictType 字典类型
+     * @param dictLabel 字典标签
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    public static String getDictValue(String dictType, String dictLabel, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        List<SysDictData> datas = getDictCache(dictType);
+
+        if (StrUtil.containsAny(separator, dictLabel) && CollUtil.isNotEmpty(datas))
+        {
+            for (SysDictData dict : datas)
+            {
+                for (String label : dictLabel.split(separator))
+                {
+                    if (label.equals(dict.getDictLabel()))
+                    {
+                        propertyString.append(dict.getDictValue() + separator);
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            for (SysDictData dict : datas)
+            {
+                if (dictLabel.equals(dict.getDictLabel()))
+                {
+                    return dict.getDictValue();
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null, separator);
+    }
+
+    /**
+     * 清空字典缓存
+     */
+    public static void clearDictCache()
+    {
+        Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(Constants.SYS_DICT_KEY + "*");
+        SpringUtils.getBean(RedisCache.class).deleteObject(keys);
+    }
+
+    /**
+     * 设置cache key
+     * 
+     * @param configKey 参数键
+     * @return 缓存键key
+     */
+    public static String getCacheKey(String configKey)
+    {
+        return Constants.SYS_DICT_KEY + configKey;
+    }
+
+    /**
+     * 设置cache key
+     *
+     * @param configKey 参数键
+     * @return 缓存键key
+     */
+    public static String getTopCacheKey(String configKey)
+    {
+        return Constants.SYS_TOP_DICT_KEY + configKey;
+    }
+}

+ 195 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/DoubleUtils.java

@@ -0,0 +1,195 @@
+package org.zhongzheng.common.utils;
+
+import java.math.BigDecimal;
+
+public class DoubleUtils {
+    // 默认除法运算精度
+    private static final Integer DEF_DIV_SCALE = 2;
+
+    /**
+     * 提供精确的加法运算。
+     *
+     * @param value1
+     *            被加数
+     * @param value2
+     *            加数
+     * @return 两个参数的和
+     */
+    public static Double add(Number value1, Number value2) {
+        return add(value1, value2, DEF_DIV_SCALE);
+    }
+
+    /**
+     * 提供精确的加法运算。
+     *
+     * @param value1
+     *            被加数
+     * @param value2
+     *            加数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的和
+     */
+    public static Double add(Number value1, Number value2, Integer scale) {
+        BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue()));
+        BigDecimal add = b1.add(b2);
+        BigDecimal setScale = add.setScale(scale, BigDecimal.ROUND_HALF_UP);
+        return setScale.doubleValue();
+    }
+
+    /**
+     * 提供精确的减法运算。
+     *
+     * @param value1
+     *            被减数
+     * @param value2
+     *            减数
+     * @return
+     */
+    public static Double sub(Number value1, Number value2) {
+        return sub(value1, value2, DEF_DIV_SCALE);
+    }
+
+    /**
+     *
+     * @param value1
+     *            被减数
+     * @param value2
+     *            减数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的差
+     */
+    public static Double sub(Number value1, Number value2, Integer scale) {
+        BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue()));
+        BigDecimal subtract = b1.subtract(b2);
+        BigDecimal setScale = subtract.setScale(scale, BigDecimal.ROUND_HALF_UP);
+        return setScale.doubleValue();
+    }
+
+    /**
+     * 提供精确的乘法运算。
+     *
+     * @param value1
+     *            被乘数
+     * @param value2
+     *            乘数
+     * @return 两个参数的积
+     */
+    public static Double mul(Number value1, Number value2) {
+        return mul(value1, value2, DEF_DIV_SCALE);
+    }
+
+    /**
+     * 提供精确的乘法运算。
+     *
+     * @param value1
+     *            被乘数
+     * @param value2
+     *            乘数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的积
+     */
+    public static Double mul(Number value1, Number value2, Integer scale) {
+        BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue()));
+        BigDecimal multiply = b1.multiply(b2);
+        BigDecimal setScale = multiply.setScale(scale, BigDecimal.ROUND_HALF_UP);
+        return setScale.doubleValue();
+    }
+
+    /**
+     * 提供精确的乘法运算。
+     *
+     * @param value1
+     *            被乘数
+     * @param value2
+     *            乘数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     *
+     * @return 两个参数的积
+     */
+    public static Double mul(Number value1, Number value2, int scale, int roundingMode) {
+        BigDecimal b1 = new BigDecimal(Double.toString(value1.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(value2.doubleValue()));
+        BigDecimal multiply = b1.multiply(b2);
+        BigDecimal setScale = multiply.setScale(scale, roundingMode);
+        return setScale.doubleValue();
+    }
+
+    /**
+     * 提供(相对)精确的除法运算,当发生除不尽的情况时, 精确到小数点以后2位,以后的数字四舍五入。
+     *
+     * @param dividend
+     *            被除数
+     * @param divisor
+     *            除数
+     * @return 两个参数的商
+     */
+    public static Double div(Number dividend, Number divisor) {
+        return DoubleUtils.div(dividend, divisor, DEF_DIV_SCALE);
+    }
+
+    /**
+     * 提供(相对)精确的除法运算。 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
+     *
+     * @param dividend
+     *            被除数
+     * @param divisor
+     *            除数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的商
+     */
+    public static Double div(Number dividend, Number divisor, Integer scale) {
+        if (scale < 0) {
+            throw new IllegalArgumentException("The scale must be a positive integer or zero");
+        }
+        BigDecimal b1 = new BigDecimal(Double.toString(dividend.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(divisor.doubleValue()));
+        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+    /**
+     * 提供(相对)精确的除法运算。 当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
+     *
+     * @param dividend
+     *            被除数
+     * @param divisor
+     *            除数
+     * @param scale
+     *            表示需要精确到小数点以后几位。
+     * @return 两个参数的商
+     */
+    public static Double div(Number dividend, Number divisor, Integer scale, int roundingMode) {
+        if (scale < 0) {
+            throw new IllegalArgumentException("The scale must be a positive integer or zero");
+        }
+        BigDecimal b1 = new BigDecimal(Double.toString(dividend.doubleValue()));
+        BigDecimal b2 = new BigDecimal(Double.toString(divisor.doubleValue()));
+        return b1.divide(b2, scale, roundingMode).doubleValue();
+    }
+
+    /**
+     * 提供精确的小数位四舍五入处理。
+     *
+     * @param value
+     *            需要四舍五入的数字
+     * @param scale
+     *            小数点后保留几位
+     * @return 四舍五入后的结果
+     */
+    public static Double round(Double value, Integer scale) {
+        if (scale < 0) {
+            throw new IllegalArgumentException("The scale must be a positive integer or zero");
+        }
+        BigDecimal b = new BigDecimal(Double.toString(value));
+        BigDecimal one = new BigDecimal("1");
+        return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+}

+ 46 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/JavaMailUtils.java

@@ -0,0 +1,46 @@
+package org.zhongzheng.common.utils;
+
+
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import java.util.Properties;
+
+/**
+ * @author yangdamao
+ * @date 2023年06月19日 10:11
+ */
+public class JavaMailUtils {
+
+    public static Session createsession(String postAccount,String postPassword,String STMPserver,String post) {
+        // SMTP服务器地址
+        String smtp = STMPserver;
+
+        // 邮箱账号和密码(授权密码)
+        String userName = postAccount;
+        String password = postPassword;
+
+        // SMTP服务器的连接信息
+        Properties props = new Properties();
+        props.put("mail.smtp.host", smtp); // SMTP主机号
+        props.put("mail.smtp.port", post); // 主机端口号
+        props.put("mail.smtp.auth", "true"); // 是否需要认证
+        props.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密
+        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+
+        // 创建Session
+        // 参数1:SMTP服务器的连接信息
+        // 参数2:用户认证对象(Authenticator接口的匿名实现类)
+        Session session = Session.getInstance(props, new Authenticator() {
+            @Override
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(userName, password);
+            }
+        });
+
+        // 开启调试模式
+        session.setDebug(true);
+
+        return session;
+    }
+}

+ 26 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/MessageUtils.java

@@ -0,0 +1,26 @@
+package org.zhongzheng.common.utils;
+
+import com.zhongzheng.common.utils.spring.SpringUtils;
+import org.springframework.context.MessageSource;
+import org.springframework.context.i18n.LocaleContextHolder;
+
+/**
+ * 获取i18n资源文件
+ * 
+ * @author zhongzheng
+ */
+public class MessageUtils
+{
+    /**
+     * 根据消息键和参数 获取消息 委托给spring messageSource
+     *
+     * @param code 消息键
+     * @param args 参数
+     * @return 获取国际化翻译值
+     */
+    public static String message(String code, Object... args)
+    {
+        MessageSource messageSource = SpringUtils.getBean(MessageSource.class);
+        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
+    }
+}

+ 138 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/SecurityUtils.java

@@ -0,0 +1,138 @@
+package org.zhongzheng.common.utils;
+
+import cn.hutool.http.HttpStatus;
+import com.zhongzheng.common.core.domain.model.LoginUser;
+import com.zhongzheng.common.core.domain.model.TopLoginUser;
+import com.zhongzheng.common.exception.CustomException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * 安全服务工具类
+ *
+ * @author zhongzheng
+ */
+@Component
+public class SecurityUtils
+{
+
+   // @Value("${mybatis-plus.tenant.enabled-tenant:true}")
+    public static boolean EnabledTenant;
+
+    @Value("${mybatis-plus.tenant.enabled-tenant:true}")
+    private boolean enabledTenant;
+
+    @PostConstruct
+    public void getEnvironment(){
+        EnabledTenant = this.enabledTenant;
+    }
+    /**
+     * 获取用户账户
+     **/
+    public static String getUsername()
+    {
+        try
+        {
+            if(EnabledTenant){
+                return getLoginUser().getUsername();
+            }else{
+                return getTopLoginUser().getUsername();
+            }
+        }
+        catch (Exception e)
+        {
+            throw new CustomException("获取用户账户异常", HttpStatus.HTTP_UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取用户
+     **/
+    public static LoginUser getLoginUser()
+    {
+        try
+        {
+            return (LoginUser) getAuthentication().getPrincipal();
+        }
+        catch (Exception e)
+        {
+            throw new CustomException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED);
+        }
+    }
+
+    public static TopLoginUser getTopLoginUser()
+    {
+        try
+        {
+            return (TopLoginUser) getAuthentication().getPrincipal();
+        }
+        catch (Exception e)
+        {
+            throw new CustomException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取Authentication
+     */
+    public static Authentication getAuthentication()
+    {
+        return SecurityContextHolder.getContext().getAuthentication();
+    }
+
+    /**
+     * 生成BCryptPasswordEncoder密码
+     *
+     * @param password 密码
+     * @return 加密字符串
+     */
+    public static String encryptPassword(String password)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.encode(password);
+    }
+
+    /**
+     * 判断密码是否相同
+     *
+     * @param rawPassword 真实密码
+     * @param encodedPassword 加密后字符
+     * @return 结果
+     */
+    public static boolean matchesPassword(String rawPassword, String encodedPassword)
+    {
+        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+    /**
+     * 是否为管理员
+     *
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public static boolean isAdmin(Long userId)
+    {
+        LoginUser user= (LoginUser) getAuthentication().getPrincipal();
+        return user != null && "admin".equals(user.getUser().getUserName());
+     //   return userId != null && 1L == userId;
+    }
+
+    /**
+     * 是否为管理员
+     *
+     * @param userId 用户ID
+     * @return 结果
+     */
+    public static boolean isTopAdmin(Long userId)
+    {
+        TopLoginUser user= (TopLoginUser) getAuthentication().getPrincipal();
+        return user != null && "admin".equals(user.getUser().getUserName());
+        //   return userId != null && 1L == userId;
+    }
+}

+ 159 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/ServletUtils.java

@@ -0,0 +1,159 @@
+package org.zhongzheng.common.utils;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+
+/**
+ * 客户端工具类
+ *
+ * @author zhongzheng
+ */
+public class ServletUtils
+{
+    public static HttpServletRequest httpServletRequest;
+    /**
+     * 获取模块编码参数
+     */
+    public static String getEncoded(String tag)
+    {
+        String time = String.valueOf(System.currentTimeMillis());
+        return tag+Long.valueOf(time.substring(1));
+    }
+    //导入生成编号使用
+    public static String getImportEncoded(String tag)
+    {
+        String time = String.valueOf(System.currentTimeMillis()/1000);
+        return tag+Integer.valueOf(time.substring(2))+(int)((Math.random()*100));
+    }
+    /**
+     * 获取String参数
+     */
+    public static String getParameter(String name)
+    {
+        return getRequest().getParameter(name);
+    }
+
+    /**
+     * 获取String参数
+     */
+    public static String getParameter(String name, String defaultValue)
+    {
+        return Convert.toStr(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 获取Integer参数
+     */
+    public static Integer getParameterToInt(String name)
+    {
+        return Convert.toInt(getRequest().getParameter(name));
+    }
+
+    /**
+     * 获取Integer参数
+     */
+    public static Integer getParameterToInt(String name, Integer defaultValue)
+    {
+        return Convert.toInt(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 获取request
+     */
+    public static HttpServletRequest getRequest()
+    {
+        if(getRequestAttributes()!=null){
+            return getRequestAttributes().getRequest();
+        }else{
+            return httpServletRequest;
+        }
+
+    }
+
+
+    /**
+     * 获取response
+     */
+    public static HttpServletResponse getResponse()
+    {
+        return getRequestAttributes().getResponse();
+    }
+
+    /**
+     * 获取session
+     */
+    public static HttpSession getSession()
+    {
+        return getRequest().getSession();
+    }
+
+    public static ServletRequestAttributes getRequestAttributes()
+    {
+        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+        return (ServletRequestAttributes) attributes;
+    }
+
+    /**
+     * 将字符串渲染到客户端
+     *
+     * @param response 渲染对象
+     * @param string 待渲染的字符串
+     * @return null
+     */
+    public static String renderString(HttpServletResponse response, String string)
+    {
+        try
+        {
+            response.setStatus(200);
+            response.setContentType("application/json");
+            response.setCharacterEncoding("utf-8");
+            response.getWriter().print(string);
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 是否是Ajax异步请求
+     *
+     * @param request
+     */
+    public static boolean isAjaxRequest(HttpServletRequest request)
+    {
+        String accept = request.getHeader("accept");
+        if (accept != null && accept.indexOf("application/json") != -1)
+        {
+            return true;
+        }
+
+        String xRequestedWith = request.getHeader("X-Requested-With");
+        if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1)
+        {
+            return true;
+        }
+
+        String uri = request.getRequestURI();
+        if (StrUtil.equalsAnyIgnoreCase(uri, ".json", ".xml"))
+        {
+            return true;
+        }
+
+        String ajax = request.getParameter("__ajax");
+        if (StrUtil.equalsAnyIgnoreCase(ajax, "json", "xml"))
+        {
+            return true;
+        }
+        return false;
+    }
+}

+ 52 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/SmsUtils.java

@@ -0,0 +1,52 @@
+package org.zhongzheng.common.utils;
+
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.teaopenapi.models.Config;
+
+public class SmsUtils {
+    //产品名称:云通信短信API产品,开发者无需替换
+    static final String product = "Dysmsapi";
+    //产品域名,开发者无需替换
+    static final String domain = "dysmsapi.aliyuncs.com";
+
+    // TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
+    static final String accessKeyId = "yourAccessKeyId";
+    static final String accessKeySecret = "yourAccessKeySecret";
+
+    public static com.aliyun.dysmsapi20170525.Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
+        Config config = new Config()
+                // 您的AccessKey ID
+                .setAccessKeyId(accessKeyId)
+                // 您的AccessKey Secret
+                .setAccessKeySecret(accessKeySecret);
+        // 访问的域名
+        config.endpoint = "dysmsapi.aliyuncs.com";
+        return new com.aliyun.dysmsapi20170525.Client(config);
+    }
+
+    public static SendSmsResponse sendSms(String tel,String signName,String templateCode,String Param,String accessKeyId,String accessKeySecret) throws  Exception {
+        //可自助调整超时时间
+      /*  System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
+        System.setProperty("sun.net.client.defaultReadTimeout", "10000");*/
+
+        com.aliyun.dysmsapi20170525.Client client = SmsUtils.createClient(accessKeyId, accessKeySecret);
+        SendSmsRequest request = new SendSmsRequest();
+        //必填:待发送手机号
+        request.setPhoneNumbers(tel);
+        //必填:短信签名-可在短信控制台中找到
+        request.setSignName(signName);
+        //必填:短信模板-可在短信控制台中找到
+        request.setTemplateCode(templateCode);
+        //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
+        //"{\"name\":\"Tom\", \"code\":\"123\"}"
+        request.setTemplateParam(Param);
+        //选填-上行短信扩展码(无特殊需求用户请忽略此字段)
+        //request.setSmsUpExtendCode("90997");
+        //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
+    //    request.setOutId("yourOutId");
+        SendSmsResponse sendSmsResponse = client.sendSms(request);
+
+        return sendSmsResponse;
+    }
+}

+ 128 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/SnowflakeIdUtils.java

@@ -0,0 +1,128 @@
+package org.zhongzheng.common.utils;
+
+public class SnowflakeIdUtils {
+    // ==============================Fields===========================================
+    /** 开始时间截 (2015-01-01) */
+    private final long twepoch = 1420041600000L;
+
+    /** 机器id所占的位数 */
+    private final long workerIdBits = 5L;
+
+    /** 数据标识id所占的位数 */
+    private final long datacenterIdBits = 5L;
+
+    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
+    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
+
+    /** 支持的最大数据标识id,结果是31 */
+    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
+
+    /** 序列在id中占的位数 */
+    private final long sequenceBits = 12L;
+
+    /** 机器ID向左移12位 */
+    private final long workerIdShift = sequenceBits;
+
+    /** 数据标识id向左移17位(12+5) */
+    private final long datacenterIdShift = sequenceBits + workerIdBits;
+
+    /** 时间截向左移22位(5+5+12) */
+    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+
+    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
+    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
+
+    /** 工作机器ID(0~31) */
+    private long workerId;
+
+    /** 数据中心ID(0~31) */
+    private long datacenterId;
+
+    /** 毫秒内序列(0~4095) */
+    private long sequence = 0L;
+
+    /** 上次生成ID的时间截 */
+    private long lastTimestamp = -1L;
+
+    //==============================Constructors=====================================
+    /**
+     * 构造函数
+     * @param workerId 工作ID (0~31)
+     * @param datacenterId 数据中心ID (0~31)
+     */
+    public SnowflakeIdUtils(long workerId, long datacenterId) {
+        if (workerId > maxWorkerId || workerId < 0) {
+            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
+        }
+        if (datacenterId > maxDatacenterId || datacenterId < 0) {
+            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
+        }
+        this.workerId = workerId;
+        this.datacenterId = datacenterId;
+    }
+
+    // ==============================Methods==========================================
+    /**
+     * 获得下一个ID (该方法是线程安全的)
+     * @return SnowflakeId
+     */
+    public synchronized long nextId() {
+        long timestamp = timeGen();
+
+        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
+        if (timestamp < lastTimestamp) {
+            throw new RuntimeException(
+                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
+        }
+
+        //如果是同一时间生成的,则进行毫秒内序列
+        if (lastTimestamp == timestamp) {
+            sequence = (sequence + 1) & sequenceMask;
+            //毫秒内序列溢出
+            if (sequence == 0) {
+                //阻塞到下一个毫秒,获得新的时间戳
+                timestamp = tilNextMillis(lastTimestamp);
+            }
+        }
+        //时间戳改变,毫秒内序列重置
+        else {
+            sequence = 0L;
+        }
+
+        //上次生成ID的时间截
+        lastTimestamp = timestamp;
+
+        //移位并通过或运算拼到一起组成64位的ID
+        return ((timestamp - twepoch) << timestampLeftShift) //
+                | (datacenterId << datacenterIdShift) //
+                | (workerId << workerIdShift) //
+                | sequence;
+    }
+
+    /**
+     * 阻塞到下一个毫秒,直到获得新的时间戳
+     * @param lastTimestamp 上次生成ID的时间截
+     * @return 当前时间戳
+     */
+    protected long tilNextMillis(long lastTimestamp) {
+        long timestamp = timeGen();
+        while (timestamp <= lastTimestamp) {
+            timestamp = timeGen();
+        }
+        return timestamp;
+    }
+
+    /**
+     * 返回以毫秒为单位的当前时间
+     * @return 当前时间(毫秒)
+     */
+    protected long timeGen() {
+        return System.currentTimeMillis();
+    }
+
+
+
+
+
+
+}

+ 73 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/TelPhoneUtils.java

@@ -0,0 +1,73 @@
+package org.zhongzheng.common.utils;
+
+import java.util.Random;
+
+/**
+ * 手机号码工具栏
+ *
+ * @author hjl
+ */
+public class TelPhoneUtils
+{
+    //中国移动
+    public static final String[] CHINA_MOBILE = {
+            "134", "135", "136", "137", "138", "139", "150", "151", "152", "157", "158", "159",
+            "182", "183", "184", "187", "188", "178", "147", "172", "198"
+    };
+    //中国联通
+    public static final String[] CHINA_UNICOM = {
+            "130", "131", "132", "145", "155", "156", "166", "171", "175", "176", "185", "186", "166"
+    };
+    //中国电信
+    public static final String[] CHINA_TELECOME = {
+            "133", "149", "153", "173", "177", "180", "181", "189", "199"
+    };
+
+    /**
+     * 生成手机号
+     */
+    public static String createMobile() {
+        StringBuilder sb = new StringBuilder();
+        Random random = new Random();
+        int op = random.nextInt(3);//随机运营商标志位
+        String mobileThree;//手机号前三位
+        int temp;
+        switch (op) {
+            case 0:
+                mobileThree = CHINA_MOBILE[random.nextInt(CHINA_MOBILE.length)];
+                break;
+            case 1:
+                mobileThree = CHINA_UNICOM[random.nextInt(CHINA_UNICOM.length)];
+                break;
+            case 2:
+                mobileThree = CHINA_TELECOME[random.nextInt(CHINA_TELECOME.length)];
+                break;
+            default:
+                mobileThree = "op标志位有误!";
+                break;
+        }
+        if (mobileThree.length() > 3) {
+            return mobileThree;
+        }
+        sb.append(mobileThree);
+        //生成手机号后8位
+        for (int i = 0; i < 8; i++) {
+            temp = random.nextInt(10);
+            sb.append(temp);
+        }
+        return sb.toString();
+    }
+
+
+
+    /**
+     * 隐藏手机号码
+     * @param tel
+     * @return
+     */
+    public static String hideTelPhone(String tel)
+    {
+        String phoneNumber = tel.replaceAll("(\\d{3})\\d{4}(\\d{4})","$1****$2");
+        return phoneNumber;
+    }
+}

+ 96 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/Threads.java

@@ -0,0 +1,96 @@
+package org.zhongzheng.common.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.*;
+
+/**
+ * 线程相关工具类.
+ * 
+ * @author zhongzheng
+ */
+public class Threads
+{
+    private static final Logger logger = LoggerFactory.getLogger(Threads.class);
+
+    /**
+     * sleep等待,单位为毫秒
+     */
+    public static void sleep(long milliseconds)
+    {
+        try
+        {
+            Thread.sleep(milliseconds);
+        }
+        catch (InterruptedException e)
+        {
+            return;
+        }
+    }
+
+    /**
+     * 停止线程池
+     * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
+     * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
+     * 如果仍人超時,則強制退出.
+     * 另对在shutdown时线程本身被调用中断做了处理.
+     */
+    public static void shutdownAndAwaitTermination(ExecutorService pool)
+    {
+        if (pool != null && !pool.isShutdown())
+        {
+            pool.shutdown();
+            try
+            {
+                if (!pool.awaitTermination(120, TimeUnit.SECONDS))
+                {
+                    pool.shutdownNow();
+                    if (!pool.awaitTermination(120, TimeUnit.SECONDS))
+                    {
+                        logger.info("Pool did not terminate");
+                    }
+                }
+            }
+            catch (InterruptedException ie)
+            {
+                pool.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * 打印线程异常信息
+     */
+    public static void printException(Runnable r, Throwable t)
+    {
+        if (t == null && r instanceof Future<?>)
+        {
+            try
+            {
+                Future<?> future = (Future<?>) r;
+                if (future.isDone())
+                {
+                    future.get();
+                }
+            }
+            catch (CancellationException ce)
+            {
+                t = ce;
+            }
+            catch (ExecutionException ee)
+            {
+                t = ee.getCause();
+            }
+            catch (InterruptedException ie)
+            {
+                Thread.currentThread().interrupt();
+            }
+        }
+        if (t != null)
+        {
+            logger.error(t.getMessage(), t);
+        }
+    }
+}

+ 641 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/ToolsUtils.java

@@ -0,0 +1,641 @@
+package org.zhongzheng.common.utils;
+
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import com.google.zxing.common.BitMatrix;
+import com.zhongzheng.common.exception.CustomException;
+import io.micrometer.core.lang.NonNull;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.apache.xmlbeans.impl.util.Base64.encode;
+
+
+public class ToolsUtils {
+
+    public static final int EMU_PER_PX = 9525;
+
+    /**
+     * 获取模块编码参数
+     */
+    public static String getSmsCode()
+    {
+        Random random = new Random();
+        String result="";
+        for (int i=0;i<6;i++)
+        {
+            result+=random.nextInt(10);
+        }
+       return result;
+    }
+
+    public static String join(@NonNull CharSequence delimiter, @NonNull Iterable tokens) {
+        final Iterator<?> it = tokens.iterator();
+        if (!it.hasNext()) {
+            return "";
+        }
+        final StringBuilder sb = new StringBuilder();
+        sb.append(it.next());
+        while (it.hasNext()) {
+            sb.append(delimiter);
+            sb.append(it.next());
+        }
+        return sb.toString();
+    }
+
+    public static final int emuToPx(double emu) {
+        return DoubleUtils.div(emu, EMU_PER_PX).intValue();
+    }
+
+    public static String getEncoding(String str) {
+        String encode = "GB2312";
+        try {
+            if (str.equals(new String(str.getBytes(encode), encode))) {
+                String s = encode;
+                return s;
+            }
+        } catch (Exception exception) {
+        }
+        encode = "ISO-8859-1";
+        try {
+            if (str.equals(new String(str.getBytes(encode), encode))) {
+                String s1 = encode;
+                return s1;
+            }
+        } catch (Exception exception1) {
+        }
+        encode = "UTF-8";
+        try {
+            if (str.equals(new String(str.getBytes(encode), encode))) {
+                String s2 = encode;
+                return s2;
+            }
+        } catch (Exception exception2) {
+        }
+        encode = "GBK";
+        try {
+            if (str.equals(new String(str.getBytes(encode), encode))) {
+                String s3 = encode;
+                return s3;
+            }
+        } catch (Exception exception3) {
+        }
+        return "";
+    }
+
+    /**
+     * 字符串转换UTF-8编码
+     *
+     * @param string 字符串
+     * @return java.lang.String
+     * @date 2022/4/14.
+     */
+    public static String toUtf8String(String string) {
+        StringBuilder stringBuffer = new StringBuilder();
+        for (int i = 0; i < string.length(); i++) {
+            char c = string.charAt(i);
+            if (c <= 255) {
+                stringBuffer.append(c);
+            } else {
+                byte[] b;
+                try {
+                    b = Character.toString(c).getBytes(StandardCharsets.UTF_8);
+                } catch (Exception ex) {
+                    b = new byte[0];
+                }
+                for (int value : b) {
+                    int k = value;
+                    if (k < 0) k += 256;
+                    stringBuffer.append("%").append(Integer.toHexString(k).toUpperCase());
+                }
+            }
+        }
+        return stringBuffer.toString();
+    }
+
+    public static String StringToMd5(String psw) {
+        {
+            try {
+                MessageDigest md5 = MessageDigest.getInstance("MD5");
+                md5.update(psw.getBytes("UTF-8"));
+                byte[] encryption = md5.digest();
+
+                StringBuffer strBuf = new StringBuffer();
+                for (int i = 0; i < encryption.length; i++) {
+                    if (Integer.toHexString(0xff & encryption[i]).length() == 1) {
+                        strBuf.append("0").append(Integer.toHexString(0xff & encryption[i]));
+                    } else {
+                        strBuf.append(Integer.toHexString(0xff & encryption[i]));
+                    }
+                }
+
+                return strBuf.toString();
+            } catch (NoSuchAlgorithmException e) {
+                return "";
+            } catch (UnsupportedEncodingException e) {
+                return "";
+            }
+        }
+    }
+
+    public static String getCharAndNumr(int length) {
+        Random random = new Random();
+        StringBuffer valSb = new StringBuffer();
+        String charStr = "0123456789abcdefghijklmnopqrstuvwxyz";
+        int charLength = charStr.length();
+        for (int i = 0; i < length; i++) {
+            int index = random.nextInt(charLength);
+            valSb.append(charStr.charAt(index));
+        }
+        return valSb.toString();
+    }
+
+    public static <T> List<List<T>> splitListBycapacity(List<T> source, int capacity){
+        List<List<T>> result=new ArrayList<List<T>>();
+        if (source != null){
+            int size = source.size();
+            if (size > 0 ){
+                for (int i = 0; i < size;) {
+                    List<T> value = null;
+                    int end = i+capacity;
+                    if (end > size){
+                        end = size;
+                    }
+                    value = source.subList(i,end);
+                    i = end;
+
+                    result.add(value);
+                }
+
+            }else {
+                result = null;
+            }
+        }else {
+            result = null;
+        }
+        return result;
+    }
+
+    /**
+     * 校验签名
+     *
+     * @param token token
+     * @param signature 签名
+     * @param timestamp 时间戳
+     * @param nonce     随机数
+     * @return 布尔值
+     */
+    public static boolean checkGzhServerSignature(String token,String signature, String timestamp, String nonce) {
+        String checktext = null;
+        if (null != signature) {
+            //对ToKen,timestamp,nonce 按字典排序
+            String[] paramArr = new String[]{token, timestamp, nonce};
+            Arrays.sort(paramArr);
+            //将排序后的结果拼成一个字符串
+            String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
+
+            try {
+                MessageDigest md = MessageDigest.getInstance("SHA-1");
+                //对接后的字符串进行sha1加密
+                byte[] digest = md.digest(content.toString().getBytes());
+                checktext = byteToStr(digest);
+            } catch (NoSuchAlgorithmException e) {
+                e.printStackTrace();
+            }
+        }
+        //将加密后的字符串与signature进行对比
+        return checktext != null ? checktext.equals(signature.toUpperCase()) : false;
+    }
+
+    /**
+     * 将字节数组转化我16进制字符串
+     *
+     * @param byteArrays 字符数组
+     * @return 字符串
+     */
+    private static String byteToStr(byte[] byteArrays) {
+        String str = "";
+        for (int i = 0; i < byteArrays.length; i++) {
+            str += byteToHexStr(byteArrays[i]);
+        }
+        return str;
+    }
+
+    /**
+     * 将字节转化为十六进制字符串
+     *
+     * @param myByte 字节
+     * @return 字符串
+     */
+    private static String byteToHexStr(byte myByte) {
+        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+        char[] tampArr = new char[2];
+        tampArr[0] = Digit[(myByte >>> 4) & 0X0F];
+        tampArr[1] = Digit[myByte & 0X0F];
+        String str = new String(tampArr);
+        return str;
+    }
+
+    /**
+     * 将文件转换成Byte数组
+     */
+    public static byte[] getBytesByFile(String pathStr) {
+        File file = new File(pathStr);
+        try {
+            FileInputStream fis = new FileInputStream(file);
+            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
+            byte[] b = new byte[1000];
+            int n;
+            while ((n = fis.read(b)) != -1) {
+                bos.write(b, 0, n);
+            }
+            fis.close();
+            byte[] data = bos.toByteArray();
+            bos.close();
+            return data;
+        } catch (IOException e) {
+        }
+        return null;
+    }
+
+    public static String encodetoStr(byte[] src) {
+        byte[] encoded = encode(src);
+        return new String(encoded,0,0,encoded.length);
+    }
+
+    /**
+     * 不够位数的在前面补0,保留num的长度位数字
+     * @param code
+     * @return
+     */
+    public static String autoGenericCode(String code, int num) {
+        String result = "";
+        // 保留num的位数
+        // 0 代表前面补充0
+        // num 代表长度为4
+        // d 代表参数为正数型
+        result = String.format("%0" + num + "d", Integer.parseInt(code));
+
+        return result;
+    }
+
+    public static Boolean checkSignFromOldSys(String stamp,String sign) {
+        String newSign = stamp+"pubilc2022";
+        if(!sign.equals(ToolsUtils.EncoderByMd5(newSign))){
+            return false;
+        }
+        if((Long.parseLong(stamp)+10L>(DateUtils.getNowTime().longValue()))&&(Long.parseLong(stamp)<(DateUtils.getNowTime().longValue()-10L))){
+            return false;
+        }
+        return true;
+    }
+
+    public static Boolean checkOrderSignFromOldSys(String orderSn,String stamp,String sign) {
+        String newSign = orderSn+stamp+"pubilc2022";
+        if(!sign.equals(ToolsUtils.EncoderByMd5(newSign))){
+            return false;
+        }
+        if((Long.parseLong(stamp)+10L>(DateUtils.getNowTime().longValue()))&&(Long.parseLong(stamp)<(DateUtils.getNowTime().longValue()-10L))){
+            return false;
+        }
+        return true;
+    }
+
+    public static Boolean checkSignCwSnFromOldSys(String cwSn,String stamp,String sign) {
+        String newSign = cwSn+stamp+"pubilc2022";
+        if(!sign.equals(ToolsUtils.EncoderByMd5(newSign))){
+            return false;
+        }
+        if((Long.parseLong(stamp)+10L>(DateUtils.getNowTime().longValue()))&&(Long.parseLong(stamp)<(DateUtils.getNowTime().longValue()-10L))){
+            return false;
+        }
+        return true;
+    }
+
+    public static String EncoderByMd5WithUtf(String str) {
+        String result = "";
+        MessageDigest md5 = null;
+        try {
+            md5 = MessageDigest.getInstance("MD5");
+            // 这句是关键
+            md5.update(str.getBytes("UTF-8"));
+        } catch (NoSuchAlgorithmException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (UnsupportedEncodingException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        byte b[] = md5.digest();
+        int i;
+        StringBuffer buf = new StringBuffer("");
+        for (int offset = 0; offset < b.length; offset++) {
+            i = b[offset];
+            if (i < 0)
+                i += 256;
+            if (i < 16)
+                buf.append("0");
+            buf.append(Integer.toHexString(i));
+        }
+        result = buf.toString();
+
+        return result;
+    }
+
+
+
+    public static String EncoderByMd5(String str) {
+        String result = "";
+        MessageDigest md5 = null;
+        try {
+            md5 = MessageDigest.getInstance("MD5");
+            // 这句是关键
+            md5.update(str.getBytes("gbk"));
+        } catch (NoSuchAlgorithmException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (UnsupportedEncodingException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        byte b[] = md5.digest();
+        int i;
+        StringBuffer buf = new StringBuffer("");
+        for (int offset = 0; offset < b.length; offset++) {
+            i = b[offset];
+            if (i < 0)
+                i += 256;
+            if (i < 16)
+                buf.append("0");
+            buf.append(Integer.toHexString(i));
+        }
+        result = buf.toString();
+
+        return result;
+    }
+
+    public static boolean verifEasyPwd(String passWord) {
+        if(Validator.isEmpty(passWord)){
+            return false;
+        }
+        if(passWord.length()<8||passWord.length()>16){
+            throw new CustomException("密码长度限制8到16位");
+        }
+        return true;
+    }
+
+    public static boolean verifPwd(String passWord) {
+        if(Validator.isEmpty(passWord)){
+            return false;
+        }
+        /*if(passWord.length()<8||passWord.length()>18){
+            return false;
+        }*/
+        String regExp = "^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\\d)(?=.*?[!#@*&.])[a-zA-Z\\d!#@*&.]{8,16}$";
+        Pattern p = Pattern.compile(regExp);
+        Matcher m = p.matcher(passWord);
+        if (m.matches()){
+            return true;
+        } else {
+            throw new CustomException("密码应由8-16位数字、大小写字母、符号组成");
+        }
+    }
+
+    public static String getTenantId() {
+        String TenantId =  ServletUtils.getResponse().getHeader("TenantId");
+        if(!StrUtil.isNotBlank(TenantId)||TenantId==null){
+            TenantId = ServletUtils.getRequest().getHeader("TenantId");
+        }
+        if(Validator.isNotEmpty(TenantId)){
+            if(TenantId.equals("undefined")){
+                throw new CustomException("企业ID错误");
+            }
+        }
+        return TenantId;
+    }
+
+    private static int getRandom(int count) {
+        return (int) Math.round(Math.random() * (count));
+    }
+
+    public static String getRandomString(int length){
+        String string = "abcdefghijklmnopqrstuvwxyz";
+        StringBuffer sb = new StringBuffer();
+        int len = string.length();
+        for (int i = 0; i < length; i++) {
+            sb.append(string.charAt(getRandom(len-1)));
+        }
+        return sb.toString();
+    }
+
+    public static BufferedImage toBufferedImage(BitMatrix matrix) {
+        int black = 0xFF000000;
+        int white  = 0x00FFFFFF;
+        int width = matrix.getWidth();
+        int height = matrix.getHeight();
+        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                image.setRGB(x, y, matrix.get(x, y) ? black : white);
+            }
+        }
+        int imgHeight = image.getHeight();//取得图片的长和宽
+        int imgWidth = image.getWidth();
+        int c = image.getRGB(3, 3);
+        int alpha = 10;
+        //防止越位
+        if (alpha < 0) {
+            alpha = 0;
+        } else if (alpha > 10) {
+            alpha = 10;
+        }
+        BufferedImage tmpImg = new BufferedImage(imgWidth, imgHeight,BufferedImage.TYPE_4BYTE_ABGR);//新建一个类型支持透明的BufferedImage
+        for(int i = 0; i < imgWidth; ++i)//把原图片的内容复制到新的图片,同时把背景设为透明
+        {
+            for(int j = 0; j < imgHeight; ++j){
+                //把背景设为透明
+                if(image.getRGB(i, j) == c){
+                    tmpImg .setRGB(i, j, c & 0x00ffffff);
+                }
+                //设置透明度
+                else{
+                    int rgb = tmpImg .getRGB(i, j);
+                    rgb = ((alpha * 255 / 10) << 24) | (rgb & 0x00ffffff);
+                    tmpImg .setRGB(i, j, rgb);
+                }
+            }
+        }
+        return tmpImg ;
+    }
+
+    public static String solve(String num) {
+        if (num == null) {
+            return null;
+        }
+        // 判断是否有小数
+        int index = num.indexOf(".");
+        if (index >= 0) {
+            String integer = num.substring(0, index);
+            String decimal = num.substring(index);
+            // 分隔后的整数+小数拼接起来
+            return addSeparator(integer) + decimal;
+        } else {
+            return addSeparator(num);
+        }
+    }
+
+    // 添加分隔符
+    public static String addSeparator(String num) {
+        int length = num.length();
+        ArrayList list = new ArrayList();
+        while (length > 3) {
+            list.add(num.substring(length - 3, length));
+            length = length - 3;
+        }
+        // 将前面小于三位的数字添加到ArrayList中
+        list.add(num.substring(0, length));
+        StringBuffer buffer = new StringBuffer();
+        // 倒序拼接
+        for (int i = list.size() - 1; i > 0; i--) {
+            buffer.append(list.get(i) + ",");
+        }
+        buffer.append(list.get(0));
+        return buffer.toString();
+    }
+
+    public static String dataSign(String sourceMsg, String pKey) throws NoSuchAlgorithmException {
+        String tempPKey = MD5PubHasher(pKey.getBytes(StandardCharsets.UTF_8));
+        tempPKey = sourceMsg + tempPKey;
+        tempPKey = MD5PubHasher(tempPKey.getBytes(StandardCharsets.UTF_8));
+        return tempPKey;
+    }
+
+    public static String MD5PubHasher(byte[] hashText)  {
+        try {
+            MessageDigest md5 = MessageDigest.getInstance("MD5");
+            byte[] b = md5.digest(hashText);
+            StringBuilder ret = new StringBuilder();
+            for (int i = 0; i < b.length; i++) {
+                ret.append(String.format("%02x", b[i]));
+            }
+            return ret.toString();
+        }catch (NoSuchAlgorithmException e){
+            return null;
+        }
+    }
+
+    public static String encryptDes(String source, byte[] key, byte[] iv) throws Exception {
+        SecretKey secretKey = new SecretKeySpec(key, "DES");
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] inputByteArray = source.getBytes(StandardCharsets.UTF_8);
+        byte[] encryptedByteArray = cipher.doFinal(inputByteArray);
+        return new String(Base64.getEncoder().encode(encryptedByteArray));
+    }
+
+    public static String decryptDes(String source, byte[] key, byte[] iv) throws Exception {
+        SecretKey secretKey = new SecretKeySpec(key, "DES");
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] inputByteArray = Base64.getDecoder().decode(source.getBytes(StandardCharsets.UTF_8));
+        byte[] decryptedByteArray = cipher.doFinal(inputByteArray);
+        return new String(decryptedByteArray, StandardCharsets.UTF_8);
+    }
+
+    /*public static String encryptDesNew(String source, String pass) throws Exception {
+        byte[] rgbKey = pass.getBytes("UTF-8");
+        byte[] rgbIV = pass.getBytes("UTF-8");
+                DESedeKeySpec desKeySpec = new DESedeKeySpec(rgbKey);
+             SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+             SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+             Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
+            byte[] inputByteArray = source.getBytes("UTF-8");
+            IvParameterSpec ivParameterSpec = new IvParameterSpec(rgbIV);
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
+            byte[] encrypted = cipher.doFinal(inputByteArray);
+            return Base64.getEncoder().encodeToString(encrypted);}
+    }*/
+
+    /**
+
+     Des解密
+     @param source 源字符串
+     @param pass 密钥,长度必须8位
+     @return 解密后的字符串 */
+    /*public static String decryptDesNew(String source, String pass) throws Exception {
+        byte[] rgbKey = pass.getBytes("UTF-8");
+        byte[] rgbIV = pass.getBytes("UTF-8");
+        DESedeKeySpec desKeySpec = new DESedeKeySpec(rgbKey);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding")) {
+        byte[] inputByteArray = Base64.getDecoder().decode(source);
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(rgbIV);
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] decrypted = cipher.doFinal(inputByteArray);
+        return new String(decrypted, "UTF-8");
+        }
+    }*/
+
+
+ public static String encryptDesNew(String source, String pass) throws Exception {
+        byte[] rgbKey = pass.getBytes(StandardCharsets.UTF_8);
+        byte[] rgbIV = pass.getBytes(StandardCharsets.UTF_8);
+        DESKeySpec desKeySpec = new DESKeySpec(rgbKey);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        byte[] inputByteArray = source.getBytes(StandardCharsets.UTF_8);
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(rgbIV);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] encryptedByteArray = cipher.doFinal(inputByteArray);
+        return Base64.getEncoder().encodeToString(encryptedByteArray);
+    }
+
+   public static String decryptDesNew(String source, String pass) throws Exception {
+        byte[] rgbKey = pass.getBytes(StandardCharsets.UTF_8);
+        byte[] rgbIV = pass.getBytes(StandardCharsets.UTF_8);
+        DESKeySpec desKeySpec = new DESKeySpec(rgbKey);
+        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
+        byte[] inputByteArray = Base64.getDecoder().decode(source);
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(rgbIV);
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
+        byte[] decryptedByteArray = cipher.doFinal(inputByteArray);
+        return new String(decryptedByteArray, StandardCharsets.UTF_8);
+    }
+
+    private static String base64Encode(byte[] bytes) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        Base64.getEncoder().wrap(output).write(bytes);
+        return output.toString(StandardCharsets.UTF_8.name());
+    }
+
+    /*private static byte[] base64Decode(String str) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        Base64.getDecoder().wrap(output).write(str.getBytes(StandardCharsets.UTF_8));
+        return output.toByteArray();
+    }*/
+    private static byte[] base64Decode(String str) throws IOException {
+        byte[] decodedString = Base64.getDecoder().decode(new String(str).getBytes("UTF-8"));
+        return decodedString;
+    }
+
+}

+ 138 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/bank/DCCryptor.java

@@ -0,0 +1,138 @@
+package org.zhongzheng.common.utils.bank;
+
+import org.bouncycastle.asn1.*;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithID;
+import org.bouncycastle.crypto.signers.SM2Signer;
+import org.bouncycastle.jce.ECNamedCurveTable;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.math.ec.ECPoint;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.Security;
+import java.util.Enumeration;
+
+/**
+ * 示例代码,仅供参考
+ */
+public class DCCryptor {
+
+    static {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    public static byte[] CMBSM4EncryptWithCBC(byte key[], byte iv[], byte input[]) throws Exception {
+        if (key == null || iv == null || input == null) {
+            throw new Exception("CMBSM4EncryptWithCBC 非法输入");
+        }
+        return CMBSM4Crypt(key, iv, input, 1);
+    }
+
+    public static byte[] CMBSM4DecryptWithCBC(byte key[], byte iv[], byte input[]) throws Exception {
+        if (key == null || iv == null || input == null) {
+            throw new Exception("CMBSM4DecryptWithCBC 非法输入");
+        }
+        return CMBSM4Crypt(key, iv, input, 2);
+    }
+
+    public static byte[] CMBSM2SignWithSM3(byte[] id, byte privkey[], byte msg[]) throws Exception {
+        if (privkey == null || msg == null) {
+            throw new Exception("CMBSM2SignWithSM3 input error");
+        }
+        ECPrivateKeyParameters privateKey = encodePrivateKey(privkey);
+        SM2Signer signer = new SM2Signer();
+        ParametersWithID parameters = new ParametersWithID(privateKey, id);
+        signer.init(true, parameters);
+        signer.update(msg, 0, msg.length);
+        return decodeDERSignature(signer.generateSignature());
+    }
+
+    public static boolean CMBSM2VerifyWithSM3(byte[] id, byte pubkey[], byte msg[], byte signature[]) throws Exception {
+
+        if (pubkey == null || msg == null || signature == null) {
+            throw new Exception("CMBSM2VerifyWithSM3 input error");
+        }
+        ECPublicKeyParameters publicKey = encodePublicKey(pubkey);
+        SM2Signer signer = new SM2Signer();
+        ParametersWithID parameters = new ParametersWithID(publicKey, id);
+        signer.init(false, parameters);
+        signer.update(msg, 0, msg.length);
+        return signer.verifySignature(encodeDERSignature(signature));
+    }
+
+    private static byte[] CMBSM4Crypt(byte key[], byte iv[], byte input[], int mode) throws Exception {
+        SecretKeySpec spec = new SecretKeySpec(key, "SM4");
+        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+        Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS7Padding", BouncyCastleProvider.PROVIDER_NAME);
+        cipher.init(mode, spec, ivParameterSpec);
+        return cipher.doFinal(input);
+    }
+
+    private static ECPrivateKeyParameters encodePrivateKey(byte[] value) {
+        BigInteger d = new BigInteger(1, value);
+        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
+        ECDomainParameters ecParameters = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
+        return new ECPrivateKeyParameters(d, ecParameters);
+    }
+
+    public static ECPublicKeyParameters encodePublicKey(byte[] value) {
+        byte[] x = new byte[32];
+        byte[] y = new byte[32];
+        System.arraycopy(value, 1, x, 0, 32);
+        System.arraycopy(value, 33, y, 0, 32);
+        BigInteger X = new BigInteger(1, x);
+        BigInteger Y = new BigInteger(1, y);
+        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
+        ECPoint Q = spec.getCurve().createPoint(X, Y);
+        ECDomainParameters ecParameters = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
+        return new ECPublicKeyParameters(Q, ecParameters);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static byte[] decodeDERSignature(byte[] signature) throws Exception {
+        ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(signature));
+        ASN1Sequence primitive = (ASN1Sequence) stream.readObject();
+        Enumeration<ASN1Integer> enumeration = primitive.getObjects();
+        BigInteger R = enumeration.nextElement().getValue();
+        BigInteger S = enumeration.nextElement().getValue();
+        byte[] bytes = new byte[64];
+        byte[] r = format(R.toByteArray());
+        byte[] s = format(S.toByteArray());
+        System.arraycopy(r, 0, bytes, 0, 32);
+        System.arraycopy(s, 0, bytes, 32, 32);
+        return bytes;
+    }
+
+    private static byte[] encodeDERSignature(byte[] signature) throws Exception {
+        byte[] r = new byte[32];
+        byte[] s = new byte[32];
+        System.arraycopy(signature, 0, r, 0, 32);
+        System.arraycopy(signature, 32, s, 0, 32);
+        ASN1EncodableVector vector = new ASN1EncodableVector();
+        vector.add(new ASN1Integer(new BigInteger(1, r)));
+        vector.add(new ASN1Integer(new BigInteger(1, s)));
+        return (new DERSequence(vector)).getEncoded();
+    }
+
+    private static byte[] format(byte[] value) {
+        if (value.length == 32) {
+            return value;
+        } else {
+            byte[] bytes = new byte[32];
+            if (value.length > 32) {
+                System.arraycopy(value, value.length - 32, bytes, 0, 32);
+            } else {
+                System.arraycopy(value, 0, bytes, 32 - value.length, value.length);
+            }
+            return bytes;
+        }
+    }
+
+}

+ 169 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/bank/DCHelper.java

@@ -0,0 +1,169 @@
+package org.zhongzheng.common.utils.bank;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+import javax.net.ssl.*;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.KeyStore;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * 示例代码,仅供参考
+ * 本示例中用到的(HttpUrlConnection)存在不遵循RFC协议的重试机制,请在启动参数中将sun.net.http.retryPost参数设置为false,禁用重发。
+ */
+public class DCHelper {
+
+    public static String serialJsonOrdered(JsonObject json) throws Exception {
+        StringBuilder appender = new StringBuilder();
+        appender.append("{");
+        Iterator<String> keys = new TreeSet<>(json.keySet()).iterator();
+        boolean isFirstEle = true;
+        while (keys.hasNext()) {
+            if (!isFirstEle) {
+                appender.append(",");
+            }
+            String key = keys.next();
+            Object val = json.get(key);
+            if (val instanceof JsonObject) {
+                appender.append("\"").append(key).append("\":");
+                appender.append(serialJsonOrdered((JsonObject) val));
+            } else if (val instanceof JsonArray) {
+                JsonArray jarray = (JsonArray) val;
+                appender.append("\"").append(key).append("\":[");
+                boolean isFirstArrEle = true;
+                for (int i = 0; i < jarray.size(); i++) {
+                    if (!isFirstArrEle) {
+                        appender.append(",");
+                    }
+                    Object obj = jarray.get(i);
+                    if (obj instanceof JsonObject) {
+                        appender.append(serialJsonOrdered((JsonObject) obj));
+                    } else {
+                        appender.append(obj.toString());
+                    }
+                    isFirstArrEle = false;
+                }
+                appender.append("]");
+            } else {
+                String value = val.toString();
+                appender.append("\"").append(key).append("\":").append(value);
+            }
+            isFirstEle = false;
+        }
+        appender.append("}");
+        return appender.toString();
+    }
+
+    public static String getTime() {
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+        return dateFormat.format(new Date());
+    }
+
+    public static String doPostForm(String httpUrl, Map<String, String> param) throws Exception {
+        HttpURLConnection connection = null;
+        InputStream is = null;
+        OutputStream os = null;
+        BufferedReader br = null;
+        String result = null;
+        try {
+            URL url = new URL(httpUrl);
+            SSLContext sslcontext;
+            sslcontext = SSLContext.getInstance("SSL");
+            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            tmf.init((KeyStore) null);
+            X509TrustManager defaultTm = null;
+            for (TrustManager tm : tmf.getTrustManagers()) {
+                if (tm instanceof X509TrustManager) {
+                    defaultTm = (X509TrustManager) tm;
+                    break;
+                }
+            }
+            sslcontext.init(null, new TrustManager[] { defaultTm }, new java.security.SecureRandom());
+            HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());
+
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.setConnectTimeout(15000);
+            connection.setReadTimeout(60000);
+            connection.setInstanceFollowRedirects(true);
+
+            connection.setDoOutput(true);
+            connection.setDoInput(true);
+
+            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+            os = connection.getOutputStream();
+            os.write(createLinkString(param).getBytes());
+            if (connection.getResponseCode() != 200) {
+                is = connection.getErrorStream();
+                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+                StringBuilder sbf = new StringBuilder();
+                String temp = null;
+                while ((temp = br.readLine()) != null) {
+                    sbf.append(temp);
+                    sbf.append("\r\n");
+                }
+                result = sbf.toString();
+            } else {
+                is = connection.getInputStream();
+                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+                StringBuilder sbf = new StringBuilder();
+                String temp = null;
+                boolean firstLine = true;
+                while ((temp = br.readLine()) != null) {
+                    if (!firstLine) {
+                        firstLine = false;
+                        sbf.append("\r\n");
+                    }
+                    sbf.append(temp);
+                }
+                result = sbf.toString();
+            }
+        } finally {
+            if (null != br) {
+                try {
+                    br.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (null != os) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (null != is) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            connection.disconnect();
+        }
+        return result;
+    }
+
+    private static String createLinkString(Map<String, String> params) throws Exception {
+        ArrayList<String> keys = new ArrayList<>(params.keySet());
+        Collections.sort(keys);
+
+        StringBuilder prestr = new StringBuilder();
+        for (int i = 0; i < keys.size(); i++) {
+            String key = keys.get(i);
+            String value = params.get(key);
+            if (i == keys.size() - 1) {
+                prestr.append(key).append("=").append(value);
+            } else {
+                prestr.append(key).append("=").append(value).append("&");
+            }
+        }
+        return prestr.toString();
+    }
+
+}

+ 59 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/bank/GENKey.java

@@ -0,0 +1,59 @@
+package org.zhongzheng.common.utils.bank;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.jce.ECNamedCurveTable;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+
+
+
+public class GENKey {
+
+    private static ECDomainParameters getECDomainParameters() {
+        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
+        return new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
+    }
+
+
+    /**
+     * 生成国密密钥对
+     *
+     * @return
+     */
+    public static Map<String, byte[]> CMBSM2KeyGen() {
+        ECDomainParameters domainParameters = getECDomainParameters();
+        ECKeyPairGenerator generator = new ECKeyPairGenerator();
+        ECKeyGenerationParameters parameters = new ECKeyGenerationParameters(domainParameters, new SecureRandom());
+        generator.init(parameters);
+        AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
+        ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters) keyPair.getPublic();
+        ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) keyPair.getPrivate();
+        Map<String, byte[]> map = new HashMap<>();
+        map.put("publickey", publicKeyParameters.getQ().getEncoded(false));
+        map.put("privatekey", format(privateKeyParameters.getD().toByteArray()));
+        return map;
+    }
+
+    private static byte[] format(byte[] value) {
+        if (value.length == 32) {
+            return value;
+        }
+        byte bytes[] = new byte[32];
+        if (value.length > 32) {
+            System.arraycopy(value, value.length - 32, bytes, 0, 32);
+        } else {
+            System.arraycopy(value, 0, bytes, 32 - value.length, value.length);
+        }
+        return bytes;
+    }
+
+}

+ 77 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/FileTypeUtils.java

@@ -0,0 +1,77 @@
+package org.zhongzheng.common.utils.file;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+
+/**
+ * 文件类型工具类
+ *
+ * @author zhongzheng
+ */
+public class FileTypeUtils
+{
+    /**
+     * 获取文件类型
+     * <p>
+     * 例如: ruoyi.txt, 返回: txt
+     * 
+     * @param file 文件名
+     * @return 后缀(不含".")
+     */
+    public static String getFileType(File file)
+    {
+        if (null == file)
+        {
+            return StringUtils.EMPTY;
+        }
+        return getFileType(file.getName());
+    }
+
+    /**
+     * 获取文件类型
+     * <p>
+     * 例如: ruoyi.txt, 返回: txt
+     *
+     * @param fileName 文件名
+     * @return 后缀(不含".")
+     */
+    public static String getFileType(String fileName)
+    {
+        int separatorIndex = fileName.lastIndexOf(".");
+        if (separatorIndex < 0)
+        {
+            return "";
+        }
+        return fileName.substring(separatorIndex + 1).toLowerCase();
+    }
+
+    /**
+     * 获取文件类型
+     * 
+     * @param photoByte 文件字节码
+     * @return 后缀(不含".")
+     */
+    public static String getFileExtendName(byte[] photoByte)
+    {
+        String strFileExtendName = "JPG";
+        if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
+                && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
+        {
+            strFileExtendName = "GIF";
+        }
+        else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
+        {
+            strFileExtendName = "JPG";
+        }
+        else if ((photoByte[0] == 66) && (photoByte[1] == 77))
+        {
+            strFileExtendName = "BMP";
+        }
+        else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
+        {
+            strFileExtendName = "PNG";
+        }
+        return strFileExtendName;
+    }
+}

+ 390 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/FileUploadUtils.java

@@ -0,0 +1,390 @@
+package org.zhongzheng.common.utils.file;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.config.RuoYiConfig;
+import com.zhongzheng.common.constant.Constants;
+import com.zhongzheng.common.exception.file.FileNameLengthLimitExceededException;
+import com.zhongzheng.common.exception.file.FileSizeLimitExceededException;
+import com.zhongzheng.common.exception.file.InvalidExtensionException;
+import com.zhongzheng.common.utils.DateUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 文件上传工具类
+ *
+ * @author zhongzheng
+ */
+public class FileUploadUtils
+{
+    /**
+     * 默认大小 50M
+     */
+    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
+
+    /**
+     * 默认的文件名最大长度 100
+     */
+    public static final int DEFAULT_FILE_NAME_LENGTH = 100;
+
+    /**
+     * 默认上传的地址
+     */
+    private static String defaultBaseDir = RuoYiConfig.getProfile();
+
+    public static void setDefaultBaseDir(String defaultBaseDir)
+    {
+        FileUploadUtils.defaultBaseDir = defaultBaseDir;
+    }
+
+    public static String getDefaultBaseDir()
+    {
+        return defaultBaseDir;
+    }
+
+    /**
+     * 以默认配置进行文件上传
+     *
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws Exception
+     */
+    public static final String upload(MultipartFile file) throws IOException
+    {
+        try
+        {
+            return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 根据文件路径上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws IOException
+     */
+    public static final String upload(String baseDir, MultipartFile file) throws IOException
+    {
+        try
+        {
+            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @param allowedExtension 上传文件类型
+     * @return 返回上传成功的文件名
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws FileNameLengthLimitExceededException 文件名太长
+     * @throws IOException 比如读写文件出错时
+     * @throws InvalidExtensionException 文件校验异常
+     */
+    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+            InvalidExtensionException
+    {
+        int fileNamelength = file.getOriginalFilename().length();
+        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
+        {
+            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+        }
+
+        assertAllowed(file, allowedExtension);
+
+        String fileName = extractFilename(file);
+
+        File desc = getAbsoluteFile(baseDir, fileName);
+        file.transferTo(desc);
+        String pathFileName = getPathFileName(baseDir, fileName);
+        return pathFileName;
+    }
+
+
+    /**
+     * 根据文件路径上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @return 文件名称
+     * @throws IOException
+     */
+    public static final String uploadFragment(String baseDir, MultipartFile file,String fileName) throws IOException
+    {
+        try
+        {
+            return uploadFragment(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION,fileName);
+        }
+        catch (Exception e)
+        {
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 文件上传
+     *
+     * @param baseDir 相对应用的基目录
+     * @param file 上传的文件
+     * @param allowedExtension 上传文件类型
+     * @return 返回上传成功的文件名
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws FileNameLengthLimitExceededException 文件名太长
+     * @throws IOException 比如读写文件出错时
+     * @throws InvalidExtensionException 文件校验异常
+     */
+    public static final String uploadFragment(String baseDir, MultipartFile file, String[] allowedExtension,String fileName)
+            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+            InvalidExtensionException
+    {
+        int fileNamelength = file.getOriginalFilename().length();
+        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
+        {
+            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+        }
+
+        assertAllowedFragment(file, allowedExtension,fileName);
+        File desc = getAbsoluteFile(baseDir, fileName);
+        file.transferTo(desc);
+        String pathFileName = getPathFileName(baseDir, fileName);
+        return pathFileName;
+    }
+
+    /**
+     * 编码文件名
+     */
+    public static final String extractFilename(MultipartFile file)
+    {
+        String fileName = file.getOriginalFilename();
+        String extension = getExtension(file);
+        fileName = DateUtils.datePath() + "/" + IdUtil.fastUUID() + "." + extension;
+        return fileName;
+    }
+
+    private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
+    {
+        File desc = new File(uploadDir + File.separator + fileName);
+
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        // 解决undertow文件上传bug,因底层实现不同,无需创建新文件
+//        if (!desc.exists())
+//        {
+//            desc.createNewFile();
+//        }
+        return desc;
+    }
+
+    private static final String getPathFileName(String uploadDir, String fileName) throws IOException
+    {
+        int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
+        String currentDir = StrUtil.subSuf(uploadDir, dirLastIndex);
+        String pathFileName = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
+        return pathFileName;
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @return
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws InvalidExtensionException
+     */
+    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
+            throws FileSizeLimitExceededException, InvalidExtensionException
+    {
+        long size = file.getSize();
+        if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE)
+        {
+            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
+        }
+
+        String fileName = file.getOriginalFilename();
+        String extension = getExtension(file);
+        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
+        {
+            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else
+            {
+                throw new InvalidExtensionException(allowedExtension, extension, fileName);
+            }
+        }
+
+    }
+
+    /**
+     * 文件大小校验
+     *
+     * @param file 上传的文件
+     * @return
+     * @throws FileSizeLimitExceededException 如果超出最大大小
+     * @throws InvalidExtensionException
+     */
+    public static final void assertAllowedFragment(MultipartFile file, String[] allowedExtension,String fileName)
+            throws FileSizeLimitExceededException, InvalidExtensionException
+    {
+        long size = file.getSize();
+        if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE)
+        {
+            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
+        }
+
+        String extension = getExtensionFragment(fileName);
+        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
+        {
+            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
+            {
+                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
+                        fileName);
+            }
+            else
+            {
+                throw new InvalidExtensionException(allowedExtension, extension, fileName);
+            }
+        }
+
+    }
+
+    /**
+     * 判断MIME类型是否是允许的MIME类型
+     *
+     * @param extension
+     * @param allowedExtension
+     * @return
+     */
+    public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
+    {
+        for (String str : allowedExtension)
+        {
+            if (str.equalsIgnoreCase(extension))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取文件名的后缀
+     *
+     * @param fileName 表单文件
+     * @return 后缀名
+     */
+    public static final String getExtensionFragment(String fileName)
+    {
+        String extension = FilenameUtils.getExtension(fileName);
+        if (Validator.isEmpty(extension))
+        {
+            extension = MimeTypeUtils.getExtension(fileName);
+        }
+        return extension;
+    }
+
+    /**
+     * 获取文件名的后缀
+     *
+     * @param file 表单文件
+     * @return 后缀名
+     */
+    public static final String getExtension(MultipartFile file)
+    {
+        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
+        if (Validator.isEmpty(extension))
+        {
+            extension = MimeTypeUtils.getExtension(file.getContentType());
+        }
+        return extension;
+    }
+
+    public static void merge(String fromSign,String from, String to) throws IOException {
+        File t = new File(to);
+        FileInputStream in = null;
+        FileChannel inChannel = null;
+
+        FileOutputStream out = new FileOutputStream(t, true);
+        FileChannel outChannel = out.getChannel();
+
+        File f = new File(from);
+        // 获取目录下的每一个文件名,再将每个文件一次写入目标文件
+        if (f.isDirectory()) {
+            List<File> list = getAllFileAndSort(from,fromSign);
+            // 记录新文件最后一个数据的位置
+            long start = 0;
+            for (File file : list) {
+
+                in = new FileInputStream(file);
+                inChannel = in.getChannel();
+
+                // 从inChannel中读取file.length()长度的数据,写入outChannel的start处
+                outChannel.transferFrom(inChannel, start, file.length());
+                start += file.length();
+                in.close();
+                inChannel.close();
+            }
+        }
+        out.close();
+        outChannel.close();
+    }
+
+    private static List<File> getAllFileAndSort(String dirPath,String fromSign) {
+        File dirFile = new File(dirPath);
+        List<File> list =  Arrays.stream(dirFile.listFiles()).filter(item -> item.getName().contains(fromSign)).collect(Collectors.toList());
+        Collections.sort(list, (o1, o2) -> {
+            return Integer.parseInt(o1.getName().substring(o1.getName().indexOf("_")+1,o1.getName().indexOf(".")))
+                    - Integer.parseInt(o2.getName().substring(o2.getName().indexOf("_")+1,o2.getName().indexOf(".")));
+        });
+        return list;
+    }
+}

+ 569 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/FileUtils.java

@@ -0,0 +1,569 @@
+package org.zhongzheng.common.utils.file;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.config.RuoYiConfig;
+import com.zhongzheng.common.exception.CustomException;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.MediaType;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.commons.CommonsMultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.math.BigInteger;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * 文件处理工具类
+ *
+ * @author zhongzheng
+ */
+public class FileUtils extends org.apache.commons.io.FileUtils
+{
+
+    private static final byte[] buf = new byte[1024];
+    public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
+
+    /**
+     * 输出指定文件的byte数组
+     *
+     * @param filePath 文件路径
+     * @param os 输出流
+     * @return
+     */
+    public static void writeBytes(String filePath, OutputStream os) throws IOException
+    {
+        FileInputStream fis = null;
+        try
+        {
+            File file = new File(filePath);
+            if (!file.exists())
+            {
+                throw new FileNotFoundException(filePath);
+            }
+            fis = new FileInputStream(file);
+            byte[] b = new byte[1024];
+            int length;
+            while ((length = fis.read(b)) > 0)
+            {
+                os.write(b, 0, length);
+            }
+        }
+        catch (IOException e)
+        {
+            throw e;
+        }
+        finally
+        {
+            if (os != null)
+            {
+                try
+                {
+                    os.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (fis != null)
+            {
+                try
+                {
+                    fis.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param filePath 文件
+     * @return
+     */
+    public static boolean deleteFile(String filePath)
+    {
+        boolean flag = false;
+        File file = new File(filePath);
+        // 路径为文件且不为空则进行删除
+        if (file.isFile() && file.exists())
+        {
+            file.delete();
+            flag = true;
+        }
+        return flag;
+    }
+
+    /**
+     * 文件名称验证
+     *
+     * @param filename 文件名称
+     * @return true 正常 false 非法
+     */
+    public static boolean isValidFilename(String filename)
+    {
+        return filename.matches(FILENAME_PATTERN);
+    }
+
+    /**
+     * 检查文件是否可下载
+     *
+     * @param resource 需要下载的文件
+     * @return true 正常 false 非法
+     */
+    public static boolean checkAllowDownload(String resource)
+    {
+        // 禁止目录上跳级别
+        if (StrUtil.contains(resource, ".."))
+        {
+            return false;
+        }
+
+        // 检查允许下载的文件规则
+        if (ArrayUtil.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
+        {
+            return true;
+        }
+
+        // 不在允许下载的文件规则
+        return false;
+    }
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @param request 请求对象
+     * @param fileName 文件名
+     * @return 编码后的文件名
+     */
+    public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
+    {
+        final String agent = request.getHeader("USER-AGENT");
+        String filename = fileName;
+        if (agent.contains("MSIE"))
+        {
+            // IE浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+            filename = filename.replace("+", " ");
+        }
+        else if (agent.contains("Firefox"))
+        {
+            // 火狐浏览器
+            filename = new String(fileName.getBytes(), "ISO8859-1");
+        }
+        else if (agent.contains("Chrome"))
+        {
+            // google浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+        }
+        else
+        {
+            // 其它浏览器
+            filename = URLEncoder.encode(filename, "utf-8");
+        }
+        return filename;
+    }
+
+    /**
+     * 下载文件名重新编码
+     *
+     * @param response 响应对象
+     * @param realFileName 真实文件名
+     * @return
+     */
+    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
+    {
+        String percentEncodedFileName = percentEncode(realFileName);
+
+        StringBuilder contentDispositionValue = new StringBuilder();
+        contentDispositionValue.append("attachment; filename=")
+                .append(percentEncodedFileName)
+                .append(";")
+                .append("filename*=")
+                .append("utf-8''")
+                .append(percentEncodedFileName);
+
+        response.setHeader("Content-disposition", contentDispositionValue.toString());
+    }
+
+    /**
+     * 百分号编码工具方法
+     *
+     * @param s 需要百分号编码的字符串
+     * @return 百分号编码后的字符串
+     */
+    public static String percentEncode(String s) throws UnsupportedEncodingException
+    {
+        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
+        return encode.replaceAll("\\+", "%20");
+    }
+
+    public static String encodingZipFilename(String prefix)
+    {
+        String  filename = prefix+"_"+ UUID.randomUUID().toString() + ".zip";
+        return filename;
+    }
+
+    public static String getZipAbsoluteFile(String filename)
+    {
+        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
+        File desc = new File(downloadPath);
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        return downloadPath;
+    }
+
+    public static void deleteDirectoryStream(Path path) {
+        try {
+            Files.delete(path);
+            System.out.printf("删除文件成功:%s%n",path.toString());
+        } catch (IOException e) {
+            System.err.printf("无法删除的路径 %s%n%s", path, e);
+        }
+    }
+    
+    /**
+     * File转MultipartFile
+     * @author change
+     * @date 2023/4/10 11:09
+     * @param file 
+     * @return org.springframework.web.multipart.MultipartFile
+     */
+    public static MultipartFile getMultipartFile(File file) {
+        FileItem item = new DiskFileItemFactory().createItem("file"
+                , MediaType.MULTIPART_FORM_DATA_VALUE
+                , true
+                , file.getName());
+        try (InputStream input = new FileInputStream(file);
+             OutputStream os = item.getOutputStream()) {
+            // 流转移
+            IOUtils.copy(input, os);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new IllegalArgumentException("Invalid file: " + e, e);
+        }
+
+        return new CommonsMultipartFile(item);
+    }
+
+    /**
+     * 获取文件md5值
+     */
+    public static String md5HashCode(String filePath) {
+        try {
+            InputStream fis = new FileInputStream(filePath);
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] buffer = new byte[1024];
+            int length = -1;
+            while ((length = fis.read(buffer, 0, 1024)) != -1) {
+                md.update(buffer, 0, length);
+            }
+            fis.close();
+            //转换并返回包含16个元素字节数组,返回数值范围为-128到127
+            byte[] md5Bytes = md.digest();
+            BigInteger bigInt = new BigInteger(1, md5Bytes);
+            return bigInt.toString(16);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    public static void deleteFilePackage(String path){
+        //删除本地资源
+        Path pathStr = Paths.get(path);
+        try (Stream<Path> walk = Files.walk(pathStr)) {
+            walk.sorted(Comparator.reverseOrder())
+                    .forEach(FileUtils::deleteDirectoryStream);
+        }catch (IOException e) {
+            e.printStackTrace();
+            throw new CustomException(e.getMessage());
+        }
+    }
+
+    /**
+     * 压缩成ZIP 方法1
+     *
+     * @param zipFileName       压缩文件夹路径
+     * @param sourceFileName    要压缩的文件路径
+     * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
+     *                         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
+     * @throws RuntimeException 压缩失败会抛出运行时异常
+     */
+    public static Boolean toZip(String zipFileName, String sourceFileName, boolean KeepDirStructure) {
+        Boolean result = true;
+        long start = System.currentTimeMillis();//开始
+        ZipOutputStream zos = null;
+        try {
+            FileOutputStream fileOutputStream = new FileOutputStream(zipFileName);
+            zos = new ZipOutputStream(fileOutputStream);
+            File sourceFile = new File(sourceFileName);
+            compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
+            long end = System.currentTimeMillis();//结束
+            System.out.println("压缩完成,耗时:" + (end - start) + " 毫秒");
+        } catch (Exception e) {
+            result = false;
+            e.printStackTrace();
+        } finally {
+            if (zos != null) {
+                try {
+                    zos.close();
+                } catch (IOException e) {
+                    e.getStackTrace();
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @Title: compress
+     * @Description: TODO
+     * @param filePaths 需要压缩的文件地址列表(绝对路径)
+     * @param zipFilePath 需要压缩到哪个zip文件(无需创建这样一个zip,只需要指定一个全路径)
+     * @param keepDirStructure 压缩后目录是否保持原目录结构
+     * @throws IOException
+     * @return int   压缩成功的文件个数
+     */
+    public static Boolean compressList(List<String> filePaths, String zipFilePath, Boolean keepDirStructure){
+        byte[] buf = new byte[1024];
+        File zipFile = new File(zipFilePath);
+        int fileCount = 0;//记录压缩了几个文件?
+        try {
+            //zip文件不存在,则创建文件,用于压缩
+            if(!zipFile.exists()){
+                zipFile.createNewFile();
+            }
+            ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile));
+            for(int i = 0; i < filePaths.size(); i++){
+                String relativePath = filePaths.get(i);
+                if(StringUtils.isEmpty(relativePath)){
+                    continue;
+                }
+                File sourceFile = new File(relativePath);//绝对路径找到file
+                if(sourceFile == null || !sourceFile.exists()){
+                    continue;
+                }
+
+                FileInputStream fis = new FileInputStream(sourceFile);
+                if(keepDirStructure!=null && keepDirStructure){
+                    //保持目录结构
+                    zos.putNextEntry(new ZipEntry(relativePath));
+                }else{
+                    //直接放到压缩包的根目录
+                    zos.putNextEntry(new ZipEntry(sourceFile.getName()));
+                }
+                //System.out.println("压缩当前文件:"+sourceFile.getName());
+                int len;
+                while((len = fis.read(buf)) > 0){
+                    zos.write(buf, 0, len);
+                }
+                zos.closeEntry();
+                fis.close();
+                fileCount++;
+            }
+            zos.close();
+            //System.out.println("压缩完成");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return fileCount > 0;
+    }
+
+
+    /**
+     * 递归压缩方法
+     *
+     * @param sourceFile       源文件
+     * @param zos              zip输出流
+     * @param name             压缩后的名称
+     * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
+     *                         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
+     * @throws Exception
+     */
+    public static void compress(File sourceFile, ZipOutputStream zos, String name,
+                                boolean KeepDirStructure) throws Exception {
+
+        if (sourceFile.isFile()) {
+            // 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
+            zos.putNextEntry(new ZipEntry(name));
+            // copy文件到zip输出流中
+            int len;
+            FileInputStream in = new FileInputStream(sourceFile);
+            while ((len = in.read(buf)) != -1) {
+                zos.write(buf, 0, len);
+            }
+            // Complete the entry
+            zos.closeEntry();
+            in.close();
+        } else {
+            File[] listFiles = sourceFile.listFiles();
+            if (listFiles == null || listFiles.length == 0) {
+                // 需要保留原来的文件结构时,需要对空文件夹进行处理
+                if (KeepDirStructure) {
+                    // 空文件夹的处理
+                    zos.putNextEntry(new ZipEntry(name + "/"));
+                    // 没有文件,不需要文件的copy
+                    zos.closeEntry();
+                }
+            } else {
+                for (File file : listFiles) {
+                    // 判断是否需要保留原来的文件结构
+                    if (KeepDirStructure) {
+                        // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
+                        // 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
+                        compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
+                    } else {
+                        compress(file, zos, file.getName(), KeepDirStructure);
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 解压
+     *
+     * @param inputStream zip压缩文件
+     * @param descDir 指定的解压目录
+     */
+    public static void unzipWithStream(InputStream inputStream, String descDir) {
+        if (!descDir.endsWith(File.separator)) {
+            descDir = descDir + File.separator;
+        }
+        try (ZipInputStream zipInputStream = new ZipInputStream(inputStream, Charset.forName("GBK"))) {
+            ZipEntry zipEntry;
+            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
+                String zipEntryNameStr = zipEntry.getName();
+                String zipEntryName = zipEntryNameStr;
+                if (zipEntryNameStr.contains("/")) {
+                    String str1 = zipEntryNameStr.substring(0, zipEntryNameStr.indexOf("/"));
+                    zipEntryName = zipEntryNameStr.substring(str1.length() + 1);
+                }
+                String outPath = (descDir + zipEntryName).replace("\\\\", "/");
+                File outFile = new File(outPath.substring(0, outPath.lastIndexOf('/')));
+                if (!outFile.exists()) {
+                    outFile.mkdirs();
+                }
+                if (new File(outPath).isDirectory()) {
+                    continue;
+                }
+                writeFile(outPath, zipInputStream);
+                zipInputStream.closeEntry();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new CustomException("压缩包处理异常,异常信息{}"+ e);
+        }
+    }
+
+    //将流写到文件中
+    public static void writeFile(String filePath, ZipInputStream zipInputStream) {
+        File file = new File(filePath);
+        File directory = file.getParentFile();
+        if (!directory.exists()){
+            directory.mkdirs();
+        }
+        try (OutputStream outputStream = new FileOutputStream(filePath)) {
+            byte[] bytes = new byte[4096];
+            int len;
+            while ((len = zipInputStream.read(bytes)) != -1) {
+                outputStream.write(bytes, 0, len);
+            }
+        } catch (IOException ex) {
+            ex.printStackTrace();
+            throw new CustomException("解压文件时,写出到文件出错!");
+        }
+    }
+
+
+    /**
+     * 打包压缩文件夹
+     *
+     * @param folderPath 文件夹路径
+     * @param zipFilePath 压缩后的文件路径
+     * @throws IOException IO异常
+     */
+    public static void zipFolder(String folderPath, String zipFilePath) throws IOException {
+        FileOutputStream fos = null;
+        ZipOutputStream zos = null;
+        try {
+            fos = new FileOutputStream(zipFilePath);
+            zos = new ZipOutputStream(fos);
+
+            // 递归遍历整个文件夹并添加到压缩包
+            addFolderToZip("", new File(folderPath), zos);
+        } finally {
+            if (zos != null) {
+                zos.close();
+            }
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+
+    /**
+     * 将文件夹及其中的文件递归添加到压缩流中
+     *
+     * @param parentPath 父级路径
+     * @param folder 文件夹
+     * @param zos Zip输出流
+     * @throws FileNotFoundException 文件未找到异常
+     * @throws IOException IO异常
+     */
+    private static void addFolderToZip(String parentPath, File folder, ZipOutputStream zos) throws FileNotFoundException, IOException {
+        for (File file : folder.listFiles()) {
+            if (file.isDirectory()) {
+                // 递归添加子文件夹中的文件
+                addFolderToZip(parentPath + folder.getName() + "/", file, zos);
+            } else {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(file);
+
+                    // 新建Zip条目并将输入流加入到Zip包中
+                    ZipEntry zipEntry = new ZipEntry( folder.getName() + "/" + file.getName());
+                    zos.putNextEntry(zipEntry);
+
+                    byte[] bytes = new byte[1024];
+                    int length;
+                    while ((length = fis.read(bytes)) >= 0) {
+                        zos.write(bytes, 0, length);
+                    }
+                } finally {
+                    if (fis != null) {
+                        fis.close();
+                    }
+                }
+            }
+        }
+    }
+
+}

+ 131 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/ImageUtils.java

@@ -0,0 +1,131 @@
+package org.zhongzheng.common.utils.file;
+
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.config.RuoYiConfig;
+import com.zhongzheng.common.constant.Constants;
+import org.apache.poi.util.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sun.misc.BASE64Encoder;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+
+/**
+ * 图片处理工具类
+ *
+ * @author zhongzheng
+ */
+public class ImageUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
+
+    public static byte[] getImage(String imagePath)
+    {
+        InputStream is = getFile(imagePath);
+        try
+        {
+            return IOUtils.toByteArray(is);
+        }
+        catch (Exception e)
+        {
+            log.error("图片加载异常 {}", e);
+            return null;
+        }
+        finally
+        {
+            IOUtils.closeQuietly(is);
+        }
+    }
+
+    public static InputStream getFile(String imagePath)
+    {
+        try
+        {
+            byte[] result = readFile(imagePath);
+            result = Arrays.copyOf(result, result.length);
+            return new ByteArrayInputStream(result);
+        }
+        catch (Exception e)
+        {
+            log.error("获取图片异常 {}", e);
+        }
+        return null;
+    }
+
+    /**
+     * 读取文件为字节数据
+     *
+     * @param key 地址
+     * @return 字节数据
+     */
+    public static byte[] readFile(String url)
+    {
+        InputStream in = null;
+        ByteArrayOutputStream baos = null;
+        try
+        {
+            if (url.startsWith("http"))
+            {
+                // 网络地址
+                URL urlObj = new URL(url);
+                URLConnection urlConnection = urlObj.openConnection();
+                urlConnection.setConnectTimeout(30 * 1000);
+                urlConnection.setReadTimeout(60 * 1000);
+                urlConnection.setDoInput(true);
+                in = urlConnection.getInputStream();
+            }
+            else
+            {
+                // 本机地址
+                String localPath = RuoYiConfig.getProfile();
+                String downloadPath = localPath + StrUtil.subAfter(url, Constants.RESOURCE_PREFIX,false);
+                in = new FileInputStream(downloadPath);
+            }
+            return IOUtils.toByteArray(in);
+        }
+        catch (Exception e)
+        {
+            log.error("获取文件路径异常 {}", e);
+            return null;
+        }
+        finally
+        {
+            IOUtils.closeQuietly(baos);
+        }
+    }
+
+
+    public static String encodeImgageToBase64(String url) {
+        BASE64Encoder encoder = new BASE64Encoder();
+        String s= encoder.encode(readFile(url));
+        return s;
+
+    }
+
+    /** * 旋转图片为指定角度  图片宽高不变*
+     * @param bufferedimage * 目标图像 *
+     * @param degree * 旋转角度 *
+     * @return */
+    public static BufferedImage rotateImage(final BufferedImage bufferedimage, final int degree)
+    {
+        int w= bufferedimage.getWidth(); // 得到图片宽度。
+        int h= bufferedimage.getHeight();// 得到图片高度。
+        int type= bufferedimage.getColorModel().getTransparency();// 得到图片透明度。
+        BufferedImage img;// 空的图片。
+        Graphics2D graphics2d;// 空的画笔。
+        (graphics2d= (img= new BufferedImage(w, h, type)).createGraphics()).setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+        graphics2d.rotate(Math.toRadians(degree), w/2, h/2);// 旋转,degree是整型,度数,比如垂直90度。   •rotate(double arc,double x, double y):图形以点(x,y)为轴点,旋转arc弧度。
+        graphics2d.drawImage(bufferedimage, 0, 0, null);// 从bufferedimagecopy图片至img,0,0是img的坐标。
+        graphics2d.dispose();
+
+        return img;// 返回复制好的图片,原图片依然没有变,没有旋转,下次还可以使用。
+    }
+}

+ 55 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/file/MimeTypeUtils.java

@@ -0,0 +1,55 @@
+package org.zhongzheng.common.utils.file;
+
+/**
+ * 媒体类型工具类
+ * 
+ * @author zhongzheng
+ */
+public class MimeTypeUtils
+{
+    public static final String IMAGE_PNG = "image/png";
+
+    public static final String IMAGE_JPG = "image/jpg";
+
+    public static final String IMAGE_JPEG = "image/jpeg";
+
+    public static final String IMAGE_BMP = "image/bmp";
+
+    public static final String IMAGE_GIF = "image/gif";
+    
+    public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
+
+    public static final String[] FLASH_EXTENSION = { "swf", "flv" };
+
+    public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+            "asf", "rm", "rmvb" };
+
+    public static final String[] DEFAULT_ALLOWED_EXTENSION = {
+            // 图片
+            "bmp", "gif", "jpg", "jpeg", "png",
+            // word excel powerpoint
+            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+            // 压缩文件
+            "rar", "zip", "gz", "bz2",
+            // pdf
+            "pdf" };
+
+    public static String getExtension(String prefix)
+    {
+        switch (prefix)
+        {
+            case IMAGE_PNG:
+                return "png";
+            case IMAGE_JPG:
+                return "jpg";
+            case IMAGE_JPEG:
+                return "jpeg";
+            case IMAGE_BMP:
+                return "bmp";
+            case IMAGE_GIF:
+                return "gif";
+            default:
+                return "";
+        }
+    }
+}

+ 56 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/http/HttpHelper.java

@@ -0,0 +1,56 @@
+package org.zhongzheng.common.utils.http;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletRequest;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+
+/**
+ * 通用http工具封装
+ * 
+ * @author zhongzheng
+ */
+public class HttpHelper
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);
+
+    public static String getBodyString(ServletRequest request)
+    {
+        StringBuilder sb = new StringBuilder();
+        BufferedReader reader = null;
+        try (InputStream inputStream = request.getInputStream())
+        {
+            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
+            String line = "";
+            while ((line = reader.readLine()) != null)
+            {
+                sb.append(line);
+            }
+        }
+        catch (IOException e)
+        {
+            LOGGER.warn("getBodyString出现问题!");
+        }
+        finally
+        {
+            if (reader != null)
+            {
+                try
+                {
+                    reader.close();
+                }
+                catch (IOException e)
+                {
+                    LOGGER.error(ExceptionUtil.getMessage(e));
+                }
+            }
+        }
+        return sb.toString();
+    }
+}

+ 727 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/http/HttpUtils.java

@@ -0,0 +1,727 @@
+package org.zhongzheng.common.utils.http;
+
+import cn.hutool.core.lang.Validator;
+import com.alibaba.fastjson.JSONObject;
+import com.sun.deploy.net.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpEntity;
+
+import javax.net.ssl.*;
+import java.io.*;
+import java.net.*;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+
+
+/**
+ * 通用http发送方法
+ *
+ * @author zhongzheng
+ */
+public class HttpUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
+    private static final String UTF8 = "UTF-8";
+    private static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded; charset=UTF-8";
+    private static final CloseableHttpClient httpclient = HttpClients.createDefault();
+
+    /**
+     * 向指定 URL 发送GET方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendGet(String url, String param)
+    {
+        return sendGet(url, param, UTF8);
+    }
+
+    /**
+     * 向指定 URL 发送GET方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @param contentType 编码类型
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendGet(String url, String param, String contentType)
+    {
+        StringBuilder result = new StringBuilder();
+        BufferedReader in = null;
+        try
+        {
+            String urlNameString = url + "?" + param;
+            log.info("sendGet - {}", urlNameString);
+            URL realUrl = new URL(urlNameString);
+            URLConnection connection = realUrl.openConnection();
+            connection.setRequestProperty("accept", "*/*");
+            connection.setRequestProperty("connection", "Keep-Alive");
+            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            connection.connect();
+            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
+            String line;
+            while ((line = in.readLine()) != null)
+            {
+                result.append(line);
+            }
+        //    log.info("recv - {}", result);
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
+        }
+        finally
+        {
+            try
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+            catch (Exception ex)
+            {
+                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
+            }
+        }
+        return result.toString();
+    }
+    public static String postFormBody(String url, Map<String, String> paramMap) throws IOException{
+        try{
+            String result = post(url, paramMap, null);
+            return result;
+        }catch (Exception e){
+            return null;
+        }
+
+    }
+
+    public static String postFormBodyHeader(String url, Map<String, String> paramMap, Map<String, String> headersMap) throws IOException{
+        try{
+            String result = postHead(url, paramMap, null,headersMap);
+            return result;
+        }catch (Exception e){
+            return null;
+        }
+
+    }
+
+    public static String sendPost(String url, JSONObject param)
+    {
+        HttpClient client = HttpClients.createDefault();
+        HttpPost post = new HttpPost(url);
+        try {
+            //此处应设定参数的编码格式,不然中文会变乱码
+            StringEntity s = new StringEntity(param.toString(), "UTF-8");
+            s.setContentEncoding("UTF-8");
+            s.setContentType("application/json");
+            post.setEntity(s);
+            post.addHeader("content-type", "application/json");
+            HttpResponse res = client.execute(post);
+            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                if (res.getEntity().getContentType().getValue().equalsIgnoreCase("image/jpeg")) {
+                    InputStream inputStream = res.getEntity().getContent();
+                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                    byte[] buffer = new byte[1024];
+                    int len = 0;
+                    while ((len = inputStream.read(buffer)) != -1){
+                        outputStream.write(buffer, 0, len);
+                    }
+                    inputStream.close();
+                    Base64.Encoder encoder1 = Base64.getEncoder();
+                    String encoder = "data:image/jpeg;base64,"
+                            + encoder1.encodeToString(outputStream.toByteArray());
+                    return encoder;
+                }
+                String result = EntityUtils.toString(res.getEntity());// 返回json格式
+                System.out.println("推送成功" + result);
+                return result;
+            } else {
+                System.out.println("推送失败");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    public static String sendPostJsonHeader(String url, String json, Map<String, String> headersMap)
+    {
+   //     System.out.println(json);
+        HttpClient client = HttpClients.createDefault();
+        HttpPost post = new HttpPost(url);
+        try {
+            //此处应设定参数的编码格式,不然中文会变乱码
+            StringEntity s = new StringEntity(json, "UTF-8");
+            s.setContentEncoding("UTF-8");
+            s.setContentType("application/json");
+            post.setEntity(s);
+            post.addHeader("content-type", "application/json");
+            headersMap.forEach(post::setHeader);
+            HttpResponse res = client.execute(post);
+            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                if (res.getEntity().getContentType().getValue().equalsIgnoreCase("image/jpeg")) {
+                    InputStream inputStream = res.getEntity().getContent();
+                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                    byte[] buffer = new byte[1024];
+                    int len = 0;
+                    while ((len = inputStream.read(buffer)) != -1){
+                        outputStream.write(buffer, 0, len);
+                    }
+                    inputStream.close();
+                    Base64.Encoder encoder1 = Base64.getEncoder();
+                    String encoder = "data:image/jpeg;base64,"
+                            + encoder1.encodeToString(outputStream.toByteArray());
+                    return encoder;
+                }
+                String result = EntityUtils.toString(res.getEntity());// 返回json格式
+                System.out.println("推送成功" + result);
+                return result;
+            } else {
+                System.out.println("推送失败");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    public static String sendPostJsonHeaderAsync(String url, String json, Map<String, String> headersMap)
+    {
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5*1000).setConnectTimeout(5*1000).build();
+        HttpClient client =  HttpClients.createDefault();
+        HttpPost post = new HttpPost(url);
+        post.setConfig(requestConfig);
+        try {
+            //此处应设定参数的编码格式,不然中文会变乱码
+            StringEntity s = new StringEntity(json, "UTF-8");
+            s.setContentEncoding("UTF-8");
+            s.setContentType("application/json");
+            post.setEntity(s);
+            post.addHeader("content-type", "application/json");
+            headersMap.forEach(post::setHeader);
+            client.execute(post);
+        } catch (Exception e) {
+            e.printStackTrace();
+//            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    public static String sendPostHeader(String url, JSONObject param, Map<String, String> headersMap)
+    {
+        HttpClient client = HttpClients.createDefault();
+        HttpPost post = new HttpPost(url);
+        try {
+            //此处应设定参数的编码格式,不然中文会变乱码
+            StringEntity s = new StringEntity(param.toString(), "UTF-8");
+            s.setContentEncoding("UTF-8");
+            s.setContentType("application/json");
+            post.setEntity(s);
+            post.addHeader("content-type", "application/json");
+            headersMap.forEach(post::setHeader);
+            HttpResponse res = client.execute(post);
+            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                if (res.getEntity().getContentType().getValue().equalsIgnoreCase("image/jpeg")) {
+                    InputStream inputStream = res.getEntity().getContent();
+                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                    byte[] buffer = new byte[1024];
+                    int len = 0;
+                    while ((len = inputStream.read(buffer)) != -1){
+                        outputStream.write(buffer, 0, len);
+                    }
+                    inputStream.close();
+                    Base64.Encoder encoder1 = Base64.getEncoder();
+                    String encoder = "data:image/jpeg;base64,"
+                            + encoder1.encodeToString(outputStream.toByteArray());
+                    return encoder;
+                }
+                String result = EntityUtils.toString(res.getEntity());// 返回json格式
+                System.out.println("推送成功" + result);
+                return result;
+            } else {
+                System.out.println("推送失败");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    /**
+     * 向指定 URL 发送POST方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @return 所代表远程资源的响应结果
+     */
+    public static String sendPost(String url, String param)
+    {
+        System.out.println(param);
+        PrintWriter out = null;
+        BufferedReader in = null;
+        StringBuilder result = new StringBuilder();
+        try
+        {
+            String urlNameString = url;
+            log.info("sendPost - {}", urlNameString);
+            URL realUrl = new URL(urlNameString);
+            URLConnection conn = realUrl.openConnection();
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            conn.setRequestProperty("Accept-Charset", "utf-8");
+            conn.setRequestProperty("contentType", "application/json;charset=UTF-8");
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            out = new PrintWriter(conn.getOutputStream());
+            out.print(param);
+            out.flush();
+            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
+            String line;
+            while ((line = in.readLine()) != null)
+            {
+                result.append(line);
+            }
+            log.info("recv - {}", result);
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
+        }
+        finally
+        {
+            try
+            {
+                if (out != null)
+                {
+                    out.close();
+                }
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+            catch (IOException ex)
+            {
+                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
+            }
+        }
+        return result.toString();
+    }
+
+    public static String sendSSLPost(String url, String param)
+    {
+        StringBuilder result = new StringBuilder();
+        String urlNameString = url + "?" + param;
+        try
+        {
+            log.info("sendSSLPost - {}", urlNameString);
+            SSLContext sc = SSLContext.getInstance("SSL");
+            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
+            URL console = new URL(urlNameString);
+            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+            conn.setRequestProperty("Accept-Charset", "utf-8");
+            conn.setRequestProperty("contentType", "utf-8");
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+
+            conn.setSSLSocketFactory(sc.getSocketFactory());
+            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
+            conn.connect();
+            InputStream is = conn.getInputStream();
+            BufferedReader br = new BufferedReader(new InputStreamReader(is));
+            String ret = "";
+            while ((ret = br.readLine()) != null)
+            {
+                if (ret != null && !"".equals(ret.trim()))
+                {
+                    result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8"));
+                }
+            }
+            log.info("recv - {}", result);
+            conn.disconnect();
+            br.close();
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
+        }
+        return result.toString();
+    }
+
+    private static class TrustAnyTrustManager implements X509TrustManager
+    {
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+        {
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+        {
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers()
+        {
+            return new X509Certificate[] {};
+        }
+    }
+
+    private static class TrustAnyHostnameVerifier implements HostnameVerifier
+    {
+        @Override
+        public boolean verify(String hostname, SSLSession session)
+        {
+            return true;
+        }
+    }
+
+    /**
+     * 公共数据解析接口
+     * @param <T>
+     */
+    private interface DataParse<T> {
+        /**
+         * 解析返回数据
+         * @param httpEntity 返回实体
+         * @param encoding 编码
+         * @return 实际解析返回内容
+         * @throws IOException io异常
+         */
+        T parseData(HttpEntity httpEntity, String encoding) throws IOException;
+
+    }
+
+    /**
+     * 将url与map拼接成HTTP查询字符串
+     * @param url 请求url
+     * @param paramMap 需要拼装的map
+     * @return 拼装好的url
+     */
+    public static String appendUrl(String url, Map<String, String> paramMap) throws UnsupportedEncodingException {
+        if (paramMap == null) {
+            return url;
+        }
+        StringBuffer paramStringBuffer = new StringBuffer();
+        Iterator<Map.Entry<String, String>> mapIterator = paramMap.entrySet().iterator();
+        while (mapIterator.hasNext()) {
+            Map.Entry<String, String> next = mapIterator.next();
+            paramStringBuffer.append(next.getKey()).append("=").append(URLEncoder.encode(next.getValue(), UTF8)).append("&");
+        }
+        String paramStr = paramStringBuffer.toString();
+//        String paramStr = mapJoinNotEncode(paramMap);
+        if (paramStr != null && !"".equals(paramStr)) {
+            if (url.indexOf("?") > 0) {
+                if (url.endsWith("&")) {
+                    url += paramStr.substring(0, paramStr.length() - 1);
+                } else {
+                    url += "&" + paramStr.substring(0, paramStr.length() - 1);
+                }
+            } else {
+                url += "?" + paramStr.substring(0, paramStr.length() - 1);
+            }
+        }
+        return url;
+    }
+
+    private static String postHead(String url, Map<String, String> paramMap, String encoding, Map<String, String> headersMap) throws IOException, NoSuchAlgorithmException, KeyManagementException {
+        log.debug("http 请求 url: {} , 请求参数: {}", url, appendUrl("", paramMap).replace("?", ""));
+        encoding = encoding == null ? UTF8 : encoding;
+        // 创建post方式请求对象
+        HttpPost httpPost = new HttpPost(url);
+        // 装填参数
+        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
+        if (paramMap != null) {
+            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+                String value = entry.getValue();
+                //去掉如下判断会造成String类型的value为null时
+                if (value != null) {
+                    nameValuePairs.add(new BasicNameValuePair(entry.getKey(), value));
+                }
+            }
+        }
+        // 设置参数到请求对象中
+        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, encoding));
+        // 设置header信息
+        // 指定报文头【Content-type】、【User-Agent】
+        httpPost.setHeader("Content-type", APPLICATION_FORM_URLENCODED);
+        headersMap.forEach(httpPost::setHeader);
+        return post(url, httpPost, encoding, new DataParse<String>() {
+            @Override
+            public String parseData(HttpEntity httpEntity, String encoding) throws IOException {
+                if(Validator.isNotEmpty(httpEntity)){
+                    return EntityUtils.toString(httpEntity, encoding);
+                }else{
+                    return null;
+                }
+
+            }
+        });
+    }
+
+    /**
+     * 向url发送post请求表单提交数据
+     * @param url 请求url
+     * @param paramMap 表单数据
+     * @param encoding 编码
+     * @return 请求返回的数据
+     * @throws IOException 读写异常
+     */
+    private static String post(String url, Map<String, String> paramMap, String encoding) throws IOException, NoSuchAlgorithmException, KeyManagementException {
+        log.debug("http 请求 url: {} , 请求参数: {}", url, appendUrl("", paramMap).replace("?", ""));
+        encoding = encoding == null ? UTF8 : encoding;
+        // 创建post方式请求对象
+        HttpPost httpPost = new HttpPost(url);
+        // 装填参数
+        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
+        if (paramMap != null) {
+            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+                String value = entry.getValue();
+                //去掉如下判断会造成String类型的value为null时
+                if (value != null) {
+                    nameValuePairs.add(new BasicNameValuePair(entry.getKey(), value));
+                }
+            }
+        }
+        // 设置参数到请求对象中
+        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, encoding));
+        // 设置header信息
+        // 指定报文头【Content-type】、【User-Agent】
+        httpPost.setHeader("Content-type", APPLICATION_FORM_URLENCODED);
+        return post(url, httpPost, encoding, new DataParse<String>() {
+            @Override
+            public String parseData(HttpEntity httpEntity, String encoding) throws IOException {
+                if(Validator.isNotEmpty(httpEntity)){
+                    return EntityUtils.toString(httpEntity, encoding);
+                }else{
+                    return null;
+                }
+
+            }
+        });
+    }
+
+    /**
+     * 向url发送post请求
+     * @param url 请求url
+     * @param httpPost httpClient
+     * @return 请求返回的数据
+     * @throws IOException 读写异常
+     */
+    private static <T> T post(String url, HttpPost httpPost, String encoding, DataParse<T> dataParse)
+            throws IOException, NoSuchAlgorithmException, KeyManagementException {
+        TrustManager[] trustAllCerts = new TrustManager[] {
+                new X509TrustManager() {
+                    public X509Certificate[] getAcceptedIssuers() {
+                        return null;
+                    }
+                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
+                        // don't check
+                    }
+                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
+                        // don't check
+                    }
+                }
+        };
+
+        SSLContext ctx = SSLContext.getInstance("TLS");
+        ctx.init(null, trustAllCerts, null);
+        LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(ctx);
+        T result = null;
+        CloseableHttpResponse response = null;
+        // 创建httpclient对象
+        CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLSocketFactory(sslSocketFactory).build();
+
+        // 执行请求操作,并拿到结果(同步阻塞)
+        response = sendRequestAndGetResult(url, httpClient, httpPost);
+        // 获取结果实体
+        // 判断网络连接状态码是否正常(0--200都数正常)
+        if (null != response) {
+            result = dataParse.parseData(response.getEntity(), encoding);
+            log.debug("http 请求结果: {}", result);
+        }
+        try {
+            if (null != response) {
+                response.close();
+            }
+        } catch (IOException ex) {
+            log.error(ex.getMessage(), ex);
+        }
+        return result;
+    }
+
+    /**
+     * 设置http头,发送http请求,打印请求耗时
+     * @param url 请求url
+     * @param httpClient httpClient
+     * @param httpUriRequest httpUriRequest
+     * @return 请求返回的数据
+     * @throws IOException 读写异常
+     */
+    private static CloseableHttpResponse sendRequestAndGetResult(String url, CloseableHttpClient httpClient,
+                                                                 HttpUriRequest httpUriRequest) throws IOException {
+        long startTime = System.currentTimeMillis();
+        CloseableHttpResponse response = httpClient.execute(httpUriRequest);
+        long endTime = System.currentTimeMillis();
+        collectAPISpendTime(url, startTime, endTime);
+        return response;
+    }
+
+    /**
+     * 打印请求信息
+     * @param url 请求url
+     * @param startTime 请求开始时间
+     * @param endTime 请求结束时间
+     */
+    private static void collectAPISpendTime(String url, long startTime, long endTime) {
+        log.debug("HTTP请求耗时分析,请求URL: {} , 耗时: {} ms", url, endTime - startTime);
+    }
+
+    /**
+     * 向指定 URL 发送POST方法的请求
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @param headersMap 请求头
+     * @return 所代表远程资源的响应结果
+     */
+    public static CloseableHttpResponse sendPostHeader(String url, String param, Map<String, String> headersMap)
+    {
+        // 字符串编码
+        StringEntity entity = new StringEntity(param, Consts.UTF_8);
+        // 设置content-type
+        entity.setContentType("application/json");
+        HttpPost httpPost = new HttpPost(url);
+        // 防止被当成攻击添加的
+        httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1");
+        // 接收参数设置
+        httpPost.setHeader("connection", "Keep-Alive");
+        httpPost.setHeader("Accept", "application/json");
+        httpPost.setHeader("Accept-Charset", "utf-8");
+        httpPost.setHeader("contentType", "utf-8");
+        headersMap.forEach(httpPost::setHeader);
+        httpPost.setEntity(entity);
+        CloseableHttpResponse response = null;
+        try {
+            response = httpclient.execute(httpPost);
+            Header[] headers = response.getHeaders("Set-Cookie");
+            Arrays.stream(headers).forEach(httpPost::addHeader);
+        } catch (IOException e) {
+            log.error(e.getMessage());
+        }
+        return response;
+    }
+
+    public static String postFormBodyHeader(String url, Map<String, String> paramMap, Map<String, String> headersMap, Header[] header) throws IOException{
+        try{
+            String result = postHearder(url, paramMap, null, headersMap, header);
+            return result;
+        }catch (Exception e){
+            return null;
+        }
+    }
+
+    /**
+     * 向url发送post请求表单提交数据
+     * @param url 请求url
+     * @param paramMap 表单数据
+     * @param encoding 编码
+     * @param headersMap 请求头
+     * @return 请求返回的数据
+     * @throws IOException 读写异常
+     */
+    private static String postHearder(String url, Map<String, String> paramMap, String encoding, Map<String, String> headersMap, Header[] header) throws IOException, NoSuchAlgorithmException, KeyManagementException {
+        log.debug("http 请求 url: {} , 请求参数: {}", url, appendUrl("", paramMap).replace("?", ""));
+        encoding = encoding == null ? UTF8 : encoding;
+        // 创建post方式请求对象
+        HttpPost httpPost = new HttpPost(url);
+        // 装填参数
+        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
+        if (paramMap != null) {
+            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+                String value = entry.getValue();
+                //去掉如下判断会造成String类型的value为null时
+                if (value != null) {
+                    nameValuePairs.add(new BasicNameValuePair(entry.getKey(), value));
+                }
+            }
+        }
+        // 设置参数到请求对象中
+        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, encoding));
+        // 设置header信息
+        // 指定报文头【Content-type】、【User-Agent】
+        httpPost.setHeader("Content-type", APPLICATION_FORM_URLENCODED);
+        httpPost.setHeader("Accept", "*/*");
+        httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1");
+        headersMap.forEach(httpPost::setHeader);
+        Arrays.stream(header).forEach(head->{
+            httpPost.addHeader("Cookie", head.getValue());
+        });
+        return post(url, httpPost, encoding, new DataParse<String>() {
+            @Override
+            public String parseData(HttpEntity httpEntity, String encoding) throws IOException {
+                return EntityUtils.toString(httpEntity, encoding);
+            }
+        });
+    }
+
+}

+ 56 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/ip/AddressUtils.java

@@ -0,0 +1,56 @@
+package org.zhongzheng.common.utils.ip;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.zhongzheng.common.config.RuoYiConfig;
+import org.zhongzheng.common.constant.Constants;
+import org.zhongzheng.common.utils.http.HttpUtils;
+
+/**
+ * 获取地址类
+ * 
+ * @author zhongzheng
+ */
+public class AddressUtils
+{
+    private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
+
+    // IP地址查询
+    public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
+
+    // 未知地址
+    public static final String UNKNOWN = "XX XX";
+
+    public static String getRealAddressByIP(String ip)
+    {
+        String address = UNKNOWN;
+        // 内网不查询
+        if (IpUtils.internalIp(ip))
+        {
+            return "内网IP";
+        }
+        if (RuoYiConfig.isAddressEnabled())
+        {
+            try
+            {
+                String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK);
+                if (StrUtil.isEmpty(rspStr))
+                {
+                    log.error("获取地理位置异常 {}", ip);
+                    return UNKNOWN;
+                }
+                JSONObject obj = JSONObject.parseObject(rspStr);
+                String region = obj.getString("pro");
+                String city = obj.getString("city");
+                return String.format("%s %s", region, city);
+            }
+            catch (Exception e)
+            {
+                log.error("获取地理位置异常 {}", ip);
+            }
+        }
+        return address;
+    }
+}

+ 195 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/ip/IpUtils.java

@@ -0,0 +1,195 @@
+package org.zhongzheng.common.utils.ip;
+
+import cn.hutool.core.lang.Validator;
+import cn.hutool.http.HtmlUtil;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 获取IP方法
+ * 
+ * @author zhongzheng
+ */
+public class IpUtils
+{
+    public static String getIpAddr(HttpServletRequest request)
+    {
+        if (request == null)
+        {
+            return "unknown";
+        }
+        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("X-Forwarded-For");
+        }
+        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.getHeader("X-Real-IP");
+        }
+
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
+        {
+            ip = request.getRemoteAddr();
+        }
+        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
+    }
+
+    public static boolean internalIp(String ip)
+    {
+        byte[] addr = textToNumericFormatV4(ip);
+        return internalIp(addr) || "127.0.0.1".equals(ip);
+    }
+
+    private static boolean internalIp(byte[] addr)
+    {
+        if (Validator.isNull(addr) || addr.length < 2)
+        {
+            return true;
+        }
+        final byte b0 = addr[0];
+        final byte b1 = addr[1];
+        // 10.x.x.x/8
+        final byte SECTION_1 = 0x0A;
+        // 172.16.x.x/12
+        final byte SECTION_2 = (byte) 0xAC;
+        final byte SECTION_3 = (byte) 0x10;
+        final byte SECTION_4 = (byte) 0x1F;
+        // 192.168.x.x/16
+        final byte SECTION_5 = (byte) 0xC0;
+        final byte SECTION_6 = (byte) 0xA8;
+        switch (b0)
+        {
+            case SECTION_1:
+                return true;
+            case SECTION_2:
+                if (b1 >= SECTION_3 && b1 <= SECTION_4)
+                {
+                    return true;
+                }
+            case SECTION_5:
+                switch (b1)
+                {
+                    case SECTION_6:
+                        return true;
+                }
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 将IPv4地址转换成字节
+     * 
+     * @param text IPv4地址
+     * @return byte 字节
+     */
+    public static byte[] textToNumericFormatV4(String text)
+    {
+        if (text.length() == 0)
+        {
+            return null;
+        }
+
+        byte[] bytes = new byte[4];
+        String[] elements = text.split("\\.", -1);
+        try
+        {
+            long l;
+            int i;
+            switch (elements.length)
+            {
+                case 1:
+                    l = Long.parseLong(elements[0]);
+                    if ((l < 0L) || (l > 4294967295L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l >> 24 & 0xFF);
+                    bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 2:
+                    l = Integer.parseInt(elements[0]);
+                    if ((l < 0L) || (l > 255L)) {
+                        return null;
+                    }
+                    bytes[0] = (byte) (int) (l & 0xFF);
+                    l = Integer.parseInt(elements[1]);
+                    if ((l < 0L) || (l > 16777215L)) {
+                        return null;
+                    }
+                    bytes[1] = (byte) (int) (l >> 16 & 0xFF);
+                    bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 3:
+                    for (i = 0; i < 2; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    l = Integer.parseInt(elements[2]);
+                    if ((l < 0L) || (l > 65535L)) {
+                        return null;
+                    }
+                    bytes[2] = (byte) (int) (l >> 8 & 0xFF);
+                    bytes[3] = (byte) (int) (l & 0xFF);
+                    break;
+                case 4:
+                    for (i = 0; i < 4; ++i)
+                    {
+                        l = Integer.parseInt(elements[i]);
+                        if ((l < 0L) || (l > 255L)) {
+                            return null;
+                        }
+                        bytes[i] = (byte) (int) (l & 0xFF);
+                    }
+                    break;
+                default:
+                    return null;
+            }
+        }
+        catch (NumberFormatException e)
+        {
+            return null;
+        }
+        return bytes;
+    }
+
+    public static String getHostIp()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostAddress();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "127.0.0.1";
+    }
+
+    public static String getHostName()
+    {
+        try
+        {
+            return InetAddress.getLocalHost().getHostName();
+        }
+        catch (UnknownHostException e)
+        {
+        }
+        return "未知";
+    }
+}

+ 174 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/poi/EasyPoiUtil.java

@@ -0,0 +1,174 @@
+package org.zhongzheng.common.utils.poi;
+
+import cn.afterturn.easypoi.excel.ExcelExportUtil;
+import cn.afterturn.easypoi.excel.ExcelImportUtil;
+import cn.afterturn.easypoi.excel.entity.ExportParams;
+import cn.afterturn.easypoi.excel.entity.ImportParams;
+import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+public class EasyPoiUtil {
+    /**
+     * 功能描述:复杂导出Excel,包括文件名以及表名。创建表头
+     *
+     * @param list 导出的实体类
+     * @param title 表头名称
+     * @param sheetName sheet表名
+     * @param pojoClass 映射的实体类
+     * @param isCreateHeader 是否创建表头
+     * @param fileName
+     * @param response
+     * @return
+     */
+    public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader, HttpServletResponse response) {
+        ExportParams exportParams = new ExportParams(title, sheetName);
+        exportParams.setCreateHeadRows(isCreateHeader);
+        defaultExport(list, pojoClass, fileName, response, exportParams);
+    }
+
+
+    /**
+     * 功能描述:复杂导出Excel,包括文件名以及表名,不创建表头
+     *
+     * @param list 导出的实体类
+     * @param title 表头名称
+     * @param sheetName sheet表名
+     * @param pojoClass 映射的实体类
+     * @param fileName
+     * @param response
+     * @return
+     */
+    public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) {
+        defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName));
+    }
+
+    /**
+     * 功能描述:Map 集合导出
+     *
+     * @param list 实体集合
+     * @param fileName 导出的文件名称
+     * @param response
+     * @return
+     */
+    public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
+        defaultExport(list, fileName, response);
+    }
+
+    /**
+     * 功能描述:默认导出方法
+     *
+     * @param list 导出的实体集合
+     * @param fileName 导出的文件名
+     * @param pojoClass pojo实体
+     * @param exportParams ExportParams封装实体
+     * @param response
+     * @return
+     */
+    private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) {
+        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
+        if (workbook != null) {
+            downLoadExcel(fileName, response, workbook);
+        }
+    }
+
+    /**
+     * 功能描述:Excel导出
+     *
+     * @param fileName 文件名称
+     * @param response
+     * @param workbook Excel对象
+     * @return
+     */
+    private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) {
+        try {
+            response.setCharacterEncoding("UTF-8");
+            response.setHeader("content-Type", "application/vnd.ms-excel");
+            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
+            workbook.write(response.getOutputStream());
+        } catch (IOException e) {
+            throw new  RuntimeException(e);
+        }
+    }
+
+    /**
+     * 功能描述:默认导出方法
+     *
+     * @param list 导出的实体集合
+     * @param fileName 导出的文件名
+     * @param response
+     * @return
+     */
+    private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) {
+        Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);
+        if (workbook != null) ;
+        downLoadExcel(fileName, response, workbook);
+    }
+
+
+    /**
+     * 功能描述:根据文件路径来导入Excel
+     *
+     * @param filePath 文件路径
+     * @param titleRows 表标题的行数
+     * @param headerRows 表头行数
+     * @param pojoClass Excel实体类
+     * @return
+     */
+    public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
+        //判断文件是否存在
+        if (StringUtils.isBlank(filePath)) {
+            return null;
+        }
+        ImportParams params = new ImportParams();
+        params.setTitleRows(titleRows);
+        params.setHeadRows(headerRows);
+        List<T> list = null;
+        try {
+            list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
+        } catch (NoSuchElementException e) {
+            throw new RuntimeException("模板不能为空");
+        } catch (Exception e) {
+            e.printStackTrace();
+
+        }
+        return list;
+    }
+
+    /**
+     * 功能描述:根据接收的Excel文件来导入Excel,并封装成实体类
+     *
+     * @param file 上传的文件
+     * @param titleRows 表标题的行数
+     * @param headerRows 表头行数
+     * @param pojoClass Excel实体类
+     * @return
+     */
+    public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) {
+        if (file == null) {
+            return null;
+        }
+        ImportParams params = new ImportParams();
+        params.setTitleRows(titleRows);
+        params.setHeadRows(headerRows);
+        List<T> list = null;
+        try {
+            list = ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params);
+        } catch (NoSuchElementException e) {
+            throw new RuntimeException("excel文件不能为空");
+        } catch (Exception e) {
+            throw new RuntimeException("导入的文件不正确,请重新导入!");
+
+        }
+        return list;
+    }
+}

+ 1478 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/poi/ExcelUtil.java

@@ -0,0 +1,1478 @@
+package org.zhongzheng.common.utils.poi;
+
+import cn.afterturn.easypoi.excel.ExcelExportUtil;
+import cn.afterturn.easypoi.excel.entity.ExportParams;
+import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import com.zhongzheng.common.annotation.Excel;
+import com.zhongzheng.common.annotation.Excel.ColumnType;
+import com.zhongzheng.common.annotation.Excel.Type;
+import com.zhongzheng.common.annotation.Excels;
+import com.zhongzheng.common.config.RuoYiConfig;
+import com.zhongzheng.common.core.domain.AjaxResult;
+import com.zhongzheng.common.exception.CustomException;
+import com.zhongzheng.common.utils.DateUtils;
+import com.zhongzheng.common.utils.DictUtils;
+import com.zhongzheng.common.utils.file.FileTypeUtils;
+import com.zhongzheng.common.utils.file.ImageUtils;
+import com.zhongzheng.common.utils.reflect.ReflectUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Excel相关处理
+ *
+ * @author zhongzheng
+ */
+public class ExcelUtil<T>
+{
+    private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+
+    /**
+     * Excel sheet最大行数,默认65536
+     */
+    public static final int sheetSize = 65536;
+
+    /**
+     * 工作表名称
+     */
+    private String sheetName;
+
+    /**
+     * 导出类型(EXPORT:导出数据;IMPORT:导入模板)
+     */
+    private Type type;
+
+    /**
+     * 工作薄对象
+     */
+    private Workbook wb;
+
+    /**
+     * 工作表对象
+     */
+    private Sheet sheet;
+
+    /**
+     * 样式列表
+     */
+    private Map<String, CellStyle> styles;
+
+    /**
+     * 导入导出数据列表
+     */
+    private List<T> list;
+
+    /**
+     * 注解列表
+     */
+    private List<Object[]> fields;
+
+    /**
+     * 最大高度
+     */
+    private short maxHeight;
+
+    /**
+     * 统计列表
+     */
+    private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
+
+    /**
+     * 数字格式
+     */
+    private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
+
+    /**
+     * 实体对象
+     */
+    public Class<T> clazz;
+
+    public ExcelUtil(Class<T> clazz)
+    {
+        this.clazz = clazz;
+    }
+
+    public void init(List<T> list, String sheetName, Type type)
+    {
+        if (list == null)
+        {
+            list = new ArrayList<T>();
+        }
+        this.list = list;
+        this.sheetName = sheetName;
+        this.type = type;
+        createExcelField();
+        createWorkbook();
+    }
+
+    /**
+     * 对excel表单默认第一个索引名转换成list
+     *
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> importExcel(InputStream is) throws Exception
+    {
+        return importExcel(StrUtil.EMPTY, is);
+    }
+
+    /**
+     * 对excel表单指定表格索引名转换成list
+     *
+     * @param sheetName 表格索引名
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> importExcel(String sheetName, InputStream is) throws Exception
+    {
+        this.type = Type.IMPORT;
+        this.wb = WorkbookFactory.create(is);
+        List<T> list = new ArrayList<T>();
+        Sheet sheet = null;
+        if (Validator.isNotEmpty(sheetName))
+        {
+            // 如果指定sheet名,则取指定sheet中的内容.
+            sheet = wb.getSheet(sheetName);
+        }
+        else
+        {
+            // 如果传入的sheet名不存在则默认指向第1个sheet.
+            sheet = wb.getSheetAt(0);
+        }
+
+        if (sheet == null)
+        {
+            throw new IOException("文件sheet不存在");
+        }
+
+        int rows = sheet.getPhysicalNumberOfRows();
+
+        if (rows > 0)
+        {
+            // 定义一个map用于存放excel列的序号和field.
+            Map<String, Integer> cellMap = new HashMap<String, Integer>();
+            // 获取表头
+            Row heard = sheet.getRow(0);
+            for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
+            {
+                Cell cell = heard.getCell(i);
+                if (Validator.isNotNull(cell))
+                {
+                    String value = this.getCellValue(heard, i).toString();
+                    cellMap.put(value, i);
+                }
+                else
+                {
+                    cellMap.put(null, i);
+                }
+            }
+            // 有数据时才处理 得到类的所有field.
+            Field[] allFields = clazz.getDeclaredFields();
+            // 定义一个map用于存放列的序号和field.
+            Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();
+            for (int col = 0; col < allFields.length; col++)
+            {
+                Field field = allFields[col];
+                Excel attr = field.getAnnotation(Excel.class);
+                if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+                {
+                    // 设置类的私有字段属性可访问.
+                    field.setAccessible(true);
+                    Integer column = cellMap.get(attr.name());
+                    if (column != null)
+                    {
+                        fieldsMap.put(column, field);
+                    }
+                }
+            }
+            for (int i = 1; i < rows; i++)
+            {
+                // 从第2行开始取数据,默认第一行是表头.
+                Row row = sheet.getRow(i);
+                if(row == null)
+                {
+                    continue;
+                }
+                T entity = null;
+                for (Map.Entry<Integer, Field> entry : fieldsMap.entrySet())
+                {
+                    Object val = this.getCellValue(row, entry.getKey());
+
+                    // 如果不存在实例则新建.
+                    entity = (entity == null ? clazz.newInstance() : entity);
+                    // 从map中得到对应列的field.
+                    Field field = fieldsMap.get(entry.getKey());
+                    // 取得类型,并根据对象类型设置值.
+                    Class<?> fieldType = field.getType();
+                    if (String.class == fieldType)
+                    {
+                        String s = Convert.toStr(val);
+                        if (StrUtil.endWith(s, ".0"))
+                        {
+                        //    val = StrUtil.subBefore(s, ".0",false);
+                        }
+                        else
+                        {
+                            if (val instanceof Double)
+                            {
+                                String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                                if (Validator.isNotEmpty(dateFormat))
+                                {
+                                    val = DateUtils.parseDateToStr(dateFormat, (Date) val);
+                                }
+                                else
+                                {
+                                    val = Convert.toStr(val);
+                                }
+                            }
+                            if (val instanceof Date)
+                            {
+                                String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                                if (Validator.isNotEmpty(dateFormat))
+                                {
+                                    val = DateUtils.parseDateToStr(dateFormat, (Date) val);
+                                }
+                                else
+                                {
+                                    val = Convert.toStr(val);
+                                }
+                            }
+
+                        }
+                    }
+                    else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && Validator.isNumber(Convert.toStr(val)))
+                    {
+                        val = Convert.toInt(val);
+                    }
+                    else if (Long.TYPE == fieldType || Long.class == fieldType)
+                    {
+                        val = Convert.toLong(val);
+                    }
+                    else if (Double.TYPE == fieldType || Double.class == fieldType)
+                    {
+                        val = Convert.toDouble(val);
+                    }
+                    else if (Float.TYPE == fieldType || Float.class == fieldType)
+                    {
+                        val = Convert.toFloat(val);
+                    }
+                    else if (BigDecimal.class == fieldType)
+                    {
+                        val = Convert.toBigDecimal(val);
+                    }
+                    else if (Date.class == fieldType)
+                    {
+                        if (val instanceof String)
+                        {
+                            val = DateUtils.parseDate(val);
+                        }
+                        else if (val instanceof Double)
+                        {
+                            val = DateUtil.getJavaDate((Double) val);
+                        }
+                    }
+                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
+                    {
+                        val = Convert.toBool(val, false);
+                    }
+                    if (Validator.isNotNull(fieldType))
+                    {
+                        Excel attr = field.getAnnotation(Excel.class);
+                        String propertyName = field.getName();
+                        if (Validator.isNotEmpty(attr.targetAttr()))
+                        {
+                            propertyName = field.getName() + "." + attr.targetAttr();
+                        }
+                        else if (Validator.isNotEmpty(attr.readConverterExp()))
+                        {
+                            val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
+                        }
+                        else if (Validator.isNotEmpty(attr.dictType()))
+                        {
+                            val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
+                        }
+                        ReflectUtils.invokeSetter(entity, propertyName, val);
+                    }
+                }
+                list.add(entity);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 获取七大员指定表格考试时间
+     *
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public String examApplyTimeAnalysis(InputStream is,Integer index) throws Exception
+    {
+        this.type = Type.IMPORT;
+        this.wb = WorkbookFactory.create(is);
+        Sheet sheet = wb.getSheetAt(0);
+        if (sheet == null)
+        {
+            throw new IOException("文件sheet不存在");
+        }
+        // 获取表头
+        Row heard = sheet.getRow(index);
+        Iterator<Cell> cellIterator = heard.cellIterator();
+        List<String> dataList = new ArrayList<>();
+        while(cellIterator.hasNext()) {
+            Cell next = cellIterator.next();
+            String stringCellValue = next.getStringCellValue();
+            dataList.add(stringCellValue);
+        }
+        return dataList.get(dataList.size() -1);
+    }
+    /**
+     * 七大员考试回执方法
+     *
+     * @param sheetName 表格索引名
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public List<T> examApplyImportExcel(String sheetName, InputStream is) throws Exception
+    {
+        this.type = Type.IMPORT;
+        this.wb = WorkbookFactory.create(is);
+        List<T> list = new ArrayList<T>();
+        Sheet sheet = null;
+        if (Validator.isNotEmpty(sheetName))
+        {
+            // 如果指定sheet名,则取指定sheet中的内容.
+            sheet = wb.getSheet(sheetName);
+        }
+        else
+        {
+            // 如果传入的sheet名不存在则默认指向第1个sheet.
+            sheet = wb.getSheetAt(0);
+        }
+
+        if (sheet == null)
+        {
+            throw new IOException("文件sheet不存在");
+        }
+
+        int rows = sheet.getPhysicalNumberOfRows();
+
+        if (rows > 0)
+        {
+            // 定义一个map用于存放excel列的序号和field.
+            Map<String, Integer> cellMap = new HashMap<String, Integer>();
+            // 第2行是表头
+            Row heard = sheet.getRow(2);
+            for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
+            {
+                Cell cell = heard.getCell(i);
+                if (Validator.isNotNull(cell))
+                {
+                    String value = this.getCellValue(heard, i).toString();
+                    cellMap.put(value, i);
+                }
+                else
+                {
+                    cellMap.put(null, i);
+                }
+            }
+            // 有数据时才处理 得到类的所有field.
+            Field[] allFields = clazz.getDeclaredFields();
+            // 定义一个map用于存放列的序号和field.
+            Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();
+            for (int col = 0; col < allFields.length; col++)
+            {
+                Field field = allFields[col];
+                Excel attr = field.getAnnotation(Excel.class);
+                if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+                {
+                    // 设置类的私有字段属性可访问.
+                    field.setAccessible(true);
+                    Integer column = cellMap.get(attr.name());
+                    if (column != null)
+                    {
+                        fieldsMap.put(column, field);
+                    }
+                }
+            }
+            for (int i = 3; i < rows; i++)
+            {
+                // 从第3行开始取数据,第2行是表头.
+                Row row = sheet.getRow(i);
+                if(row == null)
+                {
+                    continue;
+                }
+                T entity = null;
+                for (Map.Entry<Integer, Field> entry : fieldsMap.entrySet())
+                {
+                    Object val = this.getCellValue(row, entry.getKey());
+
+                    // 如果不存在实例则新建.
+                    entity = (entity == null ? clazz.newInstance() : entity);
+                    // 从map中得到对应列的field.
+                    Field field = fieldsMap.get(entry.getKey());
+                    // 取得类型,并根据对象类型设置值.
+                    Class<?> fieldType = field.getType();
+                    if (String.class == fieldType)
+                    {
+                        String s = Convert.toStr(val);
+                        if (StrUtil.endWith(s, ".0"))
+                        {
+                            //    val = StrUtil.subBefore(s, ".0",false);
+                        }
+                        else
+                        {
+                            if (val instanceof Double)
+                            {
+                                String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                                if (Validator.isNotEmpty(dateFormat))
+                                {
+                                    val = DateUtils.parseDateToStr(dateFormat, (Date) val);
+                                }
+                                else
+                                {
+                                    val = Convert.toStr(val);
+                                }
+                            }
+                            if (val instanceof Date)
+                            {
+                                String dateFormat = field.getAnnotation(Excel.class).dateFormat();
+                                if (Validator.isNotEmpty(dateFormat))
+                                {
+                                    val = DateUtils.parseDateToStr(dateFormat, (Date) val);
+                                }
+                                else
+                                {
+                                    val = Convert.toStr(val);
+                                }
+                            }
+
+                        }
+                    }
+                    else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && Validator.isNumber(Convert.toStr(val)))
+                    {
+                        val = Convert.toInt(val);
+                    }
+                    else if (Long.TYPE == fieldType || Long.class == fieldType)
+                    {
+                        val = Convert.toLong(val);
+                    }
+                    else if (Double.TYPE == fieldType || Double.class == fieldType)
+                    {
+                        val = Convert.toDouble(val);
+                    }
+                    else if (Float.TYPE == fieldType || Float.class == fieldType)
+                    {
+                        val = Convert.toFloat(val);
+                    }
+                    else if (BigDecimal.class == fieldType)
+                    {
+                        val = Convert.toBigDecimal(val);
+                    }
+                    else if (Date.class == fieldType)
+                    {
+                        if (val instanceof String)
+                        {
+                            val = DateUtils.parseDate(val);
+                        }
+                        else if (val instanceof Double)
+                        {
+                            val = DateUtil.getJavaDate((Double) val);
+                        }
+                    }
+                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
+                    {
+                        val = Convert.toBool(val, false);
+                    }
+                    if (Validator.isNotNull(fieldType))
+                    {
+                        Excel attr = field.getAnnotation(Excel.class);
+                        String propertyName = field.getName();
+                        if (Validator.isNotEmpty(attr.targetAttr()))
+                        {
+                            propertyName = field.getName() + "." + attr.targetAttr();
+                        }
+                        else if (Validator.isNotEmpty(attr.readConverterExp()))
+                        {
+                            val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
+                        }
+                        else if (Validator.isNotEmpty(attr.dictType()))
+                        {
+                            val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
+                        }
+                        ReflectUtils.invokeSetter(entity, propertyName, val);
+                    }
+                }
+                list.add(entity);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     *
+     * @param list 导出数据集合
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public AjaxResult exportExcel(List<T> list, String sheetName)
+    {
+        this.init(list, sheetName, Type.EXPORT);
+        return exportExcel();
+    }
+
+    public List<Map<String, Object>> exportEasyData(List<T> list)
+    {
+        List<Map<String, Object>> sheetsList = new ArrayList<>();
+        ExportParams deptExportParams = new ExportParams();
+        deptExportParams.setSheetName("表1");
+        Map<String, Object> deptExportMap = new HashMap<>();
+        deptExportMap.put("title", deptExportParams);
+        deptExportMap.put("entity", clazz);
+        // sheet中要填充得数据
+        deptExportMap.put("data", list);
+        sheetsList.add(deptExportMap);
+        return sheetsList;
+    }
+
+    public String userProfileExport(String filePath, String sheetName)
+    {
+        OutputStream out = null;
+        try
+        {
+            FileInputStream inputStream = new FileInputStream(filePath);
+            String filename = encodingName(sheetName);
+            out = new FileOutputStream(getAbsoluteFile(filename));
+            byte[] buffer = new byte[1024];
+            int len = 0;
+            while ((len = inputStream.read(buffer)) != -1) {
+                out.write(buffer, 0, len);
+            }
+            return filename;
+        }
+        catch (Exception e)
+        {
+            log.error("导出学员资料异常{}", e.getMessage());
+            throw new CustomException("导出学员资料异常,请联系网站管理员!");
+        }
+        finally
+        {
+
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+
+    public AjaxResult exportEasyExcel(List<Map<String, Object>> sheetsList, String sheetName)
+    {
+        OutputStream out = null;
+   //     Workbook workbook = null;
+        try
+        {
+            Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
+            String filename = encodingXLSFilename(sheetName);
+            out = new FileOutputStream(getAbsoluteFile(filename));
+            workbook.write(out);
+            return AjaxResult.success(filename);
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel异常{}", e.getMessage());
+            throw new CustomException("导出Excel失败,请联系网站管理员!");
+        }
+        finally
+        {
+
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+
+
+    public void exportEasyExcelStudy(List<Map<String, Object>> sheetsList,String path)
+    {
+        OutputStream out = null;
+        //     Workbook workbook = null;
+        try
+        {
+            Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
+            File desc = new File(path);
+            if (!desc.getParentFile().exists())
+            {
+                desc.getParentFile().mkdirs();
+            }
+            out = new FileOutputStream(path);
+            workbook.write(out);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            log.error("导出Excel异常{}", e.getMessage());
+            throw new CustomException("导出Excel失败,请联系网站管理员!");
+        }
+        finally
+        {
+
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     *
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public AjaxResult importTemplateExcel(String sheetName)
+    {
+        this.init(null, sheetName, Type.IMPORT);
+        return exportExcel();
+    }
+
+    /**
+     * 对list数据源将其里面的数据导入到excel表单
+     *
+     * @return 结果
+     */
+    public AjaxResult exportExcel()
+    {
+        OutputStream out = null;
+        try
+        {
+            // 取出一共有多少个sheet.
+            double sheetNo = Math.ceil(list.size() / sheetSize);
+            for (int index = 0; index <= sheetNo; index++)
+            {
+                createSheet(sheetNo, index);
+
+                // 产生一行
+                Row row = sheet.createRow(0);
+                int column = 0;
+                // 写入各个字段的列头名称
+                for (Object[] os : fields)
+                {
+                    Excel excel = (Excel) os[1];
+                    this.createCell(excel, row, column++);
+                }
+                if (Type.EXPORT.equals(type))
+                {
+                    fillExcelData(index, row);
+                    addStatisticsRow();
+                }
+            }
+            String filename = encodingFilename(sheetName);
+            out = new FileOutputStream(getAbsoluteFile(filename));
+            wb.write(out);
+            return AjaxResult.success(filename);
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel异常{}", e.getMessage());
+            throw new CustomException("导出Excel失败,请联系网站管理员!");
+        }
+        finally
+        {
+            if (wb != null)
+            {
+                try
+                {
+                    wb.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+            if (out != null)
+            {
+                try
+                {
+                    out.close();
+                }
+                catch (IOException e1)
+                {
+                    e1.printStackTrace();
+                }
+            }
+        }
+    }
+
+    /**
+     * 填充excel数据
+     *
+     * @param index 序号
+     * @param row 单元格行
+     */
+    public void fillExcelData(int index, Row row)
+    {
+        int startNo = index * sheetSize;
+        int endNo = Math.min(startNo + sheetSize, list.size());
+        for (int i = startNo; i < endNo; i++)
+        {
+            row = sheet.createRow(i + 1 - startNo);
+            // 得到导出对象.
+            T vo = (T) list.get(i);
+            int column = 0;
+            for (Object[] os : fields)
+            {
+                Field field = (Field) os[0];
+                Excel excel = (Excel) os[1];
+                // 设置实体类私有属性可访问
+                field.setAccessible(true);
+                this.addCell(excel, row, vo, field, column++);
+            }
+        }
+    }
+
+    /**
+     * 创建表格样式
+     *
+     * @param wb 工作薄对象
+     * @return 样式列表
+     */
+    private Map<String, CellStyle> createStyles(Workbook wb)
+    {
+        // 写入各条记录,每条记录对应excel表中的一行
+        Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
+        CellStyle style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderTop(BorderStyle.THIN);
+        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        Font dataFont = wb.createFont();
+        dataFont.setFontName("Arial");
+        dataFont.setFontHeightInPoints((short) 10);
+        style.setFont(dataFont);
+        styles.put("data", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        Font headerFont = wb.createFont();
+        headerFont.setFontName("Arial");
+        headerFont.setFontHeightInPoints((short) 10);
+        headerFont.setBold(true);
+        headerFont.setColor(IndexedColors.WHITE.getIndex());
+        style.setFont(headerFont);
+        styles.put("header", style);
+
+        style = wb.createCellStyle();
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        Font totalFont = wb.createFont();
+        totalFont.setFontName("Arial");
+        totalFont.setFontHeightInPoints((short) 10);
+        style.setFont(totalFont);
+        styles.put("total", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.LEFT);
+        styles.put("data1", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.CENTER);
+        styles.put("data2", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(HorizontalAlignment.RIGHT);
+        styles.put("data3", style);
+
+        return styles;
+    }
+
+    /**
+     * 创建单元格
+     */
+    public Cell createCell(Excel attr, Row row, int column)
+    {
+        // 创建列
+        Cell cell = row.createCell(column);
+        // 写入列信息
+        cell.setCellValue(attr.name());
+        setDataValidation(attr, row, column);
+        cell.setCellStyle(styles.get("header"));
+        return cell;
+    }
+
+    /**
+     * 设置单元格信息
+     *
+     * @param value 单元格值
+     * @param attr 注解相关
+     * @param cell 单元格信息
+     */
+    public void setCellVo(Object value, Excel attr, Cell cell)
+    {
+        if (ColumnType.STRING == attr.cellType())
+        {
+            cell.setCellValue(Validator.isNull(value) ? attr.defaultValue() : value + attr.suffix());
+        }
+        else if (ColumnType.NUMERIC == attr.cellType())
+        {
+            if (Validator.isNotNull(value))
+            {
+                cell.setCellValue(StrUtil.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value));
+            }
+        }
+        else if (ColumnType.IMAGE == attr.cellType())
+        {
+            ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1),
+                    cell.getRow().getRowNum() + 1);
+            String imagePath = Convert.toStr(value);
+            if (Validator.isNotEmpty(imagePath))
+            {
+                byte[] data = ImageUtils.getImage(imagePath);
+                getDrawingPatriarch(cell.getSheet()).createPicture(anchor,
+                        cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));
+            }
+        }
+    }
+
+    /**
+     * 获取画布
+     */
+    public static Drawing<?> getDrawingPatriarch(Sheet sheet)
+    {
+        if (sheet.getDrawingPatriarch() == null)
+        {
+            sheet.createDrawingPatriarch();
+        }
+        return sheet.getDrawingPatriarch();
+    }
+
+    /**
+     * 获取图片类型,设置图片插入类型
+     */
+    public int getImageType(byte[] value)
+    {
+        String type = FileTypeUtils.getFileExtendName(value);
+        if ("JPG".equalsIgnoreCase(type))
+        {
+            return Workbook.PICTURE_TYPE_JPEG;
+        }
+        else if ("PNG".equalsIgnoreCase(type))
+        {
+            return Workbook.PICTURE_TYPE_PNG;
+        }
+        return Workbook.PICTURE_TYPE_JPEG;
+    }
+
+    /**
+     * 创建表格样式
+     */
+    public void setDataValidation(Excel attr, Row row, int column)
+    {
+        if (attr.name().indexOf("注:") >= 0)
+        {
+            sheet.setColumnWidth(column, 6000);
+        }
+        else
+        {
+            // 设置列宽
+            sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));
+        }
+        // 如果设置了提示信息则鼠标放上去提示.
+        if (Validator.isNotEmpty(attr.prompt()))
+        {
+            // 这里默认设了2-101列提示.
+            setXSSFPrompt(sheet, "", attr.prompt(), 1, 100, column, column);
+        }
+        // 如果设置了combo属性则本列只能选择不能输入
+        if (attr.combo().length > 0)
+        {
+            // 这里默认设了2-101列只能选择不能输入.
+            setXSSFValidation(sheet, attr.combo(), 1, 100, column, column);
+        }
+    }
+
+    /**
+     * 添加单元格
+     */
+    public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
+    {
+        Cell cell = null;
+        try
+        {
+            // 设置行高
+            row.setHeight(maxHeight);
+            // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
+            if (attr.isExport())
+            {
+                // 创建cell
+                cell = row.createCell(column);
+                int align = attr.align().value();
+                cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : "")));
+
+                // 用于读取对象中的属性
+                Object value = getTargetValue(vo, field, attr);
+                String dateFormat = attr.dateFormat();
+                String readConverterExp = attr.readConverterExp();
+                String separator = attr.separator();
+                String dictType = attr.dictType();
+                if (Validator.isNotEmpty(dateFormat) && Validator.isNotNull(value))
+                {
+                    cell.setCellValue(DateUtils.parseDateToStr(dateFormat, (Date) value));
+                }
+                else if (Validator.isNotEmpty(readConverterExp) && Validator.isNotNull(value))
+                {
+                    cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator));
+                }
+                else if (Validator.isNotEmpty(dictType) && Validator.isNotNull(value))
+                {
+                    cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator));
+                }
+                else if (value instanceof BigDecimal && -1 != attr.scale())
+                {
+                    cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).toString());
+                }
+                else
+                {
+                    // 设置列类型
+                    setCellVo(value, attr, cell);
+                }
+                addStatisticsData(column, Convert.toStr(value), attr);
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("导出Excel失败{}", e);
+        }
+        return cell;
+    }
+
+    /**
+     * 设置 POI XSSFSheet 单元格提示
+     *
+     * @param sheet 表单
+     * @param promptTitle 提示标题
+     * @param promptContent 提示内容
+     * @param firstRow 开始行
+     * @param endRow 结束行
+     * @param firstCol 开始列
+     * @param endCol 结束列
+     */
+    public void setXSSFPrompt(Sheet sheet, String promptTitle, String promptContent, int firstRow, int endRow,
+            int firstCol, int endCol)
+    {
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        DataValidationConstraint constraint = helper.createCustomConstraint("DD1");
+        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        dataValidation.createPromptBox(promptTitle, promptContent);
+        dataValidation.setShowPromptBox(true);
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 设置某些列的值只能输入预制的数据,显示下拉框.
+     *
+     * @param sheet 要设置的sheet.
+     * @param textlist 下拉框显示的内容
+     * @param firstRow 开始行
+     * @param endRow 结束行
+     * @param firstCol 开始列
+     * @param endCol 结束列
+     * @return 设置好的sheet.
+     */
+    public void setXSSFValidation(Sheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol)
+    {
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        // 加载下拉列表内容
+        DataValidationConstraint constraint = helper.createExplicitListConstraint(textlist);
+        // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
+        CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
+        // 数据有效性对象
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        // 处理Excel兼容性问题
+        if (dataValidation instanceof XSSFDataValidation)
+        {
+            dataValidation.setSuppressDropDownArrow(true);
+            dataValidation.setShowErrorBox(true);
+        }
+        else
+        {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * 解析导出值 0=男,1=女,2=未知
+     *
+     * @param propertyValue 参数值
+     * @param converterExp 翻译注解
+     * @param separator 分隔符
+     * @return 解析后值
+     */
+    public static String convertByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            if (StrUtil.containsAny(separator, propertyValue))
+            {
+                for (String value : propertyValue.split(separator))
+                {
+                    if (itemArray[0].equals(value))
+                    {
+                        propertyString.append(itemArray[1] + separator);
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                if (itemArray[0].equals(propertyValue))
+                {
+                    return itemArray[1];
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null,separator);
+    }
+
+    /**
+     * 反向解析值 男=0,女=1,未知=2
+     *
+     * @param propertyValue 参数值
+     * @param converterExp 翻译注解
+     * @param separator 分隔符
+     * @return 解析后值
+     */
+    public static String reverseByExp(String propertyValue, String converterExp, String separator)
+    {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(",");
+        for (String item : convertSource)
+        {
+            String[] itemArray = item.split("=");
+            if (StrUtil.containsAny(separator, propertyValue))
+            {
+                for (String value : propertyValue.split(separator))
+                {
+                    if (itemArray[1].equals(value))
+                    {
+                        propertyString.append(itemArray[0] + separator);
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                if (itemArray[1].equals(propertyValue))
+                {
+                    return itemArray[0];
+                }
+            }
+        }
+        return StrUtil.strip(propertyString.toString(), null,separator);
+    }
+
+    /**
+     * 解析字典值
+     *
+     * @param dictValue 字典值
+     * @param dictType 字典类型
+     * @param separator 分隔符
+     * @return 字典标签
+     */
+    public static String convertDictByExp(String dictValue, String dictType, String separator)
+    {
+        return DictUtils.getDictLabel(dictType, dictValue, separator);
+    }
+
+    /**
+     * 反向解析值字典值
+     *
+     * @param dictLabel 字典标签
+     * @param dictType 字典类型
+     * @param separator 分隔符
+     * @return 字典值
+     */
+    public static String reverseDictByExp(String dictLabel, String dictType, String separator)
+    {
+        return DictUtils.getDictValue(dictType, dictLabel, separator);
+    }
+
+    /**
+     * 合计统计信息
+     */
+    private void addStatisticsData(Integer index, String text, Excel entity)
+    {
+        if (entity != null && entity.isStatistics())
+        {
+            Double temp = 0D;
+            if (!statistics.containsKey(index))
+            {
+                statistics.put(index, temp);
+            }
+            try
+            {
+                temp = Double.valueOf(text);
+            }
+            catch (NumberFormatException e)
+            {
+            }
+            statistics.put(index, statistics.get(index) + temp);
+        }
+    }
+
+    /**
+     * 创建统计行
+     */
+    public void addStatisticsRow()
+    {
+        if (statistics.size() > 0)
+        {
+            Cell cell = null;
+            Row row = sheet.createRow(sheet.getLastRowNum() + 1);
+            Set<Integer> keys = statistics.keySet();
+            cell = row.createCell(0);
+            cell.setCellStyle(styles.get("total"));
+            cell.setCellValue("合计");
+
+            for (Integer key : keys)
+            {
+                cell = row.createCell(key);
+                cell.setCellStyle(styles.get("total"));
+                cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
+            }
+            statistics.clear();
+        }
+    }
+
+    /**
+     * 编码文件名
+     */
+    public String encodingXLSFilename(String filename)
+    {
+        filename = UUID.randomUUID().toString() + "_" + filename + ".xls";
+        return filename;
+    }
+
+    /**
+     * 编码文件名
+     */
+    public String encodingName(String filename)
+    {
+        filename = UUID.randomUUID().toString() + "_" + filename;
+        return filename;
+    }
+
+    public String encodingFilename(String filename)
+    {
+        filename = UUID.randomUUID().toString() + "_" + filename + ".xlsx";
+        return filename;
+    }
+
+    /**
+     * 获取下载路径
+     *
+     * @param filename 文件名称
+     */
+    public String getAbsoluteFile(String filename)
+    {
+        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
+        File desc = new File(downloadPath);
+        if (!desc.getParentFile().exists())
+        {
+            desc.getParentFile().mkdirs();
+        }
+        return downloadPath;
+    }
+
+    /**
+     * 获取bean中的属性值
+     *
+     * @param vo 实体对象
+     * @param field 字段
+     * @param excel 注解
+     * @return 最终的属性值
+     * @throws Exception
+     */
+    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception
+    {
+        Object o = field.get(vo);
+        if (Validator.isNotEmpty(excel.targetAttr()))
+        {
+            String target = excel.targetAttr();
+            if (target.contains("."))
+            {
+                String[] targets = target.split("[.]");
+                for (String name : targets)
+                {
+                    o = getValue(o, name);
+                }
+            }
+            else
+            {
+                o = getValue(o, target);
+            }
+        }
+        return o;
+    }
+
+    /**
+     * 以类的属性的get方法方法形式获取值
+     *
+     * @param o
+     * @param name
+     * @return value
+     * @throws Exception
+     */
+    private Object getValue(Object o, String name) throws Exception
+    {
+        if (Validator.isNotNull(o) && Validator.isNotEmpty(name))
+        {
+            Class<?> clazz = o.getClass();
+            Field field = clazz.getDeclaredField(name);
+            field.setAccessible(true);
+            o = field.get(o);
+        }
+        return o;
+    }
+
+    /**
+     * 得到所有定义字段
+     */
+    private void createExcelField()
+    {
+        this.fields = new ArrayList<Object[]>();
+        List<Field> tempFields = new ArrayList<>();
+        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
+        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+        for (Field field : tempFields)
+        {
+            // 单注解
+            if (field.isAnnotationPresent(Excel.class))
+            {
+                putToField(field, field.getAnnotation(Excel.class));
+            }
+
+            // 多注解
+            if (field.isAnnotationPresent(Excels.class))
+            {
+                Excels attrs = field.getAnnotation(Excels.class);
+                Excel[] excels = attrs.value();
+                for (Excel excel : excels)
+                {
+                    putToField(field, excel);
+                }
+            }
+        }
+        this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
+        this.maxHeight = getRowHeight();
+    }
+
+    /**
+     * 根据注解获取最大行高
+     */
+    public short getRowHeight()
+    {
+        double maxHeight = 0;
+        for (Object[] os : this.fields)
+        {
+            Excel excel = (Excel) os[1];
+            maxHeight = maxHeight > excel.height() ? maxHeight : excel.height();
+        }
+        return (short) (maxHeight * 20);
+    }
+
+    /**
+     * 放到字段集合中
+     */
+    private void putToField(Field field, Excel attr)
+    {
+        if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
+        {
+            this.fields.add(new Object[] { field, attr });
+        }
+    }
+
+    /**
+     * 创建一个工作簿
+     */
+    public void createWorkbook()
+    {
+        this.wb = new SXSSFWorkbook(500);
+    }
+
+    /**
+     * 创建工作表
+     *
+     * @param sheetNo sheet数量
+     * @param index 序号
+     */
+    public void createSheet(double sheetNo, int index)
+    {
+        this.sheet = wb.createSheet();
+        this.styles = createStyles(wb);
+        // 设置工作表的名称.
+        if (sheetNo == 0)
+        {
+            wb.setSheetName(index, sheetName);
+        }
+        else
+        {
+            wb.setSheetName(index, sheetName + index);
+        }
+    }
+
+    /**
+     * 获取单元格值
+     *
+     * @param row 获取的行
+     * @param column 获取单元格列号
+     * @return 单元格值
+     */
+    public Object getCellValue(Row row, int column)
+    {
+        if (row == null)
+        {
+            return row;
+        }
+        Object val = "";
+        try
+        {
+            Cell cell = row.getCell(column);
+            if (Validator.isNotNull(cell))
+            {
+                if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA)
+                {
+                    val = cell.getNumericCellValue();
+                    if (DateUtil.isCellDateFormatted(cell))
+                    {
+                       /* SimpleDateFormat sdf  = new SimpleDateFormat("yyyy/MM/dd");
+                        Date date = DateUtil.getJavaDate((Double) val);// POI Excel 日期格式转换
+                        val = sdf.format(date);*/
+                        val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换
+                    }
+                    else
+                    {
+                        if ((Double) val % 1 != 0)
+                        {
+                            val = new BigDecimal(val.toString());
+                        }
+                        else
+                        {
+                            val = new DecimalFormat("0").format(val);
+                        }
+                    }
+                }
+                else if (cell.getCellType() == CellType.STRING)
+                {
+                    val = cell.getStringCellValue();
+                }
+                else if (cell.getCellType() == CellType.BOOLEAN)
+                {
+                    val = cell.getBooleanCellValue();
+                }
+                else if (cell.getCellType() == CellType.ERROR)
+                {
+                    val = cell.getErrorCellValue();
+                }
+
+            }
+        }
+        catch (Exception e)
+        {
+            return val;
+        }
+        return val;
+    }
+}

+ 137 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/polyv/PolyvUtils.java

@@ -0,0 +1,137 @@
+package org.zhongzheng.common.utils.polyv;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 保利威通用调用接口
+ *
+ * @author change
+ */
+public class PolyvUtils {
+
+    private static final String UTF8 = "UTF-8";
+
+/*    public static void main(String[] args) throws Exception {
+
+        Long ptime = System.currentTimeMillis();
+        Map<String,String> map = new HashMap<>();
+        map.put("vid","d5f6d309fe6b70d56201d8b172d1293a_d");
+        map.put("ptime",ptime.toString());
+        String xpPrYdcbA1 = getSign(map, "xpPrYdcbA1");
+        //发送请求
+        String param = "vid="+"d5f6d309fe6b70d56201d8b172d1293a_d"+"&ptime="+ptime+"&sign="+xpPrYdcbA1;
+        String polyvVide = HttpUtils.sendGet("http://api.polyv.net/v2/video/" + "d5f6d309fe" + "/get-video-msg", param);
+        JSONObject jsonObject = JSONObject.parseObject(polyvVide);
+        System.out.println(ptime);
+        System.out.println(xpPrYdcbA1);
+    }*/
+    /**
+     * 生成保利威视频签名
+     *
+     * @author change
+     */
+    public static String getSign(Map<String, String> maps, String secretkey) throws Exception {
+        List<String> keys = new ArrayList<>(maps.keySet());
+        List<String> tmp = new ArrayList<>();
+        Collections.sort(keys);
+        for (String key : keys) {
+            if (null != maps.get(key) && maps.get(key).length() > 0) {
+                tmp.add(key + "=" + maps.get(key));
+            }
+        }
+        String join = String.join("&", tmp) + secretkey;
+        return getSha1(join).toUpperCase();
+    }
+
+    public static String getSha1(String input) throws NoSuchAlgorithmException {
+        MessageDigest mDigest = MessageDigest.getInstance("SHA1");
+        byte[] result = mDigest.digest(input.getBytes(StandardCharsets.UTF_8));
+        StringBuilder sb = new StringBuilder();
+        for (int b : result) {
+            sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 获取直播加密字符串,并且假如到参数params中
+     * @param params 加密参数
+     * @param appSecret 保利威用户签名密钥
+     * @return  MD5签名字符串
+     * @throws NoSuchAlgorithmException 签名异常
+     *  @throws UnsupportedEncodingException 编码异常
+     */
+    public static String getSignMd5(Map<String, String> params, String appSecret)
+            throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        // 处理参数,计算MD5哈希值
+        String concatStr = concatParams(params);
+        String plain = appSecret + concatStr + appSecret;
+        String encrypted = md5Hex(plain).toUpperCase();
+        // 32位大写MD5值
+        return encrypted;
+    }
+
+    /**
+     * 把数组所有元素排序,并按照“参数参数值”的模式成字符串,用于计算MD5哈希值
+     * @param params 需要排序并参与字符拼接的参数组
+     * @return 拼接后字符串
+     */
+    public static String concatParams(Map<String, String> params) {
+        List<String> keys = new ArrayList<String>(params.keySet());
+        Collections.sort(keys);
+
+        StringBuilder sb = new StringBuilder();
+        for (String key : keys) {
+            String value = params.get(key);
+            if (value == null || "".equals(value.trim())) {
+                continue;
+            }
+            sb.append(key).append(value);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 对字符串做MD5加密,返回加密后的字符串。
+     * @param text 待加密的字符串。
+     * @return 加密后的字符串。
+     * @throws NoSuchAlgorithmException 签名异常
+     *  @throws UnsupportedEncodingException 编码异常
+     */
+    public static String md5Hex(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+
+        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+        byte[] inputByteArray = text.getBytes(UTF8);
+        messageDigest.update(inputByteArray);
+        byte[] resultByteArray = messageDigest.digest();
+        return byteArrayToHex(resultByteArray).toLowerCase();
+
+    }
+
+    /**
+     * 将字节数组换成成16进制的字符串
+     * @param byteArray 字节
+     * @return 字符串
+     */
+    public static String byteArrayToHex(byte[] byteArray) {
+        // 初始化一个字符数组用来存放每个16进制字符
+        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+        // new一个字符数组,这个就是用来组成结果字符串的(一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方))
+        char[] resultCharArray = new char[byteArray.length * 2];
+        // 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去
+        int index = 0;
+        for (byte b : byteArray) {
+            resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];
+            resultCharArray[index++] = hexDigits[b & 0xf];
+        }
+        // 字符数组组合成字符串返回
+        return new String(resultCharArray);
+    }
+}

+ 406 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/reflect/ReflectUtils.java

@@ -0,0 +1,406 @@
+package org.zhongzheng.common.utils.reflect;
+
+import cn.hutool.core.convert.Convert;
+import com.zhongzheng.common.utils.DateUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.*;
+import java.util.Date;
+
+/**
+ * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
+ * 
+ * @author zhongzheng
+ */
+@SuppressWarnings("rawtypes")
+public class ReflectUtils
+{
+    private static final String SETTER_PREFIX = "set";
+
+    private static final String GETTER_PREFIX = "get";
+
+    private static final String CGLIB_CLASS_SEPARATOR = "$$";
+
+    private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class);
+
+    /**
+     * 调用Getter方法.
+     * 支持多级,如:对象名.对象名.方法
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeGetter(Object obj, String propertyName)
+    {
+        Object object = obj;
+        for (String name : StringUtils.split(propertyName, "."))
+        {
+            String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+            object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+        }
+        return (E) object;
+    }
+
+    /**
+     * 调用Setter方法, 仅匹配方法名。
+     * 支持多级,如:对象名.对象名.方法
+     */
+    public static <E> void invokeSetter(Object obj, String propertyName, E value)
+    {
+        Object object = obj;
+        String[] names = StringUtils.split(propertyName, ".");
+        for (int i = 0; i < names.length; i++)
+        {
+            if (i < names.length - 1)
+            {
+                String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
+                object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+            }
+            else
+            {
+                String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+                invokeMethodByName(object, setterMethodName, new Object[] { value });
+            }
+        }
+    }
+
+    /**
+     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E getFieldValue(final Object obj, final String fieldName)
+    {
+        Field field = getAccessibleField(obj, fieldName);
+        if (field == null)
+        {
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            return null;
+        }
+        E result = null;
+        try
+        {
+            result = (E) field.get(obj);
+        }
+        catch (IllegalAccessException e)
+        {
+            logger.error("不可能抛出的异常{}", e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
+     */
+    public static <E> void setFieldValue(final Object obj, final String fieldName, final E value)
+    {
+        Field field = getAccessibleField(obj, fieldName);
+        if (field == null)
+        {
+            // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
+            return;
+        }
+        try
+        {
+            field.set(obj, value);
+        }
+        catch (IllegalAccessException e)
+        {
+            logger.error("不可能抛出的异常: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符.
+     * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
+     * 同时匹配方法名+参数类型,
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
+            final Object[] args)
+    {
+        if (obj == null || methodName == null)
+        {
+            return null;
+        }
+        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
+        if (method == null)
+        {
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
+            return null;
+        }
+        try
+        {
+            return (E) method.invoke(obj, args);
+        }
+        catch (Exception e)
+        {
+            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
+            throw convertReflectionExceptionToUnchecked(msg, e);
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符,
+     * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
+     * 只匹配函数名,如果有多个同名函数调用第一个。
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeMethodByName(final Object obj, final String methodName, final Object[] args)
+    {
+        Method method = getAccessibleMethodByName(obj, methodName, args.length);
+        if (method == null)
+        {
+            // 如果为空不报错,直接返回空。
+            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
+            return null;
+        }
+        try
+        {
+            // 类型转换(将参数数据类型转换为目标方法参数类型)
+            Class<?>[] cs = method.getParameterTypes();
+            for (int i = 0; i < cs.length; i++)
+            {
+                if (args[i] != null && !args[i].getClass().equals(cs[i]))
+                {
+                    if (cs[i] == String.class)
+                    {
+                        args[i] = Convert.toStr(args[i]);
+                        if (StringUtils.endsWith((String) args[i], ".0"))
+                        {
+                            args[i] = StringUtils.substringBefore((String) args[i], ".0");
+                        }
+                    }
+                    else if (cs[i] == Integer.class)
+                    {
+                        args[i] = Convert.toInt(args[i]);
+                    }
+                    else if (cs[i] == Long.class)
+                    {
+                        args[i] = Convert.toLong(args[i]);
+                    }
+                    else if (cs[i] == Double.class)
+                    {
+                        args[i] = Convert.toDouble(args[i]);
+                    }
+                    else if (cs[i] == Float.class)
+                    {
+                        args[i] = Convert.toFloat(args[i]);
+                    }
+                    else if (cs[i] == Date.class)
+                    {
+                        if (args[i] instanceof String)
+                        {
+                            args[i] = DateUtils.parseDate(args[i]);
+                        }
+                        else
+                        {
+                            args[i] = DateUtil.getJavaDate((Double) args[i]);
+                        }
+                    }
+                    else if (cs[i] == boolean.class || cs[i] == Boolean.class)
+                    {
+                        args[i] = Convert.toBool(args[i]);
+                    }
+                }
+            }
+            return (E) method.invoke(obj, args);
+        }
+        catch (Exception e)
+        {
+            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
+            throw convertReflectionExceptionToUnchecked(msg, e);
+        }
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     */
+    public static Field getAccessibleField(final Object obj, final String fieldName)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return null;
+        }
+        Validate.notBlank(fieldName, "fieldName can't be blank");
+        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass())
+        {
+            try
+            {
+                Field field = superClass.getDeclaredField(fieldName);
+                makeAccessible(field);
+                return field;
+            }
+            catch (NoSuchFieldException e)
+            {
+                continue;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     * 匹配函数名+参数类型。
+     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+     */
+    public static Method getAccessibleMethod(final Object obj, final String methodName,
+            final Class<?>... parameterTypes)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return null;
+        }
+        Validate.notBlank(methodName, "methodName can't be blank");
+        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
+        {
+            try
+            {
+                Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
+                makeAccessible(method);
+                return method;
+            }
+            catch (NoSuchMethodException e)
+            {
+                continue;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     * 只匹配函数名。
+     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+     */
+    public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum)
+    {
+        // 为空不报错。直接返回 null
+        if (obj == null)
+        {
+            return null;
+        }
+        Validate.notBlank(methodName, "methodName can't be blank");
+        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
+        {
+            Method[] methods = searchType.getDeclaredMethods();
+            for (Method method : methods)
+            {
+                if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum)
+                {
+                    makeAccessible(method);
+                    return method;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+     */
+    public static void makeAccessible(Method method)
+    {
+        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
+                && !method.isAccessible())
+        {
+            method.setAccessible(true);
+        }
+    }
+
+    /**
+     * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+     */
+    public static void makeAccessible(Field field)
+    {
+        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
+                || Modifier.isFinal(field.getModifiers())) && !field.isAccessible())
+        {
+            field.setAccessible(true);
+        }
+    }
+
+    /**
+     * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
+     * 如无法找到, 返回Object.class.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Class<T> getClassGenricType(final Class clazz)
+    {
+        return getClassGenricType(clazz, 0);
+    }
+
+    /**
+     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
+     * 如无法找到, 返回Object.class.
+     */
+    public static Class getClassGenricType(final Class clazz, final int index)
+    {
+        Type genType = clazz.getGenericSuperclass();
+
+        if (!(genType instanceof ParameterizedType))
+        {
+            logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType");
+            return Object.class;
+        }
+
+        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
+
+        if (index >= params.length || index < 0)
+        {
+            logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+                    + params.length);
+            return Object.class;
+        }
+        if (!(params[index] instanceof Class))
+        {
+            logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
+            return Object.class;
+        }
+
+        return (Class) params[index];
+    }
+
+    public static Class<?> getUserClass(Object instance)
+    {
+        if (instance == null)
+        {
+            throw new RuntimeException("Instance must not be null");
+        }
+        Class clazz = instance.getClass();
+        if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR))
+        {
+            Class<?> superClass = clazz.getSuperclass();
+            if (superClass != null && !Object.class.equals(superClass))
+            {
+                return superClass;
+            }
+        }
+        return clazz;
+
+    }
+
+    /**
+     * 将反射时的checked exception转换为unchecked exception.
+     */
+    public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e)
+    {
+        if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
+                || e instanceof NoSuchMethodException)
+        {
+            return new IllegalArgumentException(msg, e);
+        }
+        else if (e instanceof InvocationTargetException)
+        {
+            return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException());
+        }
+        return new RuntimeException(msg, e);
+    }
+}

+ 161 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/spring/SpringUtils.java

@@ -0,0 +1,161 @@
+package org.zhongzheng.common.utils.spring;
+
+import cn.hutool.core.lang.Validator;
+import net.polyv.live.v1.config.LiveGlobalConfig;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring工具类 方便在非spring管理环境中获取bean
+ *
+ * @author zhongzheng
+ */
+@Component
+public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
+{
+    /** Spring应用上下文环境 */
+    private static ConfigurableListableBeanFactory beanFactory;
+
+    private static ApplicationContext applicationContext;
+
+    @Value("${poliv.sdk.userid}")
+    private String poliv_sdk_userid;
+
+    @Value("${poliv.sdk.appId}")
+    private String poliv_sdk_appId;
+
+    @Value("${poliv.sdk.appSecret}")
+    private String poliv_sdk_appSecret;
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
+    {
+        SpringUtils.beanFactory = beanFactory;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
+    {
+        SpringUtils.applicationContext = applicationContext;
+        String userId = "egsxlptzdq";
+        String appId = "ezl5uy4zei";
+        String appSecret = "2bf5bb3c31d34531943df10284edd50b";
+        LiveGlobalConfig.init(appId,userId,appSecret);
+    }
+
+    /**
+     * 获取对象
+     *
+     * @param name
+     * @return Object 一个以所给名字注册的bean的实例
+     * @throws BeansException
+     *
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) throws BeansException
+    {
+        return (T) beanFactory.getBean(name);
+    }
+
+    /**
+     * 获取类型为requiredType的对象
+     *
+     * @param clz
+     * @return
+     * @throws BeansException
+     *
+     */
+    public static <T> T getBean(Class<T> clz) throws BeansException
+    {
+        T result = (T) beanFactory.getBean(clz);
+        return result;
+    }
+
+    /**
+     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+     *
+     * @param name
+     * @return boolean
+     */
+    public static boolean containsBean(String name)
+    {
+        return beanFactory.containsBean(name);
+    }
+
+    /**
+     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
+     *
+     * @param name
+     * @return boolean
+     * @throws NoSuchBeanDefinitionException
+     *
+     */
+    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.isSingleton(name);
+    }
+
+    /**
+     * @param name
+     * @return Class 注册对象的类型
+     * @throws NoSuchBeanDefinitionException
+     *
+     */
+    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.getType(name);
+    }
+
+    /**
+     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
+     *
+     * @param name
+     * @return
+     * @throws NoSuchBeanDefinitionException
+     *
+     */
+    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
+    {
+        return beanFactory.getAliases(name);
+    }
+
+    /**
+     * 获取aop代理对象
+     *
+     * @param invoker
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getAopProxy(T invoker)
+    {
+        return (T) AopContext.currentProxy();
+    }
+
+    /**
+     * 获取当前的环境配置,无配置返回null
+     *
+     * @return 当前的环境配置
+     */
+    public static String[] getActiveProfiles()
+    {
+        return applicationContext.getEnvironment().getActiveProfiles();
+    }
+
+    /**
+     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
+     *
+     * @return 当前的环境配置
+     */
+    public static String getActiveProfile()
+    {
+        final String[] activeProfiles = getActiveProfiles();
+        return Validator.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
+    }
+}

+ 37 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/sql/SqlUtil.java

@@ -0,0 +1,37 @@
+package org.zhongzheng.common.utils.sql;
+
+import cn.hutool.core.lang.Validator;
+import com.zhongzheng.common.exception.BaseException;
+
+/**
+ * sql操作工具类
+ * 
+ * @author zhongzheng
+ */
+public class SqlUtil
+{
+    /**
+     * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
+     */
+    public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
+
+    /**
+     * 检查字符,防止注入绕过
+     */
+    public static String escapeOrderBySql(String value)
+    {
+        if (Validator.isNotEmpty(value) && !isValidOrderBySql(value))
+        {
+            throw new BaseException("参数不符合规范,不能进行查询");
+        }
+        return value;
+    }
+
+    /**
+     * 验证 order by 语法是否符合规范
+     */
+    public static boolean isValidOrderBySql(String value)
+    {
+        return value.matches(SQL_PATTERN);
+    }
+}

+ 175 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wisdom/SignatureUtil.java

@@ -0,0 +1,175 @@
+package org.zhongzheng.common.utils.wisdom;
+
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SignatureException;
+import java.util.*;
+
+public class SignatureUtil {
+    /**
+     * 校验签名是否有效
+     *
+     * @param paramMap
+     * @param key
+     * @param signature
+     * @return
+     */
+    public static boolean isSignatureValid(Map<String, Object> paramMap, String key,
+                                           String signature) throws SignatureException {
+        String mySignature = null;
+        try {
+            mySignature = createSignature(paramMap, key);
+        } catch (SignatureException e) {
+            e.printStackTrace();// should not happen
+            throw e;
+        }
+        if (mySignature.equals(signature)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 校验签名是否有效
+     *
+     * @param paramMapList
+     * @param key
+     * @param signature
+     * @return
+     */
+    public static boolean isSignatureValid(List<Map<String, Object>> paramMapList, String key,
+                                           String signature) throws SignatureException {
+        Map<String, Object> paramMap = getMap(paramMapList, key);
+        return isSignatureValid(paramMap, key, signature);
+    }
+
+    /**
+     * 创建签名字符串
+     *
+     * @param paramMap
+     * @param key
+     * @return
+     * @throws SignatureException
+     */
+    public static String createSignature(Map<String, Object> paramMap, String key) throws SignatureException {
+        String data = getParamString(paramMap);
+        try {
+            String hmac = "HmacSHA512";
+            SecretKeySpec signingKey = new SecretKeySpec(key.getBytes("UTF-8"), hmac);
+            Mac mac = Mac.getInstance(hmac);
+            mac.init(signingKey);
+            byte[] rawHmac = mac.doFinal(data.getBytes("UTF-8"));
+            return DatatypeConverter.printBase64Binary(rawHmac);
+            // Base64 only valid in Java8
+            //return new String(Base64.getEncoder().encode(rawHmac));
+        } catch (NoSuchAlgorithmException e) {
+            throw new SignatureException("no such algorithm.", e);
+        } catch (InvalidKeyException e) {
+            throw new SignatureException("invalid key.", e);
+        } catch (UnsupportedEncodingException e) {
+            throw new SignatureException("unsupported encoding.", e);
+        }
+    }
+
+    /**
+     * 创建签名字符串
+     *
+     * @param paramMapList
+     * @param key
+     * @return
+     * @throws SignatureException
+     */
+    public static String createSignature(List<Map<String, Object>> paramMapList, String key) throws SignatureException {
+        Map<String, Object> paramMap = getMap(paramMapList, key);
+        return createSignature(paramMap, key);
+    }
+
+    public static String getParamString(Map<String, Object> paramMap) {
+        if (paramMap == null || paramMap.isEmpty()) {
+            return null;
+        }
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("me=my");
+//        List<String> keys = new ArrayList<String>(paramMap.keySet());
+//        Collections.sort(keys);
+//        for (String key : keys) {
+//            if (paramMap.get(key) != null){
+//                Object obj = paramMap.get(key);
+//                String value = obj.toString();
+//                if (StringUtils.isNotBlank(value)) {
+//                    try {
+//                        value = URLEncoder.encode(value, "UTF-8");
+//                    } catch (UnsupportedEncodingException e) {
+//                        value = null;
+//                    }
+//                }
+//                buffer.append("&").append(key).append("=").append(value);
+//            }
+//
+//        }
+        getParams(paramMap, buffer);
+        String result = buffer.toString();
+        return result;
+    }
+
+    private static void getParams(Map<String, Object> paramMap, StringBuilder buffer) {
+        List<String> keys = new ArrayList<String>(paramMap.keySet());
+        Collections.sort(keys);
+        for (String key : keys) {
+            if (paramMap.get(key) != null && notfiler(key)) {
+                Object obj = paramMap.get(key);
+                if (obj instanceof List<?>) {
+                    try {
+                        List<Map<String, Object>> list = (List<Map<String, Object>>) obj;
+                        for (Map<String, Object> map : list) {
+                            getParams(map, buffer);
+                        }
+                    } catch (Exception e) {
+                        System.out.println("list 转map出错 :" + e.getMessage());
+                    }
+                } else if (obj instanceof Map<?, ?>) {
+                    Map<String, Object> map = (Map<String, Object>) obj;
+                    getParams(map, buffer);
+                } else if (obj instanceof String) {
+                    String value = obj.toString();
+                    if (StringUtils.isNotBlank(value)) {
+                        try {
+                            value = URLEncoder.encode(value, "UTF-8");
+                        } catch (UnsupportedEncodingException e) {
+                            value = null;
+                        }
+                    }
+                    buffer.append("&").append(key).append("=").append(value);
+                }
+
+            }
+
+        }
+
+    }
+
+    private static boolean notfiler(String key) {
+        boolean flag = true;
+        Map<String, Integer> map = new HashMap<>();
+        map.put("examineeZipFiles", 0);
+        if (map.containsKey(key)) {
+            flag = false;
+        }
+        return flag;
+    }
+
+    private static Map<String, Object> getMap(List<Map<String, Object>> paramMapList, String key) {
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put(key, paramMapList);
+
+        return paramMap;
+    }
+
+}

+ 42 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/IWXPayDomain.java

@@ -0,0 +1,42 @@
+package org.zhongzheng.common.utils.wxpay;
+
+/**
+ * 域名管理,实现主备域名自动切换
+ */
+public abstract interface IWXPayDomain {
+    /**
+     * 上报域名网络状况
+     * @param domain 域名。 比如:api.mch.weixin.qq.com
+     * @param elapsedTimeMillis 耗时
+     * @param ex 网络请求中出现的异常。
+     *           null表示没有异常
+     *           ConnectTimeoutException,表示建立网络连接异常
+     *           UnknownHostException, 表示dns解析异常
+     */
+    abstract void report(final String domain, long elapsedTimeMillis, final Exception ex);
+
+    /**
+     * 获取域名
+     * @param config 配置
+     * @return 域名
+     */
+    abstract DomainInfo getDomain(final WXPayConfig config);
+
+    static class DomainInfo{
+        public String domain;       //域名
+        public boolean primaryDomain;     //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名
+        public DomainInfo(String domain, boolean primaryDomain) {
+            this.domain = domain;
+            this.primaryDomain = primaryDomain;
+        }
+
+        @Override
+        public String toString() {
+            return "DomainInfo{" +
+                    "domain='" + domain + '\'' +
+                    ", primaryDomain=" + primaryDomain +
+                    '}';
+        }
+    }
+
+}

+ 690 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPay.java

@@ -0,0 +1,690 @@
+package org.zhongzheng.common.utils.wxpay;
+
+import com.zhongzheng.common.utils.wxpay.WXPayConstants.SignType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class WXPay {
+
+    private WXPayConfig config;
+    private SignType signType;
+    private boolean autoReport;
+    private boolean useSandbox;
+    private String notifyUrl;
+    private WXPayRequest wxPayRequest;
+
+    public WXPay(final WXPayConfig config) throws Exception {
+        this(config, null, true, false);
+    }
+
+    public WXPay(final WXPayConfig config, final boolean autoReport) throws Exception {
+        this(config, null, autoReport, false);
+    }
+
+
+    public WXPay(final WXPayConfig config, final boolean autoReport, final boolean useSandbox) throws Exception{
+        this(config, null, autoReport, useSandbox);
+    }
+
+    public WXPay(final WXPayConfig config, final String notifyUrl) throws Exception {
+        this(config, notifyUrl, true, false);
+    }
+
+    public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport) throws Exception {
+        this(config, notifyUrl, autoReport, false);
+    }
+
+    public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
+        this.config = config;
+        this.notifyUrl = notifyUrl;
+        this.autoReport = autoReport;
+        this.useSandbox = useSandbox;
+        if (useSandbox) {
+            this.signType = SignType.MD5; // 沙箱环境
+        }
+        else {
+        //    this.signType = SignType.HMACSHA256;
+            this.signType = SignType.MD5;
+        }
+        this.wxPayRequest = new WXPayRequest(config);
+    }
+
+    private void checkWXPayConfig() throws Exception {
+        if (this.config == null) {
+            throw new Exception("config is null");
+        }
+        if (this.config.getAppID() == null || this.config.getAppID().trim().length() == 0) {
+            throw new Exception("appid in config is empty");
+        }
+        if (this.config.getMchID() == null || this.config.getMchID().trim().length() == 0) {
+            throw new Exception("appid in config is empty");
+        }
+        if (this.config.getCertStream() == null) {
+            throw new Exception("cert stream in config is empty");
+        }
+        if (this.config.getWXPayDomain() == null){
+            throw new Exception("config.getWXPayDomain() is null");
+        }
+
+        if (this.config.getHttpConnectTimeoutMs() < 10) {
+            throw new Exception("http connect timeout is too small");
+        }
+        if (this.config.getHttpReadTimeoutMs() < 10) {
+            throw new Exception("http read timeout is too small");
+        }
+
+    }
+
+    /**
+     * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
+     * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
+     *
+     * @param reqData
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
+        reqData.put("appid", config.getAppID());
+        reqData.put("mch_id", config.getMchID());
+        reqData.put("nonce_str", WXPayUtil.generateNonceStr());
+        if (SignType.MD5.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.MD5);
+        }
+        else if (SignType.HMACSHA256.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.HMACSHA256);
+        }
+        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
+        return reqData;
+    }
+
+    /**
+     * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
+        // 返回数据的签名方式和请求中给定的签名方式是一致的
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
+    }
+
+    /**
+     * 判断支付结果通知中的sign是否有效
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
+        String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
+        SignType signType;
+        if (signTypeInData == null) {
+            signType = SignType.MD5;
+        }
+        else {
+            signTypeInData = signTypeInData.trim();
+            if (signTypeInData.length() == 0) {
+                signType = SignType.MD5;
+            }
+            else if (WXPayConstants.MD5.equals(signTypeInData)) {
+                signType = SignType.MD5;
+            }
+            else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
+                signType = SignType.HMACSHA256;
+            }
+            else {
+                throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
+            }
+        }
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);
+    }
+
+
+    /**
+     * 不需要证书的请求
+     * @param urlSuffix String
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public String requestWithoutCert(String urlSuffix, Map<String, String> reqData,
+                                     int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String msgUUID = reqData.get("nonce_str");
+        String reqBody = WXPayUtil.mapToXml(reqData);
+
+        String resp = this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
+        return resp;
+    }
+
+
+    /**
+     * 需要证书的请求
+     * @param urlSuffix String
+     * @param reqData 向wxpay post的请求数据  Map
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public String requestWithCert(String urlSuffix, Map<String, String> reqData,
+                                  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String msgUUID= reqData.get("nonce_str");
+        String reqBody = WXPayUtil.mapToXml(reqData);
+
+        String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
+        return resp;
+    }
+
+    /**
+     * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
+     * @param xmlStr API返回的XML格式数据
+     * @return Map类型数据
+     * @throws Exception
+     */
+    public Map<String, String> processResponseXml(String xmlStr) throws Exception {
+        String RETURN_CODE = "return_code";
+        String return_code;
+        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
+        if (respData.containsKey(RETURN_CODE)) {
+            return_code = respData.get(RETURN_CODE);
+        }
+        else {
+            throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
+        }
+
+        if (return_code.equals(WXPayConstants.FAIL)) {
+            return respData;
+        }
+        else if (return_code.equals(WXPayConstants.SUCCESS)) {
+           if (this.isResponseSignatureValid(respData)) {
+               return respData;
+           }
+           else {
+               throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
+           }
+        }
+        else {
+            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
+        }
+    }
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> microPay(Map<String, String> reqData) throws Exception {
+        return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_MICROPAY_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.MICROPAY_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+    /**
+     * 提交刷卡支付,针对软POS,尽可能做成功
+     * 内置重试机制,最多60s
+     * @param reqData
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> microPayWithPos(Map<String, String> reqData) throws Exception {
+        return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs());
+    }
+
+    /**
+     * 提交刷卡支付,针对软POS,尽可能做成功
+     * 内置重试机制,最多60s
+     * @param reqData
+     * @param connectTimeoutMs
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> microPayWithPos(Map<String, String> reqData, int connectTimeoutMs) throws Exception {
+        int remainingTimeMs = 60*1000;
+        long startTimestampMs = 0;
+        Map<String, String> lastResult = null;
+        Exception lastException = null;
+
+        while (true) {
+            startTimestampMs = WXPayUtil.getCurrentTimestampMs();
+            int readTimeoutMs = remainingTimeMs - connectTimeoutMs;
+            if (readTimeoutMs > 1000) {
+                try {
+                    lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs);
+                    String returnCode = lastResult.get("return_code");
+                    if (returnCode.equals("SUCCESS")) {
+                        String resultCode = lastResult.get("result_code");
+                        String errCode = lastResult.get("err_code");
+                        if (resultCode.equals("SUCCESS")) {
+                            break;
+                        }
+                        else {
+                            // 看错误码,若支付结果未知,则重试提交刷卡支付
+                            if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) {
+                                remainingTimeMs = remainingTimeMs - (int)(WXPayUtil.getCurrentTimestampMs() - startTimestampMs);
+                                if (remainingTimeMs <= 100) {
+                                    break;
+                                }
+                                else {
+                                    WXPayUtil.getLogger().info("microPayWithPos: try micropay again");
+                                    if (remainingTimeMs > 5*1000) {
+                                        Thread.sleep(5*1000);
+                                    }
+                                    else {
+                                        Thread.sleep(1*1000);
+                                    }
+                                    continue;
+                                }
+                            }
+                            else {
+                                break;
+                            }
+                        }
+                    }
+                    else {
+                        break;
+                    }
+                }
+                catch (Exception ex) {
+                    lastResult = null;
+                    lastException = ex;
+                }
+            }
+            else {
+                break;
+            }
+        }
+
+        if (lastResult == null) {
+            throw lastException;
+        }
+        else {
+            return lastResult;
+        }
+    }
+
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
+        return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> unifiedOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.UNIFIEDORDER_URL_SUFFIX;
+        }
+        if(this.notifyUrl != null) {
+            reqData.put("notify_url", this.notifyUrl);
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception {
+        return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据 int
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_ORDERQUERY_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.ORDERQUERY_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData) throws Exception {
+        return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REVERSE_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REVERSE_URL_SUFFIX;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception {
+        return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_CLOSEORDER_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.CLOSEORDER_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData) throws Exception {
+        return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUND_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REFUND_URL_SUFFIX;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception {
+        return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUNDQUERY_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REFUNDQUERY_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception {
+        return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:对账单下载<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:无论是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data,
+     *      其中return_code为`SUCCESS`,data为对账单数据。
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return 经过封装的API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.DOWNLOADBILL_URL_SUFFIX;
+        }
+        String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();
+        Map<String, String> ret;
+        // 出现错误,返回XML数据
+        if (respStr.indexOf("<") == 0) {
+            ret = WXPayUtil.xmlToMap(respStr);
+        }
+        else {
+            // 正常返回csv数据
+            ret = new HashMap<String, String>();
+            ret.put("return_code", WXPayConstants.SUCCESS);
+            ret.put("return_msg", "ok");
+            ret.put("data", respStr);
+        }
+        return ret;
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData) throws Exception {
+        return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REPORT_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REPORT_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return WXPayUtil.xmlToMap(respXml);
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception {
+        return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_SHORTURL_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.SHORTURL_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception {
+        return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.AUTHCODETOOPENID_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+} // end class

+ 103 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPayConfig.java

@@ -0,0 +1,103 @@
+package org.zhongzheng.common.utils.wxpay;
+
+import java.io.InputStream;
+
+public abstract class WXPayConfig {
+
+
+
+    /**
+     * 获取 App ID
+     *
+     * @return App ID
+     */
+    protected abstract String getAppID();
+
+
+    /**
+     * 获取 Mch ID
+     *
+     * @return Mch ID
+     */
+    protected abstract String getMchID();
+
+
+    /**
+     * 获取 API 密钥
+     *
+     * @return API密钥
+     */
+    protected abstract String getKey();
+
+
+    /**
+     * 获取商户证书内容
+     *
+     * @return 商户证书内容
+     */
+    protected abstract InputStream getCertStream();
+
+    /**
+     * HTTP(S) 连接超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpConnectTimeoutMs() {
+        return 6*1000;
+    }
+
+    /**
+     * HTTP(S) 读数据超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpReadTimeoutMs() {
+        return 8*1000;
+    }
+
+    /**
+     * 获取WXPayDomain, 用于多域名容灾自动切换
+     * @return
+     */
+    protected abstract IWXPayDomain getWXPayDomain();
+
+    /**
+     * 是否自动上报。
+     * 若要关闭自动上报,子类中实现该函数返回 false 即可。
+     *
+     * @return
+     */
+    public boolean shouldAutoReport() {
+        return true;
+    }
+
+    /**
+     * 进行健康上报的线程的数量
+     *
+     * @return
+     */
+    public int getReportWorkerNum() {
+        return 6;
+    }
+
+
+    /**
+     * 健康上报缓存消息的最大数量。会有线程去独立上报
+     * 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受
+     *
+     * @return
+     */
+    public int getReportQueueMaxSize() {
+        return 10000;
+    }
+
+    /**
+     * 批量上报,一次最多上报多个数据
+     *
+     * @return
+     */
+    public int getReportBatchSize() {
+        return 10;
+    }
+
+}

+ 59 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPayConstants.java

@@ -0,0 +1,59 @@
+package org.zhongzheng.common.utils.wxpay;
+
+import org.apache.http.client.HttpClient;
+
+/**
+ * 常量
+ */
+public class WXPayConstants {
+
+    public enum SignType {
+        MD5, HMACSHA256
+    }
+
+    public static final String DOMAIN_API = "api.mch.weixin.qq.com";
+    public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com";
+    public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com";
+    public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com";
+
+
+    public static final String FAIL     = "FAIL";
+    public static final String SUCCESS  = "SUCCESS";
+    public static final String HMACSHA256 = "HMAC-SHA256";
+    public static final String MD5 = "MD5";
+
+    public static final String FIELD_SIGN = "sign";
+    public static final String FIELD_SIGN_TYPE = "sign_type";
+
+    public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
+    public static final String USER_AGENT = WXPAYSDK_VERSION +
+            " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
+            ") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
+
+    public static final String MICROPAY_URL_SUFFIX     = "/pay/micropay";
+    public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";
+    public static final String ORDERQUERY_URL_SUFFIX   = "/pay/orderquery";
+    public static final String REVERSE_URL_SUFFIX      = "/secapi/pay/reverse";
+    public static final String CLOSEORDER_URL_SUFFIX   = "/pay/closeorder";
+    public static final String REFUND_URL_SUFFIX       = "/secapi/pay/refund";
+    public static final String REFUNDQUERY_URL_SUFFIX  = "/pay/refundquery";
+    public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";
+    public static final String REPORT_URL_SUFFIX       = "/payitil/report";
+    public static final String SHORTURL_URL_SUFFIX     = "/tools/shorturl";
+    public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";
+
+    // sandbox
+    public static final String SANDBOX_MICROPAY_URL_SUFFIX     = "/sandboxnew/pay/micropay";
+    public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";
+    public static final String SANDBOX_ORDERQUERY_URL_SUFFIX   = "/sandboxnew/pay/orderquery";
+    public static final String SANDBOX_REVERSE_URL_SUFFIX      = "/sandboxnew/secapi/pay/reverse";
+    public static final String SANDBOX_CLOSEORDER_URL_SUFFIX   = "/sandboxnew/pay/closeorder";
+    public static final String SANDBOX_REFUND_URL_SUFFIX       = "/sandboxnew/secapi/pay/refund";
+    public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX  = "/sandboxnew/pay/refundquery";
+    public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";
+    public static final String SANDBOX_REPORT_URL_SUFFIX       = "/sandboxnew/payitil/report";
+    public static final String SANDBOX_SHORTURL_URL_SUFFIX     = "/sandboxnew/tools/shorturl";
+    public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";
+
+}
+

+ 265 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPayReport.java

@@ -0,0 +1,265 @@
+package org.zhongzheng.common.utils.wxpay;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * 交易保障
+ */
+public class WXPayReport {
+
+    public static class ReportInfo {
+
+        /**
+         * 布尔变量使用int。0为false, 1为true。
+         */
+
+        // 基本信息
+        private String version = "v1";
+        private String sdk = WXPayConstants.WXPAYSDK_VERSION;
+        private String uuid;  // 交易的标识
+        private long timestamp;   // 上报时的时间戳,单位秒
+        private long elapsedTimeMillis; // 耗时,单位 毫秒
+
+        // 针对主域名
+        private String firstDomain;  // 第1次请求的域名
+        private boolean primaryDomain; //是否主域名
+        private int firstConnectTimeoutMillis;  // 第1次请求设置的连接超时时间,单位 毫秒
+        private int firstReadTimeoutMillis;  // 第1次请求设置的读写超时时间,单位 毫秒
+        private int firstHasDnsError;  // 第1次请求是否出现dns问题
+        private int firstHasConnectTimeout; // 第1次请求是否出现连接超时
+        private int firstHasReadTimeout; // 第1次请求是否出现连接超时
+
+        public ReportInfo(String uuid, long timestamp, long elapsedTimeMillis, String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis, boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
+            this.uuid = uuid;
+            this.timestamp = timestamp;
+            this.elapsedTimeMillis = elapsedTimeMillis;
+            this.firstDomain = firstDomain;
+            this.primaryDomain = primaryDomain;
+            this.firstConnectTimeoutMillis = firstConnectTimeoutMillis;
+            this.firstReadTimeoutMillis = firstReadTimeoutMillis;
+            this.firstHasDnsError = firstHasDnsError?1:0;
+            this.firstHasConnectTimeout = firstHasConnectTimeout?1:0;
+            this.firstHasReadTimeout = firstHasReadTimeout?1:0;
+         }
+
+        @Override
+        public String toString() {
+            return "ReportInfo{" +
+                    "version='" + version + '\'' +
+                    ", sdk='" + sdk + '\'' +
+                    ", uuid='" + uuid + '\'' +
+                    ", timestamp=" + timestamp +
+                    ", elapsedTimeMillis=" + elapsedTimeMillis +
+                    ", firstDomain='" + firstDomain + '\'' +
+                    ", primaryDomain=" + primaryDomain +
+                    ", firstConnectTimeoutMillis=" + firstConnectTimeoutMillis +
+                    ", firstReadTimeoutMillis=" + firstReadTimeoutMillis +
+                    ", firstHasDnsError=" + firstHasDnsError +
+                    ", firstHasConnectTimeout=" + firstHasConnectTimeout +
+                    ", firstHasReadTimeout=" + firstHasReadTimeout +
+                    '}';
+        }
+
+        /**
+         * 转换成 csv 格式
+         *
+         * @return
+         */
+        public String toLineString(String key) {
+            String separator = ",";
+            Object[] objects = new Object[] {
+                version, sdk, uuid, timestamp, elapsedTimeMillis,
+                    firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
+                    firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout
+            };
+            StringBuffer sb = new StringBuffer();
+            for(Object obj: objects) {
+                sb.append(obj).append(separator);
+            }
+            try {
+                String sign = WXPayUtil.HMACSHA256(sb.toString(), key);
+                sb.append(sign);
+                return sb.toString();
+            }
+            catch (Exception ex) {
+                return null;
+            }
+
+        }
+
+    }
+
+    private static final String REPORT_URL = "http://report.mch.weixin.qq.com/wxpay/report/default";
+    // private static final String REPORT_URL = "http://127.0.0.1:5000/test";
+
+
+    private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6*1000;
+    private static final int DEFAULT_READ_TIMEOUT_MS = 8*1000;
+
+    private LinkedBlockingQueue<String> reportMsgQueue = null;
+    private WXPayConfig config;
+    private ExecutorService executorService;
+
+    private volatile static WXPayReport INSTANCE;
+
+    private WXPayReport(final WXPayConfig config) {
+        this.config = config;
+        reportMsgQueue = new LinkedBlockingQueue<String>(config.getReportQueueMaxSize());
+
+        // 添加处理线程
+        executorService = Executors.newFixedThreadPool(config.getReportWorkerNum(), new ThreadFactory() {
+            public Thread newThread(Runnable r) {
+                Thread t = Executors.defaultThreadFactory().newThread(r);
+                t.setDaemon(true);
+                return t;
+            }
+        });
+
+        if (config.shouldAutoReport()) {
+            WXPayUtil.getLogger().info("report worker num: {}", config.getReportWorkerNum());
+            for (int i = 0; i < config.getReportWorkerNum(); ++i) {
+                executorService.execute(new Runnable() {
+                    public void run() {
+                        while (true) {
+                            // 先用 take 获取数据
+                            try {
+                                StringBuffer sb = new StringBuffer();
+                                String firstMsg = reportMsgQueue.take();
+                                WXPayUtil.getLogger().info("get first report msg: {}", firstMsg);
+                                String msg = null;
+                                sb.append(firstMsg); //会阻塞至有消息
+                                int remainNum = config.getReportBatchSize() - 1;
+                                for (int j=0; j<remainNum; ++j) {
+                                    WXPayUtil.getLogger().info("try get remain report msg");
+                                    // msg = reportMsgQueue.poll();  // 不阻塞了
+                                    msg = reportMsgQueue.take();
+                                    WXPayUtil.getLogger().info("get remain report msg: {}", msg);
+                                    if (msg == null) {
+                                        break;
+                                    }
+                                    else {
+                                        sb.append("\n");
+                                        sb.append(msg);
+                                    }
+                                }
+                                // 上报
+                                WXPayReport.httpRequest(sb.toString(), DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
+                            }
+                            catch (Exception ex) {
+                                WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
+                            }
+                        }
+                    }
+                });
+            }
+        }
+
+    }
+
+    /**
+     * 单例,双重校验,请在 JDK 1.5及更高版本中使用
+     *
+     * @param config
+     * @return
+     */
+    public static WXPayReport getInstance(WXPayConfig config) {
+        if (INSTANCE == null) {
+            synchronized (WXPayReport.class) {
+                if (INSTANCE == null) {
+                    INSTANCE = new WXPayReport(config);
+                }
+            }
+        }
+        return INSTANCE;
+    }
+
+    public void report(String uuid, long elapsedTimeMillis,
+                       String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis,
+                       boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
+        long currentTimestamp = WXPayUtil.getCurrentTimestamp();
+        ReportInfo reportInfo = new ReportInfo(uuid, currentTimestamp, elapsedTimeMillis,
+                firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
+                firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout);
+        String data = reportInfo.toLineString(config.getKey());
+        WXPayUtil.getLogger().info("report {}", data);
+        if (data != null) {
+            reportMsgQueue.offer(data);
+        }
+    }
+
+
+    @Deprecated
+    private void reportSync(final String data) throws Exception {
+        httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
+    }
+
+    @Deprecated
+    private void reportAsync(final String data) throws Exception {
+        new Thread(new Runnable() {
+            public void run() {
+                try {
+                    httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
+                }
+                catch (Exception ex) {
+                    WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
+                }
+            }
+        }).start();
+    }
+
+    /**
+     * http 请求
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @return
+     * @throws Exception
+     */
+    private static String httpRequest(String data, int connectTimeoutMs, int readTimeoutMs) throws Exception{
+        BasicHttpClientConnectionManager connManager;
+        connManager = new BasicHttpClientConnectionManager(
+                RegistryBuilder.<ConnectionSocketFactory>create()
+                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                        .register("https", SSLConnectionSocketFactory.getSocketFactory())
+                        .build(),
+                null,
+                null,
+                null
+        );
+        HttpClient httpClient = HttpClientBuilder.create()
+                .setConnectionManager(connManager)
+                .build();
+
+        HttpPost httpPost = new HttpPost(REPORT_URL);
+
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
+        httpPost.setConfig(requestConfig);
+
+        StringEntity postEntity = new StringEntity(data, "UTF-8");
+        httpPost.addHeader("Content-Type", "text/xml");
+        httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT);
+        httpPost.setEntity(postEntity);
+
+        HttpResponse httpResponse = httpClient.execute(httpPost);
+        HttpEntity httpEntity = httpResponse.getEntity();
+        return EntityUtils.toString(httpEntity, "UTF-8");
+    }
+
+}

+ 259 - 0
zzbusiness-common/src/main/java/org/zhongzheng/common/utils/wxpay/WXPayRequest.java

@@ -0,0 +1,259 @@
+package org.zhongzheng.common.utils.wxpay;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+
+import static com.zhongzheng.common.utils.wxpay.WXPayConstants.USER_AGENT;
+
+
+public class WXPayRequest {
+    private WXPayConfig config;
+    public WXPayRequest(WXPayConfig config) throws Exception{
+
+        this.config = config;
+    }
+
+    /**
+     * 请求,只请求一次,不做重试
+     * @param domain
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @param useCert 是否使用证书,针对退款、撤销等操作
+     * @return
+     * @throws Exception
+     */
+    private String requestOnce(final String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {
+        BasicHttpClientConnectionManager connManager;
+
+        if (useCert) {
+            // 证书
+            char[] password = config.getMchID().toCharArray();
+            InputStream certStream = config.getCertStream();
+            KeyStore ks = KeyStore.getInstance("PKCS12");
+            ks.load(certStream, password);
+            // 实例化密钥库 & 初始化密钥工厂
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            kmf.init(ks, password);
+
+            // 创建 SSLContext
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
+
+            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
+                    sslContext,
+                    new String[]{"TLSv1"},
+                    null,
+                    new DefaultHostnameVerifier());
+
+            connManager = new BasicHttpClientConnectionManager(
+                    RegistryBuilder.<ConnectionSocketFactory>create()
+                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                            .register("https", sslConnectionSocketFactory)
+                            .build(),
+                    null,
+                    null,
+                    null
+            );
+        }
+        else {
+            connManager = new BasicHttpClientConnectionManager(
+                    RegistryBuilder.<ConnectionSocketFactory>create()
+                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                            .register("https", SSLConnectionSocketFactory.getSocketFactory())
+                            .build(),
+                    null,
+                    null,
+                    null
+            );
+        }
+
+        HttpClient httpClient = HttpClientBuilder.create()
+                .setConnectionManager(connManager)
+                .build();
+
+        String url = "https://" + domain + urlSuffix;
+        HttpPost httpPost = new HttpPost(url);
+
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
+        httpPost.setConfig(requestConfig);
+
+        StringEntity postEntity = new StringEntity(data, "UTF-8");
+        httpPost.addHeader("Content-Type", "text/xml");
+        httpPost.addHeader("User-Agent", USER_AGENT + " " + config.getMchID());
+        httpPost.setEntity(postEntity);
+
+        HttpResponse httpResponse = httpClient.execute(httpPost);
+        HttpEntity httpEntity = httpResponse.getEntity();
+        return EntityUtils.toString(httpEntity, "UTF-8");
+
+    }
+
+
+    private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
+        Exception exception = null;
+        long elapsedTimeMillis = 0;
+        long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
+        boolean firstHasDnsErr = false;
+        boolean firstHasConnectTimeout = false;
+        boolean firstHasReadTimeout = false;
+        IWXPayDomain.DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
+        if(domainInfo == null){
+            throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
+        }
+        try {
+            String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout);
+            return result;
+        }
+        catch (UnknownHostException ex) {  // dns 解析错误,或域名不存在
+            exception = ex;
+            firstHasDnsErr = true;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout
+            );
+        }
+        catch (ConnectTimeoutException ex) {
+            exception = ex;
+            firstHasConnectTimeout = true;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout
+            );
+        }
+        catch (SocketTimeoutException ex) {
+            exception = ex;
+            firstHasReadTimeout = true;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout);
+        }
+        catch (Exception ex) {
+            exception = ex;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout);
+        }
+        config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
+        throw exception;
+    }
+
+
+    /**
+     * 可重试的,非双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @return
+     */
+    public String requestWithoutCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), false, autoReport);
+    }
+
+    /**
+     * 可重试的,非双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @return
+     */
+    public String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs,  boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);
+    }
+
+    /**
+     * 可重试的,双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @return
+     */
+    public String requestWithCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), true, autoReport);
+    }
+
+    /**
+     * 可重试的,双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @return
+     */
+    public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);
+    }
+}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff