ソースを参照

邮件项目独立项目搭建

1811872455@163.com 3 日 前
コミット
0c434d1924
100 ファイル変更11830 行追加0 行削除
  1. 8 0
      .idea/.gitignore
  2. 36 0
      .idea/compiler.xml
  3. 25 0
      .idea/encodings.xml
  4. 35 0
      .idea/jarRepositories.xml
  5. 14 0
      .idea/misc.xml
  6. 8 0
      .idea/modules.xml
  7. 9 0
      .idea/storlead-saas-mail.iml
  8. 6 0
      .idea/vcs.xml
  9. 355 0
      Jenkinsfile
  10. 403 0
      pom.xml
  11. 41 0
      storlead-api/pom.xml
  12. 38 0
      storlead-api/src/main/java/com/storlead/api/StorleadMailServiceApplication.java
  13. 193 0
      storlead-api/src/main/resources/application-dev.yml
  14. 234 0
      storlead-api/src/main/resources/application-prod.yml
  15. 156 0
      storlead-api/src/main/resources/application-test.yml
  16. 76 0
      storlead-api/src/main/resources/application.yml
  17. 306 0
      storlead-dependencies/pom.xml
  18. 25 0
      storlead-framework/pom.xml
  19. 31 0
      storlead-framework/storlead-auth/pom.xml
  20. 133 0
      storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/jwt/JwtUtil.java
  21. 188 0
      storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/LoginUser.java
  22. 17 0
      storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/MenuPermis.java
  23. 56 0
      storlead-framework/storlead-auth/src/main/java/com/storlead/framework/util/LoginUserUtil.java
  24. 212 0
      storlead-framework/storlead-common/pom.xml
  25. 33 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/aviator/EqualsFunction.java
  26. 33 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/aviator/IndexOfFunction.java
  27. 31 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/aviator/MapKeyExistsFunction.java
  28. 34 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/aviator/NoIndexOfFunction.java
  29. 33 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/config/AutowiringSpringBeanJobFactory.java
  30. 50 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/config/QuartzConfig.java
  31. 32 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CacheConstant.java
  32. 41 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CodeGenerateInterface.java
  33. 82 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CommonConstant.java
  34. 35 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DSConstants.java
  35. 60 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DataBaseConstant.java
  36. 12 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DefContants.java
  37. 14 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/LongToStringSerializer.java
  38. 25 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/RedisKeySaltConstant.java
  39. 11 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/SystemConstant.java
  40. 24 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/UserCacheKeyConstants.java
  41. 20 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/dto/page/PageDTO.java
  42. 36 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/dto/query/QueryBaseDTO.java
  43. 712 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/ecode/BCrypt.java
  44. 159 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/ecode/BCryptPasswordEncoder.java
  45. 19 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/ecode/PasswordEncoder.java
  46. 26 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/enums/DataTypeEnum.java
  47. 79 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/enums/ErrorMsgCode.java
  48. 42 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/enums/ResultCode.java
  49. 6 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/package-info.java
  50. 90 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/properties/UrlChainDefinitionPorperties.java
  51. 40 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/property/YamlPropertyResourceFactory.java
  52. 183 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/result/Result.java
  53. 44 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/MatchTypeEnum.java
  54. 55 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/QueryCondition.java
  55. 71 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/QueryRuleEnum.java
  56. 88 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/thread/ThreadPoolUtil.java
  57. 31 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/CloseUtil.java
  58. 652 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ConvertUtils.java
  59. 914 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtil.java
  60. 895 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtils.java
  61. 367 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/FileUtil.java
  62. 150 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/HtmlUtils.java
  63. 151 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/HttpUtil.java
  64. 529 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/LocalDateUtils.java
  65. 47 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/MD5Util.java
  66. 227 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/OssBootUtil.java
  67. 232 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/PingyinUtil.java
  68. 100 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RandomCodeUtil.java
  69. 26 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RandomGenerateHelper.java
  70. 202 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RsaUtils.java
  71. 106 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SnowFlake.java
  72. 48 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SpringCompareUtil.java
  73. 95 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SpringContextUtils.java
  74. 103 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SqlInjectionUtil.java
  75. 201 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/StringUtil.java
  76. 29 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SystemUtils.java
  77. 47 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/UrlChainBlackAndWhiteUtil.java
  78. 372 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/WordUtils.java
  79. 219 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/WordUtilsTest.java
  80. 211 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ZipUtil.java
  81. 87 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/encryptor/AccessKeyEncryptor.java
  82. 78 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/CodeGenerate.java
  83. 29 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/DefaultIdentifierGenerator.java
  84. 36 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/IdWorker.java
  85. 10 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/IdentifierGenerator.java
  86. 126 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/Sequence.java
  87. 144 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/sso/CryptoZipUtil.java
  88. 62 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/sso/HttpSecurityCheckUtil.java
  89. 20 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NeContains.java
  90. 31 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NeContainsValidator.java
  91. 28 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NotContains.java
  92. 31 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NotContainsValidator.java
  93. 55 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/FeignResult.java
  94. 37 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/StaffVo.java
  95. 55 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/menu/TreeVo.java
  96. 119 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/user/UserVO.java
  97. 44 0
      storlead-framework/storlead-core/pom.xml
  98. 26 0
      storlead-framework/storlead-core/src/main/java/com/storlead/framework/annotate/AnnBase.java
  99. 22 0
      storlead-framework/storlead-core/src/main/java/com/storlead/framework/annotate/AnnotateEnumType.java
  100. 11 0
      storlead-framework/storlead-core/src/main/java/com/storlead/framework/annotate/Dept.java

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 36 - 0
.idea/compiler.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="storlead-auth" />
+        <module name="storlead-common" />
+        <module name="storlead-core" />
+        <module name="storlead-redis" />
+        <module name="storlead-web" />
+        <module name="storlead-mail" />
+        <module name="storlead-api" />
+        <module name="storlead-mybatis" />
+      </profile>
+    </annotationProcessing>
+    <bytecodeTargetLevel>
+      <module name="storlead-mail-mail" target="11" />
+      <module name="storlead-saas-platform" target="11" />
+    </bytecodeTargetLevel>
+  </component>
+  <component name="JavacSettings">
+    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
+      <module name="storlead-api" options="-parameters" />
+      <module name="storlead-auth" options="-parameters" />
+      <module name="storlead-common" options="-parameters" />
+      <module name="storlead-core" options="-parameters" />
+      <module name="storlead-mail" options="-parameters" />
+      <module name="storlead-mybatis" options="-parameters" />
+      <module name="storlead-redis" options="-parameters" />
+      <module name="storlead-web" options="-parameters" />
+    </option>
+  </component>
+</project>

+ 25 - 0
.idea/encodings.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-api/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-dependencies/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-dependencies/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-auth/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-auth/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-common/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-common/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-core/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-core/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-mybatis/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-mybatis/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-redis/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-redis/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-web/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-web/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-mail/src/main/java" charset="UTF-8" />
+  </component>
+</project>

+ 35 - 0
.idea/jarRepositories.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Central Repository" />
+      <option name="url" value="https://repo.maven.apache.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="aliyunmaven" />
+      <option name="name" value="aliyun" />
+      <option name="url" value="https://maven.aliyun.com/repository/public" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="spring-milestones" />
+      <option name="name" value="Spring Milestones" />
+      <option name="url" value="https://repo.spring.io/milestone" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="spring-snapshots" />
+      <option name="name" value="Spring Snapshots" />
+      <option name="url" value="https://repo.spring.io/snapshot" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+  </component>
+</project>

+ 14 - 0
.idea/misc.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="ms-17" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/storlead-mail/storlead-mail.iml" filepath="$PROJECT_DIR$/storlead-mail/storlead-mail.iml" />
+    </modules>
+  </component>
+</project>

+ 9 - 0
.idea/storlead-saas-mail.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 355 - 0
Jenkinsfile

@@ -0,0 +1,355 @@
+properties([
+        parameters([
+                [$class: 'ChoiceParameter',
+                 choiceType: 'PT_SINGLE_SELECT',
+                 description: '选择要部署 API or UI',
+                 name: 'modulePrefix',
+                 randomName: 'choice-parameter-5631314439613978',
+                 script: [
+                         $class: 'GroovyScript',
+                         fallbackScript: [
+                                 classpath: [],
+                                 sandbox: true,
+                                 script:
+                                         'return[\'Could not get Env\']'
+                         ],
+                         script: [
+                                 classpath: [],
+                                 sandbox: true,
+                                 script:
+                                         'return["java","ui"]'
+                         ]
+                 ]
+                ],
+                [$class: 'CascadeChoiceParameter',
+                 choiceType: 'PT_SINGLE_SELECT',
+                 description: '选择要部署的项目',
+                 name: 'module',
+                 randomName: 'choice-parameter-5631314456178619',
+                 referencedParameters: 'modulePrefix',
+                 script: [
+                         $class: 'GroovyScript',
+                         fallbackScript: [
+                                 classpath: [],
+                                 sandbox: true,
+                                 script:
+                                         'return[\'Could not get Environment from Env Param\']'
+                         ],
+                         script: [
+                                 classpath: [],
+                                 sandbox: true,
+                                 script:
+                                         ''' if (modulePrefix.equals("java")){
+                                return["storlead-centre-api"]
+                            }
+                            else if(modulePrefix.equals("ui")){
+                                return["sp-user-center"]
+                            }
+                        '''
+                         ]
+                 ]
+                ]
+
+
+        ])
+])
+
+pipeline {
+    options {
+        timestamps()
+    }
+    environment {
+        GROUPID = readMavenPom().getGroupId()//com.storlead
+        ARTIFACTID = readMavenPom().getArtifactId()//sp-project
+        VERSION = readMavenPom().getVersion()//1.0
+    }
+    // 参数
+    parameters {
+        choice(
+                name: 'profile',
+                choices: ['test', 'prod','uat'],
+                description: '选择要部署的配置文件'
+        )
+    }
+    agent any
+    stages {
+        stage('处理参数') {
+            steps {
+                script {
+                    echo "处理参数"
+                    script {
+                        //服务器配置
+                        MODULE_PREFIX = "${params.modulePrefix}"
+                        SERVER_SSH_PORT = 53023
+                        SERVER_HOST = "node1.storlead.com"
+                        SWARM_INIT_REPLICAS_NUM = 1
+
+                        //服务配置
+                        API_PORT = 9200
+                        API_REMOTE_DEBUG_PORT = 9201
+                        API_NAMESPACE_RESTFUL = "${params.module}"
+
+                        //DOCKER配置
+                        DOCKER_REGISTRY = "reg-aliyun"
+                        DOCKER_HOST = "registry.cn-shenzhen.aliyuncs.com"
+                        //Docker私服登陆url https://registry-vpc.cn-shenzhen.aliyuncs.com
+                        DOCKER_LOGIN_REGISTRY = "https://${DOCKER_HOST}"
+
+                        BUILD_PREFIX = "storlead-huawei"
+                        //镜像完整名称 registry-vpc.cn-shenzhen.aliyuncs.com/storlead/sp-project-management-prod:1.0
+                        DOCKER_IMG_NAME = "${DOCKER_HOST}/${BUILD_PREFIX}/${params.module}-${params.profile}:${VERSION}"
+                        //镜像名 storlead/sp-project-management-prod:1.0
+                        IMG_NAME = "${BUILD_PREFIX}/${params.module}-${params.profile}:${VERSION}"
+
+                        PUSH_DOCK_IMG = true
+                        SERVER_HOST_NAME =  "test1"
+                        echo "为指定 module 设定对应 服务器及端口"
+                        echo "处理 ${params.module} 项目"
+                        if (params.modulePrefix == "java") {
+                            echo "---- storelad-centre-api"
+                            script {
+                                if (params.profile == "test") {
+                                    SWARM_INIT_REPLICAS_NUM = 1
+                                    SERVER_HOST = "test1.storlead.com"
+                                    API_PORT = 10060
+                                    API_REMOTE_DEBUG_PORT = 10061
+                                    SERVER_HOST_NAME =  "test1"
+                                } else if (params.profile == "prod") {
+                                    SERVER_HOST = "110.41.82.21"
+                                    API_PORT = 10060
+                                    API_REMOTE_DEBUG_PORT = 10061
+                                    SERVER_HOST_NAME =  "node1"
+                                }
+                            }
+                        }  else if (params.modulePrefix == "ui") {
+                            script {
+                                if (params.profile == "test") {
+                                    SERVER_HOST = "test1.storlead.com"
+                                } else if (params.profile == "prod") {
+                                    SERVER_HOST = "110.41.82.21"
+                                }
+                            }
+                        }
+
+                        echo "处理MODULE_NAMESPACE_RESTFUL"
+                        //把 module 名 从 sp-xxx 变成 xxx 也就是 项目 的 namespace
+                        if (API_NAMESPACE_RESTFUL == "storlead-centre-api") {
+                            API_NAMESPACE_RESTFUL = "api"
+                        }
+                        echo "处理镜像推送"
+                    }
+                }
+            }
+        }
+
+        stage('Node 编译') {
+
+            when {
+                environment name: 'modulePrefix', value: 'ui'
+            }
+            agent {
+                docker {
+//                      image "registry-vpc.cn-shenzhen.aliyuncs.com/storlead/node:19-alpine"
+                    image "node:20-alpine"
+                    args '-v /app:/app/:z -v /etc/localtime:/etc/localtime:ro -u root'
+                    reuseNode true
+                }
+            }
+            steps {
+                withCredentials([
+                        usernamePassword(credentialsId: 'storleadHW', usernameVariable: 'usernameHW', passwordVariable: 'passwordHW'),
+                        usernamePassword(credentialsId: 'storlead', usernameVariable: 'username', passwordVariable: 'password')
+                ]) {
+                    script {
+                        echo "发布 UI 代码"
+
+                        def remote = [:]
+                        remote.allowAnyHosts = true
+                        remote.name = "${SERVER_HOST}"
+                        remote.host = "${SERVER_HOST}"
+                        remote.port = 53023
+                        remote.user = username
+                        remote.password = password
+
+                        if (params.profile == "prod") {
+                            remote.port = 22
+                            remote.user = "root"
+                            remote.user = usernameHW
+                            remote.password = passwordHW
+                        }
+                        if(params.module == 'sp-user-mobile-center') {
+                           sh """cd ${params.modulePrefix}/${params.module} && npm install  && npm run build:${params.profile}"""
+                        } else {
+                           sh """cd ${params.modulePrefix}/${params.module} && npm install  && npm run ${params.profile}"""
+                        }
+
+//                          sh """cd ${params.modulePrefix}/${params.module}  && npm run ${params.profile}"""
+
+                        sshCommand remote: remote, command: "mkdir -p /app/sp/ui/app-centre/${params.profile}/${params.module}"
+
+                        sshCommand remote: remote, command: "rm -rf /app/sp/ui/app-centre/${params.profile}/${params.module}/dist/*"
+
+
+                        echo "/app/sp/ui/sales/${params.profile}/${params.module}/*"
+
+                        sshPut remote: remote, from: "${params.modulePrefix}/${params.module}/dist/", into: "/app/sp/ui/app-centre/${params.profile}/${params.module}/"
+
+                        echo "${params.modulePrefix}/${params.module}/dist/,/app/sp/ui/app-centre/${params.profile}/${params.module}/"
+
+                        sshCommand remote: remote, command: "cp -rf /app/sp/ui/app-centre/${params.profile}/${params.module}/dist/* /app/sp/ui/app-centre/${params.profile}/${params.module}/"
+                    }
+                }
+            }
+        }
+
+        stage('Maven 编译') {
+            when {
+                environment name: 'modulePrefix', value: 'java'
+            }
+            agent {
+                docker {
+                    image 'maven:3-amazoncorretto-11'
+                    //让docker 使用 host 宿主机的 m2仓库 使用root用户来运行 以让指定的~/.m2/config/setting.xml 阿里加速下载maven 依赖生效
+                    args '-v /app:/app:z -v $HOME/.m2:/root/.m2:z -u root'
+                    reuseNode true
+                }
+            }
+            steps {
+                echo "编译 API 代码"
+                sh "mvn clean package -U -am -pl ${params.module} -P${params.profile} -Dmaven.test.skip=true"
+            }
+        }
+
+        // 项目打包到镜像并推送到镜像仓库
+        stage('构建Docker镜像') {
+            when {
+                environment name: 'modulePrefix', value: 'java'
+            }
+            steps {
+                echo "构建Dcoker镜像-------------------"
+                script {
+                    docker.withRegistry(DOCKER_LOGIN_REGISTRY, DOCKER_REGISTRY) {
+
+                        BASE_IMAGE = 'registry.cn-shenzhen.aliyuncs.com/storlead/openjdk:11'
+                        sh "docker pull ${BASE_IMAGE}"
+
+                        sh """
+                          echo '
+                          FROM ${BASE_IMAGE}
+
+                          COPY ${params.module}/target/${params.module}.jar app.jar
+                          EXPOSE ${API_PORT}
+                          EXPOSE ${API_REMOTE_DEBUG_PORT}
+                          HEALTHCHECK --interval=1m --timeout=10s CMD curl -f http://localhost:${API_PORT}/${API_NAMESPACE_RESTFUL}/actuator
+                          ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-Djava.awt.headless=true","-Dspring.profiles.active=${params.profile}", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:${API_REMOTE_DEBUG_PORT}","-jar","/app.jar"]' > Dockerfile
+                          docker build -t ${DOCKER_IMG_NAME} .
+                         """
+                        sh "docker push ${DOCKER_IMG_NAME}"
+                    }
+                }
+            }
+        }
+
+        stage('启动服务') {
+            when {
+                environment name: 'modulePrefix', value: 'java'
+            }
+            steps {
+                withCredentials([
+                        usernamePassword(credentialsId: 'storleadHW', usernameVariable: 'usernameHW', passwordVariable: 'passwordHW'),
+                        usernamePassword(credentialsId: 'storlead', usernameVariable: 'username', passwordVariable: 'password'),
+                        usernamePassword(credentialsId: 'reg-aliyun', usernameVariable: 'aliUsername', passwordVariable: 'aliPassword')
+                ]) {
+                    script {
+
+                        echo "启动服务"
+                        echo "${aliPassword}"
+                        echo "docker login -u ${aliUsername} -p ${aliPassword} ${DOCKER_LOGIN_REGISTRY}"
+
+                        def remote = [:]
+                        remote.name = "${SERVER_HOST}"
+                        remote.host = "${SERVER_HOST}"
+                        remote.port = 53023
+                        remote.user = username
+                        remote.password = password
+                        remote.allowAnyHosts = true
+
+                        //因为使用 docker swarm 发布 只能在 manage node 执行部署
+                        if (params.profile == "prod") {
+                            remote.port = 22
+                            remote.user = "root"
+                            remote.user = usernameHW
+                            remote.password = passwordHW
+                        }
+                        if (PUSH_DOCK_IMG) {
+                            //docker 认证
+                            sshCommand remote: remote, command: "docker login -u ${aliUsername} -p ${aliPassword} ${DOCKER_LOGIN_REGISTRY}"
+                            //如果选择推送才使用远程的版本 如果不选择推送到远程则直接运行本机刚打包的镜像版本即可
+                            sshCommand remote: remote, command: "docker pull ${DOCKER_IMG_NAME}"
+                        }
+                        sh """
+                          echo '
+  version: "3.7"
+  services:
+    ${params.module}-${params.profile}:
+      image: ${DOCKER_IMG_NAME}
+      deploy:
+        replicas: ${SWARM_INIT_REPLICAS_NUM}
+        placement:
+          constraints:  
+            - 'node.hostname == ${SERVER_HOST_NAME}'
+        update_config:
+          parallelism: 1
+          delay: 1m
+        restart_policy:
+          condition: on-failure
+          # 最多连续重试3次如果仍然失败则放弃重试
+          max_attempts: 3
+      healthcheck:
+        test: ["CMD", "curl", "-f", "http://localhost:${API_PORT}/${API_NAMESPACE_RESTFUL}/actuator"]
+        interval: 1m
+        timeout: 5s
+        retries: 3
+        start_period: 30s
+      environment:
+        - "TZ=Asia/Shanghai"
+        - "SPRING_PROFILES_ACTIVE=${params.profile}"
+      volumes:
+        - /app:/app
+        - /mnt/vdb/storlead:/mnt/vdb/storlead
+      ports:
+        - "${API_PORT}:${API_PORT}"
+        - "${API_REMOTE_DEBUG_PORT}:${API_REMOTE_DEBUG_PORT}"
+      networks:
+        - vonedao_net
+      extra_hosts:
+        - "www.storlead.com:172.18.194.161"
+        - "node1:110.41.82.21"
+        - "node2:110.41.174.46"
+        - "node3:121.37.226.174"
+        - "test1.storlead.com:172.18.194.168"
+        - "test2.storlead.com:172.18.194.163"
+  networks:
+    vonedao_net:
+       external: true
+                          ' > ${params.module}-${params.profile}-stack.yml
+                         """
+                        //                        if (params.profile =="dev") {
+                        //                            sh "docker stack deploy -c ${ARTIFACTID}-${params.profile}-stack.yml ${ARTIFACTID}-${params.profile}"
+
+                        //                            sh "docker ps -f name=${ARTIFACTID} -q | xargs --no-run-if-empty docker container stop"
+                        //                            sh "docker container ls -a -fname=${ARTIFACTID} -q | xargs -r docker container rm"
+                        //                            sh "docker run -d --restart=always -p ${API_PORT}:${API_PORT} -p ${API_REMOTE_DEBUG_PORT}:${API_REMOTE_DEBUG_PORT} -v /app:/app --name ${ARTIFACTID} ${DOCKER_IMG_NAME}"
+                        //                        } else {
+                        // 全部都以swarm 模式启动
+                        sshPut remote: remote, from: "${params.module}-${params.profile}-stack.yml", into: "/docker/sp/${params.module}-${params.profile}-stack.yml"
+                        sshCommand remote: remote, command: "docker stack deploy --resolve-image=always --with-registry-auth -c /docker/sp/${params.module}-${params.profile}-stack.yml ${params.module}-${params.profile}"
+                        //                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+}

+ 403 - 0
pom.xml

@@ -0,0 +1,403 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.0</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>com.storlead.boot</groupId>
+    <artifactId>storlead-mail-service</artifactId>
+    <version>1.0</version>
+    <packaging>pom</packaging>
+    <name>storlead-mail-service</name>
+
+    <modules>
+        <module>storlead-dependencies</module>
+        <module>storlead-framework</module>
+        <module>storlead-api</module>
+        <module>storlead-mail</module>
+    </modules>
+
+    <properties>
+        <revision>1.0</revision>
+        <java.version>11</java.version>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+        <!-- 全局配置项目版本 -->
+        <jwt.version>0.9.1</jwt.version>
+        <commons.version>2.6</commons.version>
+        <!-- 表示打包时跳过mvn test -->
+        <maven.test.skip>true</maven.test.skip>
+        <aviator.version>5.2.7</aviator.version>
+        <io.jsonwebtoken.version>0.9.1</io.jsonwebtoken.version>
+        <logstash-logback.version>6.6</logstash-logback.version>
+
+        <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
+        <maven-compiler-plugin.version>3.0</maven-compiler-plugin.version>
+        <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
+        <spring.boot.version>2.7.0</spring.boot.version>
+        <mapstruct.version>1.6.3</mapstruct.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <spring.boot.starter.parent>2.7.0</spring.boot.starter.parent>
+        <knife4j.version>4.5.0</knife4j.version>
+        <spring-web.version>5.3.32</spring-web.version>
+        <jsoup.version>1.18.3</jsoup.version>
+        <mockito-inline.version>4.11.0</mockito-inline.version>
+        <lombok.version>1.18.36</lombok.version>
+        <spring.framework.version>5.3.20</spring.framework.version>
+        <spring.security.version>5.8.14</spring.security.version>
+        <fastjson.version>2.0.23</fastjson.version>
+        <!--    5.8.35-->
+        <hutool.version>5.4.3</hutool.version>
+        <springdoc.version>1.7.0</springdoc.version>
+        <swagger.ui.version>3.0.0</swagger.ui.version>
+        <swagger.version>3.0.0</swagger.version>
+        <swagger.annotations.version>1.5.22</swagger.annotations.version>
+        <spring.web.socket.version>1.5.3</spring.web.socket.version>
+        <!--  redis -->
+        <redisson.version>3.6.5</redisson.version>
+        <httpclient.version>4.5.3</httpclient.version>
+        <!-- DB 相关 -->
+        <druid.version>1.1.24</druid.version>
+        <mybatis.version>3.5.17</mybatis.version>
+        <mybatis-plus.version>3.1.2</mybatis-plus.version>
+        <datasource.spring.boot.starter>2.5.4</datasource.spring.boot.starter>
+        <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
+        <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
+        <mysql.connector.version>5.1.47</mysql.connector.version>
+        <guava.version>33.4.0-jre</guava.version>
+        <transmittable.thread.version>2.14.0</transmittable.thread.version>
+        <google.code.gson.version>2.10.1</google.code.gson.version>
+        <squareup.okhttp.version>4.12.0</squareup.okhttp.version>
+        <okhttp.sse.version>4.10.0</okhttp.sse.version>
+        <spring.validation.version>2.6.3</spring.validation.version>
+        <apache.httpcore5.client5.version>5.2.1</apache.httpcore5.client5.version>
+        <apache.httpcore5.version>5.2.1</apache.httpcore5.version>
+        <apache.httpcore5-h2.version>5.2.1</apache.httpcore5-h2.version>
+        <apache.httpasyncclient.version>4.1.5</apache.httpasyncclient.version>
+        <io.jsonwebtoken.java.jwt.version>3.7.0</io.jsonwebtoken.java.jwt.version>
+        <cn.stylefeng.pinyin4j.version>7.2.3</cn.stylefeng.pinyin4j.version>
+    </properties>
+
+<!--    <dependencyManagement>-->
+<!--        <dependencies>-->
+<!--            <dependency>-->
+<!--                <groupId>com.storlead.boot</groupId>-->
+<!--                <artifactId>storlead-dependencies</artifactId>-->
+<!--                <version>1.0</version>-->
+<!--                <type>pom</type>-->
+<!--                <scope>import</scope>-->
+<!--            </dependency>-->
+<!--        </dependencies>-->
+<!--    </dependencyManagement>-->
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- 统一三方依赖版本入口 -->
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-dependencies</artifactId>
+                <version>${revision}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-framework</artifactId>
+            <version>${revision}</version>
+            <type>pom</type>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-core</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-auth</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-redis</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mybatis</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <!-- swagger -->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>${swagger.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>${swagger.ui.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+            <version>${swagger.annotations.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>${fastjson.version}</version>
+        </dependency>
+
+
+
+        <!-- jwt -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>${io.jsonwebtoken.java.jwt.version}</version>
+        </dependency>
+
+        <!-- jwt -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>${io.jsonwebtoken.version}</version>
+        </dependency>
+
+        <!--  pinyin4j  -->
+        <dependency>
+            <groupId>cn.stylefeng.roses</groupId>
+            <artifactId>pinyin-sdk-pinyin4j</artifactId>
+            <version>${cn.stylefeng.pinyin4j.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+            <version>${redisson.version}</version>
+        </dependency>
+
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-dependencies</artifactId>-->
+<!--            <version>${spring.boot.version}</version>-->
+<!--        </dependency>-->
+
+        <!--HttpClient-->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${httpclient.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
+            <version>${knife4j.version}</version>
+        </dependency>
+        <!--            <dependency>-->
+        <!--                <groupId>cn.hutool</groupId>-->
+        <!--                <artifactId>hutool-all</artifactId>-->
+        <!--                <version>4.5.11</version>-->
+        <!--            </dependency>-->
+
+        <dependency>
+            <groupId>com.googlecode.aviator</groupId>
+            <artifactId>aviator</artifactId>
+            <version>${aviator.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>transmittable-thread-local</artifactId>
+            <version>${transmittable.thread.version}</version>
+        </dependency>
+
+        <!-- mybatis-plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+
+        <!--mysql-->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>${mysql.connector.version}</version>
+        </dependency>
+
+        <!-- 动态数据源 -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+            <version>${datasource.spring.boot.starter}</version>
+        </dependency>
+
+
+
+<!--            <dependency>-->
+<!--                <groupId>com.aliyun</groupId>-->
+<!--                <artifactId>credentials-java</artifactId>-->
+<!--                <version>0.3.4</version>-->
+<!--            </dependency>-->
+
+            <!-- druid -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+
+
+<!--            <dependency>-->
+<!--                <groupId>com.aliyun.kms</groupId>-->
+<!--                <artifactId>kms-transfer-client</artifactId>-->
+<!--                <version>0.1.0</version>-->
+<!--            </dependency>-->
+
+<!--            <dependency>-->
+<!--                <groupId>com.aliyun</groupId>-->
+<!--                <artifactId>sts20150401</artifactId>-->
+<!--                <version>1.1.6</version>-->
+<!--            </dependency>-->
+
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <version>2.7.0</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-web</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>${guava.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+            <version>${springdoc.version}</version>
+        </dependency>
+    </dependencies>
+    </dependencyManagement>
+    <build>
+        <pluginManagement>
+            <plugins>
+                <!-- maven-surefire-plugin for unit tests -->
+                <!-- requires 3.0.X+ for JUnit 5 -->
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>${maven-surefire-plugin.version}</version>
+                </plugin>
+                <!-- maven-compiler-plugin: Lombok + MapStruct -->
+                <!-- https://stackoverflow.com/questions/33483697/re-run-spring-boot-configuration-annotation-processor-to-update-generated-metada -->
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>${maven-compiler-plugin.version}</version>
+                    <configuration>
+<!--                        <configuration>-->
+<!--                            <source>17</source>-->
+<!--                            <target>17</target>-->
+<!--                        </configuration>-->
+<!--                        <annotationProcessorPaths>-->
+<!--                            <path>-->
+<!--                                <groupId>org.springframework.boot</groupId>-->
+<!--                                <artifactId>spring-boot-configuration-processor</artifactId>-->
+<!--                                <version>${spring.boot.version}</version>-->
+<!--                            </path>-->
+<!--                            <path>-->
+<!--                                <groupId>org.projectlombok</groupId>-->
+<!--                                <artifactId>lombok</artifactId>-->
+<!--                                <version>${lombok.version}</version>-->
+<!--                            </path>-->
+<!--                            <path>-->
+<!--                                <groupId>org.mapstruct</groupId>-->
+<!--                                <artifactId>mapstruct-processor</artifactId>-->
+<!--                                <version>${mapstruct.version}</version>-->
+<!--                            </path>-->
+<!--                        </annotationProcessorPaths>-->
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
+
+    <repositories>
+        <repository>
+            <id>aliyunmaven</id>
+            <name>aliyun</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+        </repository>
+
+        <repository>
+            <id>spring-milestones</id>
+            <name>Spring Milestones</name>
+            <url>https://repo.spring.io/milestone</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>spring-snapshots</id>
+            <name>Spring Snapshots</name>
+            <url>https://repo.spring.io/snapshot</url>
+            <releases>
+                <enabled>false</enabled>
+            </releases>
+        </repository>
+    </repositories>
+</project>

+ 41 - 0
storlead-api/pom.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-mail-service</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-api</artifactId>
+    <packaging>jar</packaging>
+    <name>storlead-api</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>com.storlead.api.StorleadMailServiceApplication</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 38 - 0
storlead-api/src/main/java/com/storlead/api/StorleadMailServiceApplication.java

@@ -0,0 +1,38 @@
+package com.storlead.api;
+
+import lombok.extern.slf4j.Slf4j;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.env.Environment;
+
+import java.net.InetAddress;
+
+@SpringBootApplication(scanBasePackages = "com.storlead")
+@MapperScan("com.storlead.sales.mail.mapper")
+@Slf4j
+public class StorleadMailServiceApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(StorleadMailServiceApplication.class, args);
+    }
+
+    @Bean
+    public ApplicationRunner startupEndpointLogger(Environment env) {
+        return args -> {
+            String port = env.getProperty("server.port", "8080");
+            String contextPath = env.getProperty("server.servlet.context-path", "");
+            String hostAddress = InetAddress.getLocalHost().getHostAddress();
+
+            String base = "http://" + hostAddress + ":" + port + contextPath;
+            log.info("==================================================");
+            log.info("API Base URL: {}/", base);
+            log.info("Swagger URL : {}/doc.html", base);
+            log.info("Swagger URL : {}/swagger-ui.html", base);
+            log.info("==================================================");
+        };
+    }
+}

+ 193 - 0
storlead-api/src/main/resources/application-dev.yml

@@ -0,0 +1,193 @@
+server:
+  port: 10010
+  tomcat:
+    max-swallow-size: -1
+    basedir: /app/temp
+  servlet:
+    context-path: /router/rest
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 20MB
+      max-request-size: 20MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    host: test1.storlead.com #test1.storlead.com
+    port: 59394
+    database: 10
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: bnoCWkyDqYA*ecT7FoL7
+
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_saas_platform_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+          driver-class-name: com.mysql.jdbc.Driver
+        sales:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_sales_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        management:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        otr:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://139.159.206.64:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
+#          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_tems_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+#          username: root
+#          password: rCgRgLjH99Xvg5BN
+#          driver-class-name: com.mysql.jdbc.Driver
+#        master:
+#          url: jdbc:mysql://139.159.206.64:65369/sp_tems_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+#          username: storlead_tems
+#          password: DW8YRN*5!6u&Ajswgjt
+#          driver-class-name: com.mysql.jdbc.Driver
+#        project:
+#          driver-class-name: com.mysql.jdbc.Driver
+#          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_project_dev?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+#          username: root
+#          password: rCgRgLjH99Xvg5BN
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath*:/mapper/*Mapper.xml,classpath*:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package:
+  typeEnumsPackage:
+#  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+#    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/${spring.application.name}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug
+
+
+environment: test
+
+# 站内消息、邮件模板中的前端站点根地址(覆盖 application.yml 默认值)
+domainname: https://test1.storlead.com
+
+storlead:
+  es:
+    enabled: false
+    host: 39.108.252.62
+    port: 9200
+    scheme: http
+    username: elastic
+    password: storlead123456

+ 234 - 0
storlead-api/src/main/resources/application-prod.yml

@@ -0,0 +1,234 @@
+#开发模式
+debug: false
+server:
+  port: 10086
+  tomcat:
+    max-swallow-size: -1
+    basedir: /app/sp/sp-tems/temp
+  servlet:
+    context-path: /router/rest
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 20MB
+      max-request-size: 20MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    database: 14
+    host: 192.168.0.210
+    lettuce:
+      pool:
+        max-active: 8   #最大连接数据库连接数,设 0 为没有限制
+        max-idle: 8     #最大等待连接中的数量,设 0 为没有限制
+        max-wait: -1ms  #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
+        min-idle: 0     #最小等待连接中的数量,设 0 为没有限制
+      shutdown-timeout: 100ms
+    password: 'B3f@NT4y%etekQaDkufd'
+    port: 56379
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/sp_tems_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+          username: storlead_tems
+          password: DW8YRN*5!6u&Ajswgjt
+        project:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/storlead_project_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_project
+          password: DW8YRN*5!6u&Ajswgjt
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath*:/mapper/*Mapper.xml,classpath*:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.tems.modules.*.entity
+  type-enums-package: com.storlead.tems.modules.console.enums;com.storlead.tems.modules.perform.enums;com.storlead.tems.modules.project.enums
+    #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /app/upload
+    #webapp文件路径
+    webapp: /app/upload
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+  #
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    bucketName: sp-tems
+
+
+domainname: https://project.storlead.com
+
+storlead:
+  es:
+    enabled: true
+    host: 39.108.252.62
+    port: 9200
+    scheme: http
+    username: elastic
+    password: storlead123456
+  project:
+    baseUrl: project.storlead.com
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/${spring.application.name}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug
+
+
+# 企业微信
+corp-wechat:
+  corpId: ww5323bd8ab4394132
+  # 这个是通讯录secret只能用来调用通讯录相关API使用
+  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+  corpAgentId: 1000025
+  corpAgentSecret: MqG2_t0HB4L2KN6MsOVdpL48XFB26VkUyTsKHT0T6y0
+
+environment: prod
+
+system:
+  config:
+    orgSyncMode: 0  # 0:系统维护 1:同步OA, 2:同步企业微信
+
+  license:
+    httpUrl: http://localhost:10010${server.servlet.context-path}
+    activationCode: "activationCode"
+
+file:
+  mode: 1
+  filePath: /files
+  active: prod
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /app/files/
+    avatar: /app/files/avatar/
+  windows:
+    path: D:\images\file\
+    avatar: D:\images\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
+
+  dify:
+    base-url: http://192.168.1.77/v1/
+    dataset-api-key: dataset-qfhXMNcjsRSwN5CK6aoWx3hl
+

+ 156 - 0
storlead-api/src/main/resources/application-test.yml

@@ -0,0 +1,156 @@
+server:
+  port: 10020
+  tomcat:
+    max-swallow-size: -1
+    basedir: /app/sp/smarttrade/temp/${spring.profiles.active}
+  servlet:
+    context-path: /router/rest
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 20MB
+      max-request-size: 20MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    host: test1.storlead.com
+    port: 59394
+    database: 14
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: bnoCWkyDqYA*ecT7FoL7
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_smart_trade_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+          driver-class-name: com.mysql.jdbc.Driver
+        sales:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_sales_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        management:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: rCgRgLjH99Xvg5BN
+
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath*:/mapper/*Mapper.xml,classpath*:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.tems.modules.*.entity
+  type-enums-package:
+    #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/okr/${spring.profiles.active}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug
+
+domainname: https://test1.storlead.com

+ 76 - 0
storlead-api/src/main/resources/application.yml

@@ -0,0 +1,76 @@
+spring:
+  profiles:
+    active: dev
+
+  mvc:
+    pathmatch:
+      matching-strategy: ant_path_matcher
+  #  security:
+  #    jwt:
+  #      # 自定义 secretKey
+  #      secretKey: VzMzUUNCY0g2cnRVQkR6OU5kTnVUY2tkZWlodFdkc0dpdVRwTmk4dnRWc2lKYmllRnEyekVLV29NWEJIM2IzSm1wRllacWdndFZmZFY0UTk0RmhxQm4zR1R4
+  #      expiryInHours: 2400
+  application:
+    name: storlead-mail-service
+  main:
+    allow-circular-references: true
+    allow-bean-definition-overriding: true
+  # 定时任务
+  quartz:
+    jdbc:
+      initialize-schema: never
+    job-store-type: jdbc
+    properties:
+      org:
+        quartz:
+          scheduler:
+            instanceId: AUTO
+            instanceName: clusteredScheduler
+          jobStore:
+            useProperties: true
+            isClustered: true
+            maxMisfiresToHandleAtATime: 1
+            tablePrefix: qrtz_
+   #         class: org.quartz.impl.jdbcjobstore.LocalDataSourceJobStore
+            class: org.quartz.impl.jdbcjobstore.JobStoreTX
+            clusterCheckinInterval: 3600000
+            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+          threadPool:
+            threadCount: 10
+            threadPriority: 5
+            threadsInheritContextClassLoaderOfInitializingThread: true
+            class: org.quartz.simpl.SimpleThreadPool
+  # 在上下文中没有Executor bean的情况下,Spring Boot会自动配置ThreadPoolTaskExecutor,并使用合理的默认值,这些默认值可以自动关联到异步任务执行(@EnableAsync)和Spring MVC异步请求处理。
+  #如果您已经在上下文中定义了一个自定义执行器,那么常规任务执行(即@EnableAsync)将透明地使用它,但是Spring MVC支持将不会被配置,因为它需要一个AsyncTaskExecutor实现(名为applicationTaskExecutor)。根据您的目标安排,您可以将执行程序更改为ThreadPoolTaskExecutor,或者定义一个ThreadPoolTaskExecutor和一个包装自定义执行程序的AsyncConfigurer。
+  task:
+    execution:
+      pool:
+        core-size: 10
+        max-size: 100
+        queue-capacity: 50
+
+# 站内消息 / 邮件模板跳转链接前缀(含协议,末尾不要 /)
+domainname: http://localhost:10010/router/rest
+
+# 多租户:无 tenant_id 列的表须列入忽略,否则 SQL 会拼 tenant_id 导致 Unknown column
+storlead:
+  es:
+    enabled: false
+    host: 127.0.0.1
+    port: 9200
+    scheme: http
+    username: elastic
+    password: ""
+  tenant:
+    ignore-tables:
+      - system_wechat_config
+      - system_oss_config
+      - sys_app
+  datasource:
+    # 子模块默认数据源(未标注 @DS 时生效;标注 @DS 则以注解为准)
+    module-default-enabled: true
+    module-defaults:
+      - packages:
+          - com.storlead.sales.mail.service
+          - com.storlead.sales.mail.mapper
+        datasource: sales

+ 306 - 0
storlead-dependencies/pom.xml

@@ -0,0 +1,306 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.storlead.boot</groupId>
+    <artifactId>storlead-dependencies</artifactId>
+    <version>${revision}</version>
+
+    <packaging>pom</packaging>
+    <name>${project.artifactId}</name>
+    <description>整个项目的依赖版本</description>
+
+    <properties>
+        <!-- 与根工程保持一致的统一版本号 -->
+        <revision>1.0</revision>
+        <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <spring.boot.starter.parent>2.7.0</spring.boot.starter.parent>
+        <spring.boot.version>2.7.0</spring.boot.version>
+        <knife4j.version>4.5.0</knife4j.version>
+        <spring-web.version>5.3.32</spring-web.version>
+        <jsoup.version>1.18.3</jsoup.version>
+        <mockito-inline.version>4.11.0</mockito-inline.version>
+        <lombok.version>1.18.36</lombok.version>
+        <spring.framework.version>5.3.20</spring.framework.version>
+        <spring.security.version>5.8.14</spring.security.version>
+        <fastjson.version>2.0.23</fastjson.version>
+        <!--    5.8.35-->
+        <hutool.version>5.4.3</hutool.version>
+        <springdoc.version>1.7.0</springdoc.version>
+        <swagger.ui.version>3.0.0</swagger.ui.version>
+        <swagger.version>3.0.0</swagger.version>
+        <swagger.annotations.version>1.5.22</swagger.annotations.version>
+        <spring.web.socket.version>1.5.3</spring.web.socket.version>
+
+        <!-- DB 相关 -->
+        <druid.version>1.1.24</druid.version>
+        <mybatis.version>3.5.17</mybatis.version>
+        <mybatis-plus.version>3.1.2</mybatis-plus.version>
+        <datasource.spring.boot.starter>2.5.4</datasource.spring.boot.starter>
+        <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
+        <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
+        <mysql.connector.version>5.1.47</mysql.connector.version>
+        <guava.version>33.4.0-jre</guava.version>
+        <aviator.version>5.2.7</aviator.version>
+        <transmittable.thread.version>2.14.0</transmittable.thread.version>
+        <google.code.gson.version>2.10.1</google.code.gson.version>
+        <squareup.okhttp.version>4.12.0</squareup.okhttp.version>
+        <okhttp.sse.version>4.10.0</okhttp.sse.version>
+        <spring.validation.version>2.6.3</spring.validation.version>
+        <apache.httpcore5.client5.version>5.2.1</apache.httpcore5.client5.version>
+        <apache.httpcore5.version>5.2.1</apache.httpcore5.version>
+        <apache.httpcore5-h2.version>5.2.1</apache.httpcore5-h2.version>
+        <apache.httpasyncclient.version>4.1.5</apache.httpasyncclient.version>
+        <io.jsonwebtoken.version>0.9.1</io.jsonwebtoken.version>
+        <io.jsonwebtoken.java.jwt.version>3.7.0</io.jsonwebtoken.java.jwt.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-web</artifactId>
+                <version>${spring-web.version}</version>
+            </dependency>
+
+
+
+
+            <dependency>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-swagger2</artifactId>
+                <version>${swagger.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-swagger-ui</artifactId>
+                <version>${swagger.ui.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.swagger</groupId>
+                <artifactId>swagger-annotations</artifactId>
+                <version>${swagger.annotations.version}</version>
+            </dependency>
+
+<!--            <dependency>-->
+<!--                <groupId>com.google.code.gson</groupId>-->
+<!--                <artifactId>gson</artifactId>-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpclient</artifactId>
+                <version>4.5.3</version>
+            </dependency>
+
+<!--            <dependency>-->
+<!--                <groupId>org.apache.httpcomponents.client5</groupId>-->
+<!--                <artifactId>httpclient5</artifactId>-->
+<!--                <version>${apache.httpcore5.client5.version}</version>-->
+<!--            </dependency>-->
+
+<!--            <dependency>-->
+<!--                <groupId>org.apache.httpcomponents.core5</groupId>-->
+<!--                <artifactId>httpcore5</artifactId>-->
+<!--                <version>${apache.httpcore5.version}</version>-->
+<!--            </dependency>-->
+
+            <!--JWT-->
+            <dependency>
+                <groupId>com.auth0</groupId>
+                <artifactId>java-jwt</artifactId>
+                <version>${io.jsonwebtoken.java.jwt.version}</version>
+            </dependency>
+
+            <!--  jwt 需要,移除会报错  -->
+            <dependency>
+                <groupId>io.jsonwebtoken</groupId>
+                <artifactId>jjwt</artifactId>
+                <version>${io.jsonwebtoken.version}</version>
+            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>org.apache.httpcomponents.core5</groupId>-->
+<!--                <artifactId>httpcore5-h2</artifactId>-->
+<!--                <version>${apache.httpcore5-h2.version}</version>-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpasyncclient</artifactId>
+                <version>${apache.httpasyncclient.version}</version>
+            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>com.github.xiaoymin</groupId>-->
+<!--                <artifactId>swagger-bootstrap-ui</artifactId>-->
+<!--                <version>1.8.7</version>-->
+<!--            </dependency>-->
+
+
+<!--            <dependency>-->
+<!--                <groupId>com.squareup.okhttp3</groupId>-->
+<!--                <artifactId>okhttp</artifactId>-->
+<!--            </dependency>-->
+
+<!--            <dependency>-->
+<!--                <groupId>com.squareup.okhttp3</groupId>-->
+<!--                <artifactId>okhttp-sse</artifactId>-->
+<!--                <version>${okhttp.sse.version}</version>-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring.boot.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.xiaoymin</groupId>
+                <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
+                <version>${knife4j.version}</version>
+            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>cn.hutool</groupId>-->
+<!--                <artifactId>hutool-all</artifactId>-->
+<!--                <version>4.5.11</version>-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>com.googlecode.aviator</groupId>
+                <artifactId>aviator</artifactId>
+                <version>${aviator.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>transmittable-thread-local</artifactId>
+                <version>${transmittable.thread.version}</version> <!-- 建议使用最新版本 -->
+            </dependency>
+
+            <!-- mybatis-plus -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <!--mysql-->
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>${mysql.connector.version}</version>
+            </dependency>
+
+            <!-- 动态数据源 -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+                <version>2.5.4</version>
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-generator</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <!-- druid -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid-spring-boot-starter</artifactId>
+                <version>${druid.version}</version>
+            </dependency>
+
+
+<!--            <dependency>-->
+<!--                <groupId>com.aliyun.kms</groupId>-->
+<!--                <artifactId>kms-transfer-client</artifactId>-->
+<!--                <version>0.1.0</version>-->
+<!--            </dependency>-->
+
+
+
+<!--            <dependency>-->
+<!--                <groupId>com.github.yulichang</groupId>-->
+<!--                <version>${mybatis-plus-join.version}</version>-->
+<!--            </dependency>-->
+
+<!--            <dependency>-->
+<!--                <groupId>org.java-websocket</groupId>-->
+<!--                <artifactId>Java-WebSocket</artifactId>-->
+<!--                <version>${spring.web.socket.version}</version>-->
+<!--            </dependency>-->
+
+            <!-- Web 相关 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-web</artifactId>
+                <version>2.7.0</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.springframework</groupId>
+                        <artifactId>spring-web</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+<!--            <dependency>-->
+<!--                <groupId>org.jsoup</groupId>-->
+<!--                <artifactId>jsoup</artifactId>-->
+<!--                <version>${jsoup.version}</version>-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>${guava.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-ui</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-test</artifactId>
+                <version>${spring.boot.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <artifactId>asm</artifactId>
+                        <groupId>org.ow2.asm</groupId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>org.mockito</groupId>
+                        <artifactId>mockito-core</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+        </dependencies>
+
+    </dependencyManagement>
+
+</project>

+ 25 - 0
storlead-framework/pom.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>storlead-mail-service</artifactId>
+        <groupId>com.storlead.boot</groupId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>storlead-framework</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>storlead-common</module>
+        <module>storlead-redis</module>
+        <module>storlead-mybatis</module>
+        <module>storlead-web</module>
+        <module>storlead-core</module>
+        <module>storlead-auth</module>
+    </modules>
+
+</project>

+ 31 - 0
storlead-framework/storlead-auth/pom.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-framework</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>storlead-auth</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+            <version>1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-core</artifactId>
+            <version>1.0</version>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 133 - 0
storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/jwt/JwtUtil.java

@@ -0,0 +1,133 @@
+package com.storlead.framework.auth.jwt;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.JSONObject;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import java.util.*;
+
+/**
+ * @program: storlead-storlead-mail-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-07-09 14:53
+ */
+public class JwtUtil {
+
+    /**
+     * 失效时间: 1 天 =24*3600*1000 ms
+     */
+    public static final long  EFFECTIVE_TIME = 1 * 24 * 3600 * 1000;
+    /**
+     * 私钥
+     */
+    public static final String APP_SECRET = "sp_user_2012";
+    /**
+     * token 前缀
+     */
+    public static final String APP_KEY = "sp_APP_KEY";
+    public static final String CLAIMS_KEY = "sp_CLAIMS_KEY";
+    /**
+     * 用户登录成功后生成Jwt
+     * 使用Hs256算法  私匙使用用户密码
+     *
+     * @param jsonInfo
+     * @param subjectKey
+     * @return
+     */
+    public static String createJWT(String jsonInfo,String subjectKey) {
+
+        //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
+        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+
+        long nowMillis = System.currentTimeMillis();
+        Date now = new Date(nowMillis);
+        //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
+        //EntityUtils.entityToMap(user)
+
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(CLAIMS_KEY, JSONObject.toJSON(jsonInfo));
+        //生成签发人
+        String subject = subjectKey;
+        //下面就是在为payload添加各种标准声明和私有声明了 JwtBuilder
+        //这里其实就是new一个JwtBuilder,设置jwt的body
+        JwtBuilder builder = Jwts.builder()
+                //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
+                .setClaims(claims)
+                //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
+                .setId(UUID.randomUUID().toString())
+                //iat: jwt的签发时间
+                .setIssuedAt(now)
+                //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
+                .setSubject(subject)
+                //设置签名使用的签名算法和签名使用的秘钥Algorithm
+                .signWith(signatureAlgorithm, APP_KEY);
+
+        return builder.compact();
+    }
+
+
+    /**
+     * Token的解密
+     * @param jwtToken 加密后的token
+     * @return
+     */
+    public static String parseJWT(String jwtToken) {
+        //签名秘钥,和生成的签名的秘钥一模一样
+        Claims claims = Jwts.parser()
+                //设置签名的秘钥
+                .setSigningKey(APP_KEY)
+                //设置需要解析的jwt
+                .parseClaimsJws(jwtToken).getBody();
+        LinkedHashMap linkedHashMap = (LinkedHashMap)claims.get(CLAIMS_KEY);
+        String json =JSONObject.toJSONString(linkedHashMap);
+        return json;
+    }
+
+
+
+    //设置token的密钥
+//    private final static String SECRET = "token123";
+
+    public static String getGptToken(String apiKey) {
+        String[] apiKeySet = apiKey.split("\\.");
+        String apiKeyId = apiKeySet[0];
+
+        Date signDate = new Date();
+        Date date = new Date(System.currentTimeMillis() + EFFECTIVE_TIME);//过期时间
+        Algorithm algorithm = Algorithm.HMAC256(apiKeySet[1]);//进行加密算法
+
+        //token的Header信息
+        Map<String, Object> map = new HashMap<>();
+        map.put("alg", "HS256");
+        map.put("sign_type", "SIGN");
+
+        String token= JWT.create()
+                //(token的Header信息)
+                .withHeader(map)
+                //设置当前签发时间(token的Payload信息)
+                .withClaim("timestamp",signDate)
+
+                //设置token过期时间(token的Payload信息)
+                .withClaim("exp",date)
+                //自定义存放用户id在tokne中(token的自定义Payload信息)
+                .withClaim("api_key", apiKeyId)
+                //自定义存放用户名在token中(token的自定义Payload信息)
+//                .withClaim("username", user.getName())
+                //(token的Signature信息)
+                .sign(algorithm);
+        return token;
+    }
+
+
+    public static void main(String[] args) {
+        String apiKey = "89934b295eb37f5d5750643ba2b18dae.nnR0vfmj3rgMOgD6";
+
+        System.out.println(getGptToken(apiKey));
+    }
+}

+ 188 - 0
storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/LoginUser.java

@@ -0,0 +1,188 @@
+package com.storlead.framework.auth.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @program: storlead-storlead-mail-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-06-16 17:26
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class LoginUser implements Serializable {
+
+    /**
+     * 登录人id
+     */
+    private Long id;
+
+    /**
+     * 登录人账号
+     */
+    private String userName;
+
+    /**
+     * 公司ID
+     */
+    private Long companyId;
+
+    /**
+     * 租户(企业)ID,与数据行 tenant_id 对齐;未单独下发时可由业务等同 companyId。
+     */
+    private Long tenantId;
+
+    /**
+     * 分公司ID
+     */
+    private Long subCompanyId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 部门ID
+     */
+    private String deptName;
+
+    /**
+     * 职位ID
+     */
+    private Long jobId;
+
+    /**
+     * 职位ID
+     */
+    private String jobName;
+
+    /**
+     * 测试
+     */
+    private Integer test;
+
+    /**
+     * 角色类型
+     */
+//    private UserRoleType roleType;
+
+    /**
+     * 登录人名字
+     */
+    private String realName;
+
+    /**
+     * 登录人密码
+     */
+    private String password;
+    /**
+     * 企业微信userid
+     */
+    private String xworkUserId;
+
+    /**
+     * 登录人名字
+     */
+    private String nickName;
+
+    /**
+     * 头像
+     */
+    private String avatar;
+
+    /**
+     * 生日
+     */
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern="yyyy-MM-dd")
+    private LocalDate birthday;
+
+    /**
+     * 性别(1:男 2:女)
+     */
+    private String sex;
+
+    /**
+     * 电子邮件
+     */
+    private String email;
+
+    /**
+     * 电话
+     */
+    private String phone;
+
+    private String mobile;
+
+    /**
+     * 状态(1:正常 0:冻结 )
+     */
+    private Integer enabled;
+
+
+    /**
+     * 是否领导:0、否;1、分公司领导;2、部门领导
+     */
+    private Integer isLeader;
+
+    private String dataScope;
+
+    @ApiModelProperty(value = "直接上级ID")
+    private Long managerId;
+
+    @ApiModelProperty(value = "领导人ID集合")
+    private String managers;
+
+    /**
+     * 部门路由
+     */
+    private String deptCode;
+
+    @ApiModelProperty(value = "是否是超管")
+    private Boolean isAdmin;
+
+    /**
+     * 所属子公司路由
+     */
+    private String subCompanyCode;
+
+    /**
+     * 所属公司路由
+     */
+    private String companyCode;
+
+    @ApiModelProperty(value = "部门岗位职责")
+    private String deptJobDes;
+
+    @ApiModelProperty(value = "岗位职责")
+    private String jobDes;
+
+    private Set<Long> roleIds;
+
+    private String apiUrl;
+
+    private String scopeMenuId;
+
+    private Integer commonScope;
+
+    private String commonScopeType;
+    /**
+     * 按钮权限code
+     */
+    private Set<String> btnRes;
+
+    private List<MenuPermis> permis;
+
+}

+ 17 - 0
storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/MenuPermis.java

@@ -0,0 +1,17 @@
+package com.storlead.framework.auth.vo;
+
+import lombok.Data;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-11 09:55
+ */
+@Data
+public class MenuPermis {
+    private Long id;
+    private String perm;
+    private Integer type;
+}
+

+ 56 - 0
storlead-framework/storlead-auth/src/main/java/com/storlead/framework/util/LoginUserUtil.java

@@ -0,0 +1,56 @@
+package com.storlead.framework.util;
+
+import com.storlead.framework.auth.vo.LoginUser;
+import com.storlead.framework.common.constant.UserCacheKeyConstants;
+import com.storlead.framework.core.context.Context;
+import com.storlead.framework.core.tenant.TenantContext;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * @author chenkq
+ */
+@Log4j2
+public class LoginUserUtil {
+    /**
+     * 获取当前用户信息
+     * @return
+     */
+    public static LoginUser getLoginUser(){
+        try {
+            LoginUser loginUser = Context.getContext().getAttribute(UserCacheKeyConstants.LOGIN_USER_INFO_KEY, LoginUser.class);
+            return loginUser;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public static String getLoginToken(){
+        try {
+            String loginToken = Context.getContext().getAttribute(UserCacheKeyConstants.LOGIN_USER_INFO_TOKEN_KEY, String.class);
+            return loginToken;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+    /**
+     * 获取当前用户信息
+     * @return
+     */
+    public static Long getCurrentUserId(){
+        try {
+            Long currentUserId = Context.getContext().getAttribute(UserCacheKeyConstants.LOGIN_USER_INFO_ID_KEY, Long.class);
+            return currentUserId;
+        } catch (Exception e) {
+            log.error("error - getCurrentUserId",e);
+            return null;
+        }
+    }
+
+    /**
+     * 当前租户 ID(请求线程内);未登录或未绑定时为 null。
+     */
+    public static Long getCurrentTenantId() {
+        return TenantContext.getTenantId();
+    }
+
+}

+ 212 - 0
storlead-framework/storlead-common/pom.xml

@@ -0,0 +1,212 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-framework</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>storlead-common</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <!--JWT-->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.12.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>${commons.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.17.4</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>sts20150401</artifactId>
+            <version>1.1.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+
+        <!--  jwt 需要,移除会报错  -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.googlecode.aviator</groupId>
+            <artifactId>aviator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <!-- excel导出-->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>4.1.2</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.stylefeng.roses</groupId>
+            <artifactId>pinyin-sdk-pinyin4j</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>4.1.2</version>
+            <scope>compile</scope>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml-schemas</artifactId>
+            <version>4.1.2</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
+        </dependency>
+
+        <dependency>
+            <groupId>ma.glasnost.orika</groupId>
+            <artifactId>orika-core</artifactId>
+            <version>1.5.4</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <!--   导出到word(循环图片)     -->
+        <!-- word导出  方式:easypoi-->
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-base</artifactId>
+            <version>4.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-web</artifactId>
+            <version>4.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-annotation</artifactId>
+            <version>4.4.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+            <version>1.15.3</version> <!-- 使用最新的版本 -->
+        </dependency>
+
+        <!-- commons -->
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>${commons.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>${commons.version}</version>
+        </dependency>
+
+<!--        <dependency>-->
+<!--            <groupId>com.squareup.okhttp3</groupId>-->
+<!--            <artifactId>okhttp</artifactId>-->
+<!--        </dependency>-->
+
+<!--        <dependency>-->
+<!--            <groupId>com.squareup.okhttp3</groupId>-->
+<!--            <artifactId>okhttp-sse</artifactId>-->
+<!--        </dependency>-->
+        <!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+            <version>2.3.32</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-quartz</artifactId>
+            <version>2.7.0</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+
+    </dependencies>
+</project>

+ 33 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/aviator/EqualsFunction.java

@@ -0,0 +1,33 @@
+package com.storlead.framework.common.aviator;
+
+import com.googlecode.aviator.runtime.function.AbstractFunction;
+import com.googlecode.aviator.runtime.function.FunctionUtils;
+import com.googlecode.aviator.runtime.type.AviatorBoolean;
+import com.googlecode.aviator.runtime.type.AviatorObject;
+
+import java.util.Map;
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2022-04-26 15:44
+ */
+public class EqualsFunction extends AbstractFunction {
+    @Override
+    public AviatorBoolean call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
+
+        String num1 = FunctionUtils.getStringValue(arg1, env);
+        String num2 = FunctionUtils.getStringValue(arg2, env);
+        return AviatorBoolean.valueOf(num1.equals(num2));
+    }
+
+    @Override
+    public String getName() {
+        return "myequals";
+    }
+
+    public static String getFunctionName() {
+        return "myequals";
+    }
+}

+ 33 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/aviator/IndexOfFunction.java

@@ -0,0 +1,33 @@
+package com.storlead.framework.common.aviator;
+
+import com.googlecode.aviator.runtime.function.AbstractFunction;
+import com.googlecode.aviator.runtime.function.FunctionUtils;
+import com.googlecode.aviator.runtime.type.AviatorBoolean;
+import com.googlecode.aviator.runtime.type.AviatorObject;
+
+import java.util.Map;
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2022-04-26 15:45
+ */
+public class IndexOfFunction extends AbstractFunction {
+    @Override
+    public AviatorBoolean call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
+
+        String num1 = FunctionUtils.getStringValue(arg1, env);
+        String num2 = FunctionUtils.getStringValue(arg2, env);
+        return AviatorBoolean.valueOf(num1.indexOf(num2) >= 0);
+    }
+
+    @Override
+    public String getName() {
+        return "myIndexOf";
+    }
+
+    public static String getFunctionName() {
+        return "myIndexOf";
+    }
+}

+ 31 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/aviator/MapKeyExistsFunction.java

@@ -0,0 +1,31 @@
+package com.storlead.framework.common.aviator;
+
+import com.googlecode.aviator.runtime.function.AbstractFunction;
+import com.googlecode.aviator.runtime.type.AviatorBoolean;
+import com.googlecode.aviator.runtime.type.AviatorObject;
+
+import java.util.Map;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-06-18 18:25
+ */
+public class MapKeyExistsFunction extends AbstractFunction {
+
+    @Override
+    public AviatorBoolean call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
+
+        Map<?, ?> map = (Map<?, ?>) arg1.getValue(env);
+        String key = arg2.stringValue(env);
+        return AviatorBoolean.valueOf(map.containsKey(key));
+    }
+
+    @Override
+    public String getName() {
+        return "containsKey";
+    }
+
+
+}

+ 34 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/aviator/NoIndexOfFunction.java

@@ -0,0 +1,34 @@
+package com.storlead.framework.common.aviator;
+
+import com.googlecode.aviator.runtime.function.AbstractFunction;
+import com.googlecode.aviator.runtime.function.FunctionUtils;
+import com.googlecode.aviator.runtime.type.AviatorBoolean;
+import com.googlecode.aviator.runtime.type.AviatorObject;
+
+import java.util.Map;
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2022-04-26 16:22
+ */
+public class NoIndexOfFunction extends AbstractFunction {
+    @Override
+    public AviatorBoolean call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
+
+        String num1 = FunctionUtils.getStringValue(arg1, env);
+        String num2 = FunctionUtils.getStringValue(arg2, env);
+        return AviatorBoolean.valueOf(num1.indexOf(num2) < 0);
+    }
+
+    @Override
+    public String getName() {
+        return "myNoIndexOf";
+    }
+
+    public static String getFunctionName() {
+        return "myNoIndexOf";
+    }
+}
+

+ 33 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/config/AutowiringSpringBeanJobFactory.java

@@ -0,0 +1,33 @@
+package com.storlead.framework.common.config;
+
+
+import org.quartz.spi.TriggerFiredBundle;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.scheduling.quartz.SpringBeanJobFactory;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @program: sp-salary-system
+ * @description:
+ * @author: chenkq
+ * @create: 2024-10-17 09:32
+ */
+@Component
+public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory {
+
+    @Resource
+    private AutowireCapableBeanFactory beanFactory;
+
+    @Override
+    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
+        // 使用父类方法创建 Job 实例
+        Object jobInstance = super.createJobInstance(bundle);
+        // 将 Spring 管理的 Bean 注入到 Job 实例中
+        beanFactory.autowireBean(jobInstance);
+        return jobInstance;
+    }
+}
+

+ 50 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/config/QuartzConfig.java

@@ -0,0 +1,50 @@
+package com.storlead.framework.common.config;
+
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+import java.util.Properties;
+
+/**
+ * @program: sp-salary-system
+ * @description:
+ * @author: chenkq
+ * @create: 2024-01-03 18:06
+ */
+@Log4j2
+@Configuration
+public class QuartzConfig {
+
+    @Resource
+    private DataSource dataSource;  // Spring Boot 自动配置的数据源
+
+    @Resource
+    private AutowiringSpringBeanJobFactory jobFactory;
+
+    @Bean
+    public SchedulerFactoryBean schedulerFactoryBean() {
+        SchedulerFactoryBean factory = new SchedulerFactoryBean();
+        factory.setDataSource(dataSource);  // 显式地设置 Quartz 使用的数据源
+        factory.setJobFactory(jobFactory);
+
+        Properties quartzProperties = new Properties();
+        quartzProperties.put("org.quartz.scheduler.instanceId", "AUTO");
+        quartzProperties.put("org.quartz.scheduler.instanceName", "clusteredScheduler");
+        quartzProperties.put("org.quartz.jobStore.isClustered", "true");
+        quartzProperties.put("org.quartz.jobStore.useProperties", "true");
+        quartzProperties.put("org.quartz.jobStore.clusterCheckinInterval", "60000");
+        quartzProperties.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate");
+        quartzProperties.put("org.quartz.jobStore.tablePrefix", "qrtz_");
+        quartzProperties.put("org.quartz.threadPool.threadCount", "10");
+        quartzProperties.put("org.quartz.threadPool.threadPriority", "5");
+        quartzProperties.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "false");
+        factory.setQuartzProperties(quartzProperties);
+
+        return factory;
+    }
+}

+ 32 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CacheConstant.java

@@ -0,0 +1,32 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @author: huangxutao
+ * @date: 2019-06-14
+ * @description: 缓存常量
+ */
+public interface CacheConstant {
+
+	/**
+	 * 缓存用户信息
+	 */
+	public static final String SYS_USERS_CACHE = "sys:cache:user";
+
+	/**
+	 * 全部部门信息缓存
+	 */
+	public static final String SYS_DEPARTS_CACHE = "sys:cache:depart:alldata";
+
+
+	/**
+	 * 全部部门ids缓存
+	 */
+	public static final String SYS_DEPART_IDS_CACHE = "sys:cache:depart:allids";
+
+
+	/**
+	 * 测试缓存key
+	 */
+	public static final String TEST_DEMO_CACHE = "test:demo";
+
+}

+ 41 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CodeGenerateInterface.java

@@ -0,0 +1,41 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-21 16:27
+ */
+public interface CodeGenerateInterface {
+    // 统一编码
+    String CODE_KEY = "";
+    //订单费用
+    String ORDER_COST_CODE= "OC";
+    //订单票据
+    String ORDER_INVOICE_CODE= "OI";
+    //回款记录
+    String ORDER_RECEIVE_CODE= "OP";
+    //退款记录
+    String ORDER_REFUND_CODE= "ORF";
+    //报销单
+    String ORDER_REIMBURSE_BILL_CODE= "ORB";
+    //客户/线索编码
+    String LIAISON_CODE= "LC";
+    //客户/线索编码
+    String CUSTOMER_CODE= "CC";
+    //商机编码
+    String BUSINESS_CODE= "BC";
+    //订单编码
+    String ORDER_FORM_CODE= "OFC";
+    //工单编码
+    String WORK_ORDER_CODE= "WOC";
+    // 报价编码
+    String QUOTATION_CODE= "QC";
+    //任务
+    String TASK_CODE= "TC";
+    //邮件
+    String EMAIL_CODE= "EM";
+
+    //邮件
+    String BULLETIN_CODE= "BUC";
+}

+ 82 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CommonConstant.java

@@ -0,0 +1,82 @@
+package com.storlead.framework.common.constant;
+
+public interface CommonConstant {
+
+	/**
+	 * 正常状态
+	 */
+	Integer STATUS_NORMAL = 1;
+
+	/**
+	 * 审批未通过状态
+	 */
+	Integer STATUS_NOT_ADOPT = -1;
+
+	/**
+	 * 禁用状态
+	 */
+	Integer STATUS_DISABLE = 0;
+
+	/**
+	 * 真
+	 */
+	Integer DEL_FLAG_0 = 0;
+
+    /**
+     * 假
+     */
+    Integer DEL_FLAG_1 = 1;
+
+
+	/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
+    Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
+    /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
+    Integer SC_OK_200 = 200;
+
+    /**
+     * 业务通用异常
+     */
+    Integer BUSSINESS_COMM_ERROR = 19000;
+
+    /**访问权限认证未通过 510*/
+    Integer SC_JEECG_NO_AUTHZ=510;
+
+    /** 登录用户Shiro权限缓存KEY前缀 */
+    public static String PREFIX_USER_SHIRO_CACHE  = "shiro:cache:com.storlead.sales.modules.shiro.authc.ShiroRealm.authorizationCache:";
+    /** 登录用户Token令牌缓存KEY前缀 */
+    String PREFIX_USER_TOKEN  = "prefix_user_token_";
+
+    /**
+     * 是否用户已被冻结 1(解冻)正常 2冻结
+     */
+    Integer USER_FREEZE = 2;
+
+    /**字典翻译文本后缀*/
+    String DICT_TEXT_SUFFIX = "_dictText";
+
+    String USER_CACHE = "userCache";
+
+    String CAPCHA_CACHE = "capchaCache";
+
+    //长度超限
+    String LENGTH_EXCEEDS_THE_LIMIT = "length exceeds the limit";
+
+    /**
+     * 撤回状态
+     */
+    Integer STATUS_WITH_DRAW = 2;
+
+    /**
+     * 待发放状态
+     */
+    Integer STATUS_NOT_ISSUED = 3;
+
+    /**
+     * 已发放状态
+     */
+    Integer STATUS_ISSUED = 4;
+    /**
+     * 已完成状态
+     */
+    Integer STATUS_COMPLETE = 5;
+}

+ 35 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DSConstants.java

@@ -0,0 +1,35 @@
+package com.storlead.framework.common.constant;
+
+import lombok.Data;
+
+/**
+ * DBConstants
+ *
+ * @author blank
+ * @date 2021-6-16 上午 10:17
+ */
+@Data
+public class DSConstants {
+
+    /**
+     * 数据源分组 master库
+     */
+    public static final String DATASOURCE_MASTER = "master";
+
+    /**
+     * SASA / CRM 等业务库(与 spring.datasource.dynamic.datasource.sales 的 key 一致)
+     */
+    public static final String DATASOURCE_SALES = "sales";
+
+    /**
+     * 数据源分组 oa库
+     */
+    public static final String DATASOURCE_OA = "oa";
+
+    /**
+     * 数据源分组 oa库
+     */
+    public static final String DATASOURCE_MANAGEMENT = "management";
+
+
+}

+ 60 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DataBaseConstant.java

@@ -0,0 +1,60 @@
+package com.storlead.framework.common.constant;
+/**
+ * 数据库上下文常量
+ */
+public interface DataBaseConstant {
+	//*********数据库类型****************************************
+	public static final String DB_TYPE_MYSQL = "MYSQL";
+	public static final String DB_TYPE_ORACLE = "ORACLE";
+	public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
+	public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
+
+	//*********系统上下文变量****************************************
+	/**
+	 * 数据-系统用户编码(对应登录用户账号)
+	 */
+	public static final String SYS_USER_CODE = "sysUserCode";
+	/**
+	 * 数据-系统用户编码(对应登录用户账号)
+	 */
+	public static final String SYS_USER_CODE_TABLE = "sys_user_code";
+
+	/**
+	 * 登录用户真实姓名
+	 */
+	public static final String SYS_USER_NAME = "sysUserName";
+	/**
+	 * 登录用户真实姓名
+	 */
+	public static final String SYS_USER_NAME_TABLE = "sys_user_name";
+	/**
+	 * 系统日期"yyyy-MM-dd"
+	 */
+	public static final String SYS_DATE = "sysDate";
+	/**
+	 * 系统日期"yyyy-MM-dd"
+	 */
+	public static final String SYS_DATE_TABLE = "sys_date";
+	/**
+	 * 系统时间"yyyy-MM-dd HH:mm"
+	 */
+	public static final String SYS_TIME = "sysTime";
+	/**
+	 * 系统时间"yyyy-MM-dd HH:mm"
+	 */
+	public static final String SYS_TIME_TABLE = "sys_time";
+	//*********系统上下文变量****************************************
+
+
+	//*********系统建表标准字段****************************************
+
+	/**
+	 * 业务流程状态
+	 */
+	public static final String BPM_STATUS = "bpmStatus";
+	/**
+	 * 业务流程状态
+	 */
+	public static final String BPM_STATUS_TABLE = "bpm_status";
+	//*********系统建表标准字段****************************************
+}

+ 12 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DefContants.java

@@ -0,0 +1,12 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-03-29 17:37
+ */
+public class DefContants {
+    public static String ACCESS_TOKEN = "token";
+    public static String AUTHORIZATION = "Authorization";
+}

+ 14 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/LongToStringSerializer.java

@@ -0,0 +1,14 @@
+package com.storlead.framework.common.constant;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+
+public class LongToStringSerializer extends JsonSerializer<Long> {
+
+    @Override
+    public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        gen.writeNumber(value);
+    }
+}

+ 25 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/RedisKeySaltConstant.java

@@ -0,0 +1,25 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-07-05 09:20
+ */
+public class RedisKeySaltConstant {
+    /**
+     * 操作成功
+     */
+    public static final String API_CODE_REDIS = "api_code_redis_key_";
+
+    /**
+     * 操作成功
+     */
+    public static final String REDIS_API_CODE_SERVICE_CODE_KEY = "api_code_redis_key_user_service_code";
+
+
+    /**
+     * 操作成功
+     */
+    public static final String REDIS_LOGIN_VALID_CODE_KEY = "redis_login_valid_code_key_";
+}

+ 11 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/SystemConstant.java

@@ -0,0 +1,11 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @program: sp-sales
+ * @description: 系统产量
+ * @author: chenkq
+ * @create: 2022-07-25 20:22
+ */
+public class SystemConstant {
+
+}

+ 24 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/UserCacheKeyConstants.java

@@ -0,0 +1,24 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @program: storlead-storlead-mail-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-06-17 15:39
+ */
+public class UserCacheKeyConstants {
+        /**
+         * 用户redisKEY
+         */
+        public static final String LOGIN_USER_INFO_KEY = "login_user_info";
+
+        /**
+         * 当前登录用户token key
+         */
+        public static final String LOGIN_USER_INFO_TOKEN_KEY = "login_user_token_key";
+
+        /**
+         * 登录用户id
+         */
+        public static final String LOGIN_USER_INFO_ID_KEY = "login_user_user_id";
+}

+ 20 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/dto/page/PageDTO.java

@@ -0,0 +1,20 @@
+package com.storlead.framework.common.dto.page;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * Common pagination request DTO used as API params.
+ */
+@Data
+public class PageDTO implements Serializable {
+    private static final long serialVersionUID = 4556484214576989834L;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer pageIndex = 1;
+
+    @ApiModelProperty(value = "页面大小", example = "10")
+    private Integer pageSize = 10;
+}

+ 36 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/dto/query/QueryBaseDTO.java

@@ -0,0 +1,36 @@
+package com.storlead.framework.common.dto.query;
+
+import com.storlead.framework.common.dto.page.PageDTO;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-04-22 14:38
+ */
+@Data
+public class QueryBaseDTO extends PageDTO {
+
+    @ApiModelProperty(value = "搜索参数")
+    private String blurry;
+
+    @ApiModelProperty(value = "菜单Id")
+    private Long scopeMenuId;
+
+    @ApiModelProperty(value = "数据权限范围sql")
+    private String dataScopeSql;
+
+    @ApiModelProperty(value = "通用查询条件")
+    private String commonQueryCondition;
+
+    @ApiModelProperty(value = "通用排序")
+    private String commonQueryOrderBy;
+
+    @ApiModelProperty(value = "排序方式")
+    private String sortMethod;
+
+    @ApiModelProperty(value = "默认0: 0 不是 ,1 是公共几口")
+    private Integer commonScope;
+}

+ 712 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/ecode/BCrypt.java

@@ -0,0 +1,712 @@
+package com.storlead.framework.common.ecode;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2021-10-25 18:54
+ */
+public class BCrypt {
+
+    // BCrypt parameters
+    private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
+
+    private static final int BCRYPT_SALT_LEN = 16;
+
+    // Blowfish parameters
+    private static final int BLOWFISH_NUM_ROUNDS = 16;
+
+    // Initial contents of key schedule
+    private static final int P_orig[] = { 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
+            0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5,
+            0xb5470917, 0x9216d5d9, 0x8979fb1b };
+
+    private static final int S_orig[] = { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96,
+            0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3,
+            0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+            0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1,
+            0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6,
+            0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16,
+            0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+            0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81,
+            0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a,
+            0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050,
+            0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+            0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724,
+            0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b,
+            0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb,
+            0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+            0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe,
+            0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760,
+            0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6,
+            0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+            0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada,
+            0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0,
+            0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191,
+            0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+            0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065,
+            0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e,
+            0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2,
+            0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+            0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521,
+            0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, 0x4b7a70e9, 0xb5b32944,
+            0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c,
+            0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
+            0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f,
+            0x3ebaefc9, 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c,
+            0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58,
+            0xdc0921bd, 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
+            0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38,
+            0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810,
+            0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc,
+            0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
+            0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1,
+            0xd7a3c76b, 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1,
+            0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978,
+            0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
+            0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef,
+            0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170,
+            0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b,
+            0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
+            0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a,
+            0x97e32d77, 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263,
+            0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61,
+            0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
+            0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2,
+            0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d,
+            0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1,
+            0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
+            0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3,
+            0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a,
+            0xe6e39f2b, 0xdb83adf7, 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e,
+            0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
+            0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3,
+            0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900,
+            0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399,
+            0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
+            0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a,
+            0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9,
+            0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f,
+            0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
+            0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0,
+            0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd,
+            0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab,
+            0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
+            0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88,
+            0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b,
+            0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8,
+            0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
+            0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33,
+            0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2,
+            0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79,
+            0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
+            0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d,
+            0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa,
+            0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa,
+            0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
+            0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578,
+            0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4,
+            0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, 0x3a39ce37, 0xd3faf5cf, 0xabc27737,
+            0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+            0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee,
+            0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a,
+            0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550,
+            0xba645bd6, 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+            0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a,
+            0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6,
+            0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44,
+            0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+            0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711,
+            0xc20ad9f8, 0xabcc5167, 0xccad925f, 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce,
+            0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd,
+            0x09072166, 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+            0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463,
+            0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc,
+            0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b,
+            0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+            0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a,
+            0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a,
+            0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065,
+            0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+            0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54,
+            0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623,
+            0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b,
+            0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+            0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c,
+            0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c,
+            0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3,
+            0x3ac372e6 };
+
+    // bcrypt IV: "OrpheanBeholderScryDoubt"
+    static private final int bf_crypt_ciphertext[] = { 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944,
+            0x6f756274 };
+
+    // Table for Base64 encoding
+    static private final char base64_code[] = { '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
+            'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+            'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1',
+            '2', '3', '4', '5', '6', '7', '8', '9' };
+
+    // Table for Base64 decoding
+    static private final byte index_64[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            0, 1, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+            12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, -1, -1, -1, -1, -1, -1, 28, 29, 30, 31, 32,
+            33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1 };
+    static final int MIN_LOG_ROUNDS = 4;
+    static final int MAX_LOG_ROUNDS = 31;
+
+    // Expanded Blowfish key
+    private int P[];
+
+    private int S[];
+
+    /**
+     * Encode a byte array using bcrypt's slightly-modified base64 encoding scheme. Note
+     * that this is <strong>not</strong> compatible with the standard MIME-base64
+     * encoding.
+     * @param d the byte array to encode
+     * @param len the number of bytes to encode
+     * @param rs the destination buffer for the base64-encoded string
+     * @exception IllegalArgumentException if the length is invalid
+     */
+    static void encode_base64(byte d[], int len, StringBuilder rs) throws IllegalArgumentException {
+        int off = 0;
+        int c1, c2;
+
+        if (len <= 0 || len > d.length) {
+            throw new IllegalArgumentException("Invalid len");
+        }
+
+        while (off < len) {
+            c1 = d[off++] & 0xff;
+            rs.append(base64_code[(c1 >> 2) & 0x3f]);
+            c1 = (c1 & 0x03) << 4;
+            if (off >= len) {
+                rs.append(base64_code[c1 & 0x3f]);
+                break;
+            }
+            c2 = d[off++] & 0xff;
+            c1 |= (c2 >> 4) & 0x0f;
+            rs.append(base64_code[c1 & 0x3f]);
+            c1 = (c2 & 0x0f) << 2;
+            if (off >= len) {
+                rs.append(base64_code[c1 & 0x3f]);
+                break;
+            }
+            c2 = d[off++] & 0xff;
+            c1 |= (c2 >> 6) & 0x03;
+            rs.append(base64_code[c1 & 0x3f]);
+            rs.append(base64_code[c2 & 0x3f]);
+        }
+    }
+
+    /**
+     * Look up the 3 bits base64-encoded by the specified character, range-checking againt
+     * conversion table
+     * @param x the base64-encoded value
+     * @return the decoded value of x
+     */
+    private static byte char64(char x) {
+        if (x < 0 || x >= index_64.length) {
+            return -1;
+        }
+        return index_64[x];
+    }
+
+    /**
+     * Decode a string encoded using bcrypt's base64 scheme to a byte array. Note that
+     * this is *not* compatible with the standard MIME-base64 encoding.
+     * @param s the string to decode
+     * @param maxolen the maximum number of bytes to decode
+     * @return an array containing the decoded bytes
+     * @throws IllegalArgumentException if maxolen is invalid
+     */
+    static byte[] decode_base64(String s, int maxolen) throws IllegalArgumentException {
+        StringBuilder rs = new StringBuilder();
+        int off = 0, slen = s.length(), olen = 0;
+        byte ret[];
+        byte c1, c2, c3, c4, o;
+
+        if (maxolen <= 0) {
+            throw new IllegalArgumentException("Invalid maxolen");
+        }
+
+        while (off < slen - 1 && olen < maxolen) {
+            c1 = char64(s.charAt(off++));
+            c2 = char64(s.charAt(off++));
+            if (c1 == -1 || c2 == -1) {
+                break;
+            }
+            o = (byte) (c1 << 2);
+            o |= (c2 & 0x30) >> 4;
+            rs.append((char) o);
+            if (++olen >= maxolen || off >= slen) {
+                break;
+            }
+            c3 = char64(s.charAt(off++));
+            if (c3 == -1) {
+                break;
+            }
+            o = (byte) ((c2 & 0x0f) << 4);
+            o |= (c3 & 0x3c) >> 2;
+            rs.append((char) o);
+            if (++olen >= maxolen || off >= slen) {
+                break;
+            }
+            c4 = char64(s.charAt(off++));
+            o = (byte) ((c3 & 0x03) << 6);
+            o |= c4;
+            rs.append((char) o);
+            ++olen;
+        }
+
+        ret = new byte[olen];
+        for (off = 0; off < olen; off++) {
+            ret[off] = (byte) rs.charAt(off);
+        }
+        return ret;
+    }
+
+    /**
+     * Blowfish encipher a single 64-bit block encoded as two 32-bit halves
+     * @param lr an array containing the two 32-bit half blocks
+     * @param off the position in the array of the blocks
+     */
+    private void encipher(int lr[], int off) {
+        int i, n, l = lr[off], r = lr[off + 1];
+
+        l ^= this.P[0];
+        for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
+            // Feistel substitution on left word
+            n = this.S[(l >> 24) & 0xff];
+            n += this.S[0x100 | ((l >> 16) & 0xff)];
+            n ^= this.S[0x200 | ((l >> 8) & 0xff)];
+            n += this.S[0x300 | (l & 0xff)];
+            r ^= n ^ this.P[++i];
+
+            // Feistel substitution on right word
+            n = this.S[(r >> 24) & 0xff];
+            n += this.S[0x100 | ((r >> 16) & 0xff)];
+            n ^= this.S[0x200 | ((r >> 8) & 0xff)];
+            n += this.S[0x300 | (r & 0xff)];
+            l ^= n ^ this.P[++i];
+        }
+        lr[off] = r ^ this.P[BLOWFISH_NUM_ROUNDS + 1];
+        lr[off + 1] = l;
+    }
+
+    /**
+     * Cycically extract a word of key material
+     * @param data the string to extract the data from
+     * @param offp a "pointer" (as a one-entry array) to the current offset into data
+     * @param signp a "pointer" (as a one-entry array) to the cumulative flag for
+     * non-benign sign extension
+     * @return correct and buggy next word of material from data as int[2]
+     */
+    private static int[] streamtowords(byte data[], int offp[], int signp[]) {
+        int i;
+        int words[] = { 0, 0 };
+        int off = offp[0];
+        int sign = signp[0];
+
+        for (i = 0; i < 4; i++) {
+            words[0] = (words[0] << 8) | (data[off] & 0xff);
+            words[1] = (words[1] << 8) | data[off]; // sign extension bug
+            if (i > 0) {
+                sign |= words[1] & 0x80;
+            }
+            off = (off + 1) % data.length;
+        }
+
+        offp[0] = off;
+        signp[0] = sign;
+        return words;
+    }
+
+    /**
+     * Cycically extract a word of key material
+     * @param data the string to extract the data from
+     * @param offp a "pointer" (as a one-entry array) to the current offset into data
+     * @return the next word of material from data
+     */
+    private static int streamtoword(byte data[], int offp[]) {
+        int signp[] = { 0 };
+        return streamtowords(data, offp, signp)[0];
+    }
+
+    /**
+     * Cycically extract a word of key material, with sign-extension bug
+     * @param data the string to extract the data from
+     * @param offp a "pointer" (as a one-entry array) to the current offset into data
+     * @return the next word of material from data
+     */
+    private static int streamtoword_bug(byte data[], int offp[]) {
+        int signp[] = { 0 };
+        return streamtowords(data, offp, signp)[1];
+    }
+
+    /**
+     * Initialise the Blowfish key schedule
+     */
+    private void init_key() {
+        this.P = P_orig.clone();
+        this.S = S_orig.clone();
+    }
+
+    /**
+     * Key the Blowfish cipher
+     * @param key an array containing the key
+     * @param sign_ext_bug true to implement the 2x bug
+     * @param safety bit 16 is set when the safety measure is requested
+     */
+    private void key(byte key[], boolean sign_ext_bug, int safety) {
+        int i;
+        int koffp[] = { 0 };
+        int lr[] = { 0, 0 };
+        int plen = this.P.length, slen = this.S.length;
+
+        for (i = 0; i < plen; i++) {
+            if (!sign_ext_bug) {
+                this.P[i] = this.P[i] ^ streamtoword(key, koffp);
+            }
+            else {
+                this.P[i] = this.P[i] ^ streamtoword_bug(key, koffp);
+            }
+        }
+
+        for (i = 0; i < plen; i += 2) {
+            encipher(lr, 0);
+            this.P[i] = lr[0];
+            this.P[i + 1] = lr[1];
+        }
+
+        for (i = 0; i < slen; i += 2) {
+            encipher(lr, 0);
+            this.S[i] = lr[0];
+            this.S[i + 1] = lr[1];
+        }
+    }
+
+    /**
+     * Perform the "enhanced key schedule" step described by Provos and Mazieres in "A
+     * Future-Adaptable Password Scheme" https://www.openbsd.org/papers/bcrypt-paper.ps
+     * @param data salt information
+     * @param key password information
+     * @param sign_ext_bug true to implement the 2x bug
+     * @param safety bit 16 is set when the safety measure is requested
+     */
+    private void ekskey(byte data[], byte key[], boolean sign_ext_bug, int safety) {
+        int i;
+        int koffp[] = { 0 }, doffp[] = { 0 };
+        int lr[] = { 0, 0 };
+        int plen = this.P.length, slen = this.S.length;
+        int signp[] = { 0 }; // non-benign sign-extension flag
+        int diff = 0; // zero iff correct and buggy are same
+
+        for (i = 0; i < plen; i++) {
+            int words[] = streamtowords(key, koffp, signp);
+            diff |= words[0] ^ words[1];
+            this.P[i] = this.P[i] ^ words[sign_ext_bug ? 1 : 0];
+        }
+
+        int sign = signp[0];
+
+        /*
+         * At this point, "diff" is zero iff the correct and buggy algorithms produced
+         * exactly the same result. If so and if "sign" is non-zero, which indicates that
+         * there was a non-benign sign extension, this means that we have a collision
+         * between the correctly computed hash for this password and a set of passwords
+         * that could be supplied to the buggy algorithm. Our safety measure is meant to
+         * protect from such many-buggy to one-correct collisions, by deviating from the
+         * correct algorithm in such cases. Let's check for this.
+         */
+        diff |= diff >> 16; /* still zero iff exact match */
+        diff &= 0xffff; /* ditto */
+        diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */
+        sign <<= 9; /* move the non-benign sign extension flag to bit 16 */
+        sign &= ~diff & safety; /* action needed? */
+
+        /*
+         * If we have determined that we need to deviate from the correct algorithm, flip
+         * bit 16 in initial expanded key. (The choice of 16 is arbitrary, but let's stick
+         * to it now. It came out of the approach we used above, and it's not any worse
+         * than any other choice we could make.)
+         *
+         * It is crucial that we don't do the same to the expanded key used in the main
+         * Eksblowfish loop. By doing it to only one of these two, we deviate from a state
+         * that could be directly specified by a password to the buggy algorithm (and to
+         * the fully correct one as well, but that's a side-effect).
+         */
+        this.P[0] ^= sign;
+
+        for (i = 0; i < plen; i += 2) {
+            lr[0] ^= streamtoword(data, doffp);
+            lr[1] ^= streamtoword(data, doffp);
+            encipher(lr, 0);
+            this.P[i] = lr[0];
+            this.P[i + 1] = lr[1];
+        }
+
+        for (i = 0; i < slen; i += 2) {
+            lr[0] ^= streamtoword(data, doffp);
+            lr[1] ^= streamtoword(data, doffp);
+            encipher(lr, 0);
+            this.S[i] = lr[0];
+            this.S[i + 1] = lr[1];
+        }
+    }
+
+    static long roundsForLogRounds(int log_rounds) {
+        if (log_rounds < 4 || log_rounds > 31) {
+            throw new IllegalArgumentException("Bad number of rounds");
+        }
+        return 1L << log_rounds;
+    }
+
+    /**
+     * Perform the central password hashing step in the bcrypt scheme
+     * @param password the password to hash
+     * @param salt the binary salt to hash with the password
+     * @param log_rounds the binary logarithm of the number of rounds of hashing to apply
+     * @param sign_ext_bug true to implement the 2x bug
+     * @param safety bit 16 is set when the safety measure is requested
+     * @return an array containing the binary hashed password
+     */
+    private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety) {
+        int rounds, i, j;
+        int cdata[] = bf_crypt_ciphertext.clone();
+        int clen = cdata.length;
+        byte ret[];
+
+        if (log_rounds < 4 || log_rounds > 31) {
+            throw new IllegalArgumentException("Bad number of rounds");
+        }
+        rounds = 1 << log_rounds;
+        if (salt.length != BCRYPT_SALT_LEN) {
+            throw new IllegalArgumentException("Bad salt length");
+        }
+
+        init_key();
+        ekskey(salt, password, sign_ext_bug, safety);
+        for (i = 0; i < rounds; i++) {
+            key(password, sign_ext_bug, safety);
+            key(salt, false, safety);
+        }
+
+        for (i = 0; i < 64; i++) {
+            for (j = 0; j < (clen >> 1); j++) {
+                encipher(cdata, j << 1);
+            }
+        }
+
+        ret = new byte[clen * 4];
+        for (i = 0, j = 0; i < clen; i++) {
+            ret[j++] = (byte) ((cdata[i] >> 24) & 0xff);
+            ret[j++] = (byte) ((cdata[i] >> 16) & 0xff);
+            ret[j++] = (byte) ((cdata[i] >> 8) & 0xff);
+            ret[j++] = (byte) (cdata[i] & 0xff);
+        }
+        return ret;
+    }
+
+    /**
+     * Hash a password using the OpenBSD bcrypt scheme
+     * @param password the password to hash
+     * @param salt the salt to hash with (perhaps generated using BCrypt.gensalt)
+     * @return the hashed password
+     */
+    public static String hashpw(String password, String salt) {
+        byte passwordb[];
+
+        passwordb = password.getBytes(StandardCharsets.UTF_8);
+
+        return hashpw(passwordb, salt);
+    }
+
+    /**
+     * Hash a password using the OpenBSD bcrypt scheme
+     * @param passwordb the password to hash, as a byte array
+     * @param salt the salt to hash with (perhaps generated using BCrypt.gensalt)
+     * @return the hashed password
+     */
+    public static String hashpw(byte passwordb[], String salt) {
+        BCrypt B;
+        String real_salt;
+        byte saltb[], hashed[];
+        char minor = (char) 0;
+        int rounds, off;
+        StringBuilder rs = new StringBuilder();
+
+        if (salt == null) {
+            throw new IllegalArgumentException("salt cannot be null");
+        }
+
+        int saltLength = salt.length();
+
+        if (saltLength < 28) {
+            throw new IllegalArgumentException("Invalid salt");
+        }
+
+        if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
+            throw new IllegalArgumentException("Invalid salt version");
+        }
+        if (salt.charAt(2) == '$') {
+            off = 3;
+        }
+        else {
+            minor = salt.charAt(2);
+            if ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b') || salt.charAt(3) != '$') {
+                throw new IllegalArgumentException("Invalid salt revision");
+            }
+            off = 4;
+        }
+
+        // Extract number of rounds
+        if (salt.charAt(off + 2) > '$') {
+            throw new IllegalArgumentException("Missing salt rounds");
+        }
+
+        if (off == 4 && saltLength < 29) {
+            throw new IllegalArgumentException("Invalid salt");
+        }
+        rounds = Integer.parseInt(salt.substring(off, off + 2));
+
+        real_salt = salt.substring(off + 3, off + 25);
+        saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
+
+        if (minor >= 'a') {
+            passwordb = Arrays.copyOf(passwordb, passwordb.length + 1);
+        }
+
+        B = new BCrypt();
+        hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0);
+
+        rs.append("$2");
+        if (minor >= 'a') {
+            rs.append(minor);
+        }
+        rs.append("$");
+        if (rounds < 10) {
+            rs.append("0");
+        }
+        rs.append(rounds);
+        rs.append("$");
+        encode_base64(saltb, saltb.length, rs);
+        encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs);
+        return rs.toString();
+    }
+
+    /**
+     * Generate a salt for use with the BCrypt.hashpw() method
+     * @param prefix the prefix value (default $2a)
+     * @param log_rounds the log2 of the number of rounds of hashing to apply - the work
+     * factor therefore increases as 2**log_rounds.
+     * @param random an instance of SecureRandom to use
+     * @return an encoded salt value
+     * @exception IllegalArgumentException if prefix or log_rounds is invalid
+     */
+    public static String gensalt(String prefix, int log_rounds, SecureRandom random) throws IllegalArgumentException {
+        StringBuilder rs = new StringBuilder();
+        byte rnd[] = new byte[BCRYPT_SALT_LEN];
+
+        if (!prefix.startsWith("$2")
+                || (prefix.charAt(2) != 'a' && prefix.charAt(2) != 'y' && prefix.charAt(2) != 'b')) {
+            throw new IllegalArgumentException("Invalid prefix");
+        }
+        if (log_rounds < 4 || log_rounds > 31) {
+            throw new IllegalArgumentException("Invalid log_rounds");
+        }
+
+        random.nextBytes(rnd);
+
+        rs.append("$2");
+        rs.append(prefix.charAt(2));
+        rs.append("$");
+        if (log_rounds < 10) {
+            rs.append("0");
+        }
+        rs.append(log_rounds);
+        rs.append("$");
+        encode_base64(rnd, rnd.length, rs);
+        return rs.toString();
+    }
+
+    /**
+     * Generate a salt for use with the BCrypt.hashpw() method
+     * @param prefix the prefix value (default $2a)
+     * @param log_rounds the log2 of the number of rounds of hashing to apply - the work
+     * factor therefore increases as 2**log_rounds.
+     * @return an encoded salt value
+     * @exception IllegalArgumentException if prefix or log_rounds is invalid
+     */
+    public static String gensalt(String prefix, int log_rounds) throws IllegalArgumentException {
+        return gensalt(prefix, log_rounds, new SecureRandom());
+    }
+
+    /**
+     * Generate a salt for use with the BCrypt.hashpw() method
+     * @param log_rounds the log2 of the number of rounds of hashing to apply - the work
+     * factor therefore increases as 2**log_rounds.
+     * @param random an instance of SecureRandom to use
+     * @return an encoded salt value
+     * @exception IllegalArgumentException if log_rounds is invalid
+     */
+    public static String gensalt(int log_rounds, SecureRandom random) throws IllegalArgumentException {
+        return gensalt("$2a", log_rounds, random);
+    }
+
+    /**
+     * Generate a salt for use with the BCrypt.hashpw() method
+     * @param log_rounds the log2 of the number of rounds of hashing to apply - the work
+     * factor therefore increases as 2**log_rounds.
+     * @return an encoded salt value
+     * @exception IllegalArgumentException if log_rounds is invalid
+     */
+    public static String gensalt(int log_rounds) throws IllegalArgumentException {
+        return gensalt(log_rounds, new SecureRandom());
+    }
+
+    public static String gensalt(String prefix) {
+        return gensalt(prefix, GENSALT_DEFAULT_LOG2_ROUNDS);
+    }
+
+    /**
+     * Generate a salt for use with the BCrypt.hashpw() method, selecting a reasonable
+     * default for the number of hashing rounds to apply
+     * @return an encoded salt value
+     */
+    public static String gensalt() {
+        return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
+    }
+
+    /**
+     * Check that a plaintext password matches a previously hashed one
+     * @param plaintext the plaintext password to verify
+     * @param hashed the previously-hashed password
+     * @return true if the passwords match, false otherwise
+     */
+    public static boolean checkpw(String plaintext, String hashed) {
+        return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));
+    }
+
+    /**
+     * Check that a password (as a byte array) matches a previously hashed one
+     * @param passwordb the password to verify, as a byte array
+     * @param hashed the previously-hashed password
+     * @return true if the passwords match, false otherwise
+     * @since 5.3
+     */
+    public static boolean checkpw(byte[] passwordb, String hashed) {
+        return equalsNoEarlyReturn(hashed, hashpw(passwordb, hashed));
+    }
+
+    static boolean equalsNoEarlyReturn(String a, String b) {
+        return MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8));
+    }
+}

+ 159 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/ecode/BCryptPasswordEncoder.java

@@ -0,0 +1,159 @@
+package com.storlead.framework.common.ecode;
+
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Service;
+
+import java.security.SecureRandom;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2021-10-25 18:50
+ */
+@Service
+@Primary
+public class BCryptPasswordEncoder implements PasswordEncoder {
+
+    private Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");
+
+    private final Log logger = LogFactory.getLog(getClass());
+
+    private final int strength;
+
+    private final BCryptVersion version;
+
+    private final SecureRandom random;
+
+    public BCryptPasswordEncoder() {
+        this(-1);
+    }
+
+    /**
+     * @param strength the log rounds to use, between 4 and 31
+     */
+    public BCryptPasswordEncoder(int strength) {
+        this(strength, null);
+    }
+
+    /**
+     * @param version the version of bcrypt, can be 2a,2b,2y
+     */
+    public BCryptPasswordEncoder(BCryptVersion version) {
+        this(version, null);
+    }
+
+    /**
+     * @param version the version of bcrypt, can be 2a,2b,2y
+     * @param random the secure random instance to use
+     */
+    public BCryptPasswordEncoder(BCryptVersion version, SecureRandom random) {
+        this(version, -1, random);
+    }
+
+    /**
+     * @param strength the log rounds to use, between 4 and 31
+     * @param random the secure random instance to use
+     */
+    public BCryptPasswordEncoder(int strength, SecureRandom random) {
+        this(BCryptVersion.$2A, strength, random);
+    }
+
+    /**
+     * @param version the version of bcrypt, can be 2a,2b,2y
+     * @param strength the log rounds to use, between 4 and 31
+     */
+    public BCryptPasswordEncoder(BCryptVersion version, int strength) {
+        this(version, strength, null);
+    }
+
+    /**
+     * @param version the version of bcrypt, can be 2a,2b,2y
+     * @param strength the log rounds to use, between 4 and 31
+     * @param random the secure random instance to use
+     */
+    public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random) {
+        if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
+            throw new IllegalArgumentException("Bad strength");
+        }
+        this.version = version;
+        this.strength = (strength == -1) ? 10 : strength;
+        this.random = random;
+    }
+
+    @Override
+    public String encode(CharSequence rawPassword) {
+        if (rawPassword == null) {
+            throw new IllegalArgumentException("rawPassword cannot be null");
+        }
+        String salt = getSalt();
+        return BCrypt.hashpw(rawPassword.toString(), salt);
+    }
+
+    private String getSalt() {
+        if (this.random != null) {
+            return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);
+        }
+        return BCrypt.gensalt(this.version.getVersion(), this.strength);
+    }
+
+    @Override
+    public boolean matches(CharSequence rawPassword, String encodedPassword) {
+        if (rawPassword == null) {
+            throw new IllegalArgumentException("rawPassword cannot be null");
+        }
+        if (encodedPassword == null || encodedPassword.length() == 0) {
+            this.logger.warn("Empty encoded password");
+            return false;
+        }
+        if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
+            this.logger.warn("Encoded password does not look like BCrypt");
+            return false;
+        }
+        return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
+    }
+
+    @Override
+    public boolean upgradeEncoding(String encodedPassword) {
+        if (encodedPassword == null || encodedPassword.length() == 0) {
+            this.logger.warn("Empty encoded password");
+            return false;
+        }
+        Matcher matcher = this.BCRYPT_PATTERN.matcher(encodedPassword);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Encoded password does not look like BCrypt: " + encodedPassword);
+        }
+        int strength = Integer.parseInt(matcher.group(2));
+        return strength < this.strength;
+    }
+
+    /**
+     * Stores the default bcrypt version for use in configuration.
+     *
+     * @author Lin Feng
+     */
+    public enum BCryptVersion {
+
+        $2A("$2a"),
+
+        $2Y("$2y"),
+
+        $2B("$2b");
+
+        private final String version;
+
+        BCryptVersion(String version) {
+            this.version = version;
+        }
+
+        public String getVersion() {
+            return this.version;
+        }
+
+    }
+}

+ 19 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/ecode/PasswordEncoder.java

@@ -0,0 +1,19 @@
+package com.storlead.framework.common.ecode;
+
+/**
+ * @program: sp-cloud
+ * @description:
+ * @author: chenkq
+ * @create: 2021-10-25 18:39
+ */
+
+public interface PasswordEncoder {
+
+    String encode(CharSequence var1);
+
+    boolean matches(CharSequence var1, String var2);
+
+    default boolean upgradeEncoding(String encodedPassword) {
+        return false;
+    }
+}

+ 26 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/enums/DataTypeEnum.java

@@ -0,0 +1,26 @@
+package com.storlead.framework.common.enums;
+
+/**
+ * @program: sp-sales-gotr
+ * @description:
+ * @author: chenkq
+ * @create: 2023-07-25 16:31
+ */
+public enum DataTypeEnum {
+
+    STRING(0, "字符串"),
+    INTEGER(1, "整数"),
+    DOUBLE(2, "小数"),
+    BIGDECIMAL(3, "金额"),
+    TIME(4, "时间"),
+    DATE(5, "日期"),
+    ;
+    private Integer code;
+
+    private String desc;
+
+    DataTypeEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+}

+ 79 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/enums/ErrorMsgCode.java

@@ -0,0 +1,79 @@
+package com.storlead.framework.common.enums;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-07-15 09:30
+ */
+public enum ErrorMsgCode {
+    /** 协议级返回码 */
+    UPGRADE(100, "您当前版本需要升级"),
+    UPGRADE_DATA(101, "需更新配置或数据"),
+    SUCCESS(200, "操作成功"),
+    REDIRECT(300, "重定向"),
+    LOGIN_FAIL(401, "登陆鉴权失败"),
+    PARAMETER_FAIL(400, "参数错误"),
+    NOT_FOUND(404, "接口未找到"),
+    FORBIDDEN(403, "权限不足"),
+    ERP_SERVICE_ERROR(700, "路由"),
+    OPTIONS_CODE(9999, "预处理"),
+
+    /** 系统级错误码 */
+    SYSTEM_ERROR(1001, "系统错误"),
+    SERVICE_PAUSE(1002, "服务暂停"),
+    SERVICE_BUSY(1003, "服务器忙"),
+    API_NOT_FOUND(1004, "接口不存在"),
+
+    /** 业务级错误码 */
+    WEB_ERROR(110000, "服务端web异常"),
+    FORM_VALIDATE_ERROR(110100, "输入参数有误"),
+    USER_ABSENT(110101, "缺少用户信息"),
+    SERVICE_ERROR(120000, "服务端service异常"),
+    DAO_ERROR(130000, "服务端dao异常"),
+    DB_ERROR(191000, "数据库访问异常"),
+    IO_ERROR(192000, "IO操作异常"),
+    CACHE_ERROR(193000, "cache操作异常"),
+    LOCAL_INVOKE_ERROR(194000, "Local api调用错误"),
+    RPC_INVOKE_ERROR(195000, "RPC调用错误"),
+    SECURITY_ERROR(196000, "安全错误"),
+
+    /** 其它错误码 */
+    OTHER_ERROR(900000, "其它错误"),
+
+    /** 其它错误码 */
+    D_400000(400000, "校验成功"),
+    D_400001(400001, "超过当天发送次数"),
+    D_400002(400002, "发送验证码频繁,请稍后再试"),
+    D_400003(400003, "发送失败,请稍后再试"),
+    D_400004(400004, "请先发送验证码"),
+    D_400005(400005, "验证码不正确或已使用,请重新输入"),
+    D_400006(400006, "验证码已过有效期, 请重新发送"),
+    D_400007(400007, "验证码已失效, 请重新发送"),
+    D_400008(400008, "验证码不正确, 请重新输入"),
+    D_400009(400009, "请输入正确的手机号码"),
+
+
+    /** 其它错误码 */
+    WECHAT_SMS_100006(100006, "微信消息模板不存在"),
+    WECHAT_SMS_100007(100007, ""),
+    WECHAT_SMS_100008(100008, "消息接收人不能为空"),
+    WECHAT_SMS_100009(100009, "未找到消息接收用户"),
+    ;
+
+    private final Integer code;
+    private final String msg;
+
+    ErrorMsgCode(Integer code,String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getMsg() {
+        return  msg;
+    }
+}

+ 42 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/enums/ResultCode.java

@@ -0,0 +1,42 @@
+package com.storlead.framework.common.enums;
+
+import lombok.Getter;
+
+
+/**
+ * 定义ResultCode
+ */
+@Getter
+public enum ResultCode {
+    /**
+     * 操作成功
+     */
+    SUCCESS(200, "操作成功"),
+
+    FAILED(-1, "处理失败"),
+
+    ERROR_500(500, "500"),
+
+    LOGIN_FAIL(401, "未登录"),
+
+    FORBIDDEN(403, "权限不足"),
+
+    RULES_FAILED(1002, "规则校验失败"),
+
+    VALIDATE_FAILED(1003, "参数校验失败"),
+
+    PARAM_IS_NEEDED(1004, "缺少必传参数"),
+
+    SMS_CHECK_IS_NEEDED(1005, "验证码失效或不存在,请先验证短信验证码"),
+
+    ERROR(5000, "未知错误")
+    ;
+
+    public int code;
+    public String msg;
+
+    ResultCode(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+}

+ 6 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * 基础的通用类,和框架无关
+ *
+ * 例如说,CommonResult 为通用返回
+ */
+package com.storlead.framework.common;

+ 90 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/properties/UrlChainDefinitionPorperties.java

@@ -0,0 +1,90 @@
+package com.storlead.framework.common.properties;
+
+import lombok.Data;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-07-04 16:16
+ */
+@Data
+@Configuration
+public class UrlChainDefinitionPorperties {
+    private String contextPath = "/api" ;
+	private String [] authWhitelist = {
+			"/",
+			"/lingcun/common/view/**",
+			"/lingcun/common/download/**",
+			"/lingcun/common/pdf/**",
+			"/generic/**",
+			"/doc.html",
+			"/**/*.js",
+			"/**/*.css",
+			"/**/*.html",
+			"/**/*.svg",
+			"/**/*.pdf",
+			"/**/*.jpg",
+			"/**/*.png",
+			"/**/*.ico",
+			"/actuator",
+			"/actuator/health",
+			"/actuator/**",
+			"/app/**",
+			"/h5/**",
+			"/**/*.ttf",
+			"/**/*.woff",
+			"/druid/**",
+			"/swagger-ui.html",
+			"/**/swagger-ui",
+			"/swagger**/**",
+			"/webjars/**",
+			"/v2/**",
+			"/xwork/**",
+			"/lingcun/getPhoneCode",
+			"/lingcun/loginByMobile",
+			"/lingcun/login",
+			"/sys/auth/external_login",
+			"/lingcun/phone-login",
+			"/sys/auth/login",
+			"/sys/auth/tenant/login",
+			"/sys/auth/redirect",
+			"/actuator/metrics/**",
+			"/actuator/httptrace/**",
+			"/actuator/redis/**",
+			"/sys/common/403",
+			"/sys/common/403",
+			"/reward/resource",
+			"/xwork/login",
+			"/xwork/code-login",
+			"/view/thumbnail",
+			"/sys/custom/settings/get",
+			"/tenant/license/getLicenseCode",
+			"/analysis-report/edit",
+			"/gpt/chatWebSocket/**",
+			"/sys/user/exchangeToken",
+			"/sys/menu/allMenulist",
+			"/sys/dict/pageDict",
+			"/mail/bind_mail_customer_all",
+			"/mail/bind_mail_customer",
+			"/sys/menu/update_menu_icon",
+			"/mail/file/images",
+			"/mail/file/cache_image_file",
+			"/mail/import_mail_eml",
+			"/mail/import_mail_eml_zip",
+			"/sys/test/receive_mail_test",
+			"/mail/list.json",
+			"/mail/listUrl",
+			"/mail/v1/file/content",
+			"/ram/oss/get_post_signature_for_oss_upload",
+			"/sys/permission/getUserAccessResourceTest",
+			"/mail/draft_download_url",
+			"/mail/download_url",
+			"/wxapi/wxclientmenu/**",
+			"/wxapi/wxclientmenu/*",
+			"/wxapi/getauth",
+			"/account/auth/system/login",
+			"/account/auth/tenant/login"
+	};
+}

+ 40 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/property/YamlPropertyResourceFactory.java

@@ -0,0 +1,40 @@
+package com.storlead.framework.common.property;
+
+import org.springframework.boot.env.YamlPropertySourceLoader;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.core.io.support.PropertySourceFactory;
+import org.springframework.lang.Nullable;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.Properties;
+
+/**
+ * 让 PropertySourceFactory 支持读取 yml/yaml格式配置
+ *
+ * @author blank
+ * @since 2021-8-13 下午 4:08
+ */
+public class YamlPropertyResourceFactory implements PropertySourceFactory {
+    /**
+     * Create a {@link PropertySource} that wraps the given resource.
+     *
+     * @param name     the name of the property source
+     * @param encodedResource the resource (potentially encoded) to wrap
+     * @return the new {@link PropertySource} (never {@code null})
+     * @throws IOException if resource resolution failed
+     */
+    @Override
+    public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource encodedResource) throws IOException {
+        String resourceName = Optional.ofNullable(name).orElse(encodedResource.getResource().getFilename());
+        if (resourceName.endsWith(".yml") || resourceName.endsWith(".yaml")) {//yaml资源文件
+            List<PropertySource<?>> yamlSources = new YamlPropertySourceLoader().load(resourceName, encodedResource.getResource());
+            return yamlSources.get(0);
+        } else {//返回空的Properties
+            return new PropertiesPropertySource(resourceName, new Properties());
+        }
+    }
+}

+ 183 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/result/Result.java

@@ -0,0 +1,183 @@
+package com.storlead.framework.common.result;
+
+import com.storlead.framework.common.enums.ErrorMsgCode;
+import com.storlead.framework.common.enums.ResultCode;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ *   接口返回数据格式
+ * @author scott
+ * @email jeecgos@163.com
+ * @date  2019年1月19日
+ */
+@Data
+@ApiModel(value="接口返回对象", description="接口返回对象")
+public class Result<T> implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	@ApiModelProperty(value = "成功标志")
+	public final static String successStr = "OK";
+	/**
+	 * 成功标志
+	 */
+	@ApiModelProperty(value = "成功标志")
+	private boolean success = true;
+
+	/**
+	 * 返回处理消息
+	 */
+	@ApiModelProperty(value = "返回处理消息")
+	private String message = "操作成功!";
+
+	/**
+	 * 返回代码
+	 */
+	@ApiModelProperty(value = "返回代码")
+	private Integer code = 200;
+
+	/**
+	 * 返回数据对象 result
+	 */
+	@ApiModelProperty(value = "返回数据对象")
+	private T result;
+
+	/**
+	 * 时间戳
+	 */
+	@ApiModelProperty(value = "时间戳")
+	private long timestamp;
+
+	public Result() {
+	  this.timestamp = System.currentTimeMillis();
+	}
+
+	public T getResult(){
+		return result;
+	}
+
+	 public void setResult(T result) {
+	  this.result = result;
+	 }
+	 public Result<T> success(String message) {
+		this.message = message;
+		this.code = ResultCode.SUCCESS.getCode();
+		this.success = true;
+		this.timestamp = System.currentTimeMillis();
+		return this;
+	}
+
+
+	public static Result<Object> ok() {
+		Result<Object> r = new Result<Object>();
+		r.setSuccess(true);
+		r.setCode(ResultCode.SUCCESS.getCode());
+		r.setMessage("成功");
+		r.setTimestamp(System.currentTimeMillis());
+		return r;
+	}
+
+	public static <T> Result<T> ok(String msg) {
+		Result<T> r = new Result<T>();
+		r.setSuccess(true);
+		r.setCode(ResultCode.SUCCESS.getCode());
+		r.setMessage(msg);
+		r.setTimestamp(System.currentTimeMillis());
+		return r;
+	}
+
+	public static <T> Result<T> ok(T result) {
+		Result<T> r = new Result<T>();
+		r.setSuccess(true);
+		r.setCode(ResultCode.SUCCESS.getCode());
+		r.setResult(result);
+		r.setTimestamp(System.currentTimeMillis());
+		return r;
+	}
+
+	public static <T> Result<T> result(T result) {
+		Result<T> r = new Result<T>();
+		r.setSuccess(true);
+		r.setCode(ResultCode.SUCCESS.getCode());
+		r.setResult(result);
+		r.setTimestamp(System.currentTimeMillis());
+		return r;
+	}
+
+//	public static <T> Result<T> date(T result) {
+//		Result<T> r = new Result<T>();
+//		r.setSuccess(true);
+//		r.setCode(ResultCode.SC_OK_200.getCode());
+//		r.setData(result);
+//		r.setTimestamp(System.currentTimeMillis());
+//		return r;
+//	}
+
+	public static <T> Result<T> error(T errorData) {
+		return error(ResultCode.RULES_FAILED.getCode(), errorData);
+	}
+
+	public static <T> Result<T> error(T errorData,String msg) {
+		return error(ResultCode.RULES_FAILED.getCode(), errorData,msg);
+	}
+
+	public static Result<Object> error(String msg) {
+		return error(ResultCode.ERROR_500.getCode(), msg);
+	}
+
+	public static Result<Object> error(ErrorMsgCode msgCode) {
+		return error(msgCode.getCode(), msgCode.getMsg());
+	}
+
+	public static Result<Object> error(ResultCode code) {
+		return error(code.getCode(), code.getMsg());
+	}
+
+	public static Result<Object> error(int code, String msg) {
+		Result<Object> r = new Result<Object>();
+		r.setCode(code);
+		r.setMessage(msg);
+		r.setSuccess(false);
+		r.setTimestamp(System.currentTimeMillis());
+		return r;
+	}
+
+	public static <T>Result<T> error(int code,T errorData, String msg) {
+		Result<T> r = new Result<T>();
+		r.setCode(code);
+		r.setMessage(msg);
+		r.setResult(errorData);
+		r.setSuccess(false);
+		r.setTimestamp(System.currentTimeMillis());
+		return r;
+	}
+
+	public static <T>Result<T> error(int code,T errorData) {
+		Result<T> r = new Result<T>();
+		r.setCode(code);
+		r.setMessage("");
+		r.setResult(errorData);
+		r.setSuccess(false);
+		r.setTimestamp(System.currentTimeMillis());
+		return r;
+	}
+
+	public Result<T> error500(String message) {
+		this.message = message;
+		this.code = ResultCode.ERROR_500.getCode();
+		this.success = false;
+		this.timestamp = System.currentTimeMillis();
+		return this;
+	}
+	/**
+	 * 无权限访问返回结果
+	 */
+	public static Result<Object> noauth(String msg) {
+		return error(ResultCode.FORBIDDEN.getCode(), msg);
+	}
+
+}

+ 44 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/MatchTypeEnum.java

@@ -0,0 +1,44 @@
+package com.storlead.framework.common.system.query;
+
+import com.storlead.framework.common.util.ConvertUtils;
+
+/**
+ * 查询链接规则
+ *
+ * @Author Sunjianlei
+ */
+public enum MatchTypeEnum {
+
+    WHERE(" WHERE"),
+    AND(" AND"),
+    OR(" OR");
+
+    private String value;
+
+    MatchTypeEnum(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public static MatchTypeEnum getByValue(Object value) {
+        if (ConvertUtils.isEmpty(value)) {
+            return null;
+        }
+        return getByValue(value.toString());
+    }
+
+    public static MatchTypeEnum getByValue(String value) {
+        if (ConvertUtils.isEmpty(value)) {
+            return null;
+        }
+        for (MatchTypeEnum val : values()) {
+            if (val.getValue().toLowerCase().equals(value.toLowerCase())) {
+                return val;
+            }
+        }
+        return null;
+    }
+}

+ 55 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/QueryCondition.java

@@ -0,0 +1,55 @@
+package com.storlead.framework.common.system.query;
+
+import java.io.Serializable;
+
+public class QueryCondition implements Serializable {
+
+	private static final long serialVersionUID = 4740166316629191651L;
+
+	private String field;
+	private String type;
+	private String rule;
+	private String val;
+
+	public String getField() {
+		return field;
+	}
+
+	public void setField(String field) {
+		this.field = field;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	public String getRule() {
+		return rule;
+	}
+
+	public void setRule(String rule) {
+		this.rule = rule;
+	}
+
+	public String getVal() {
+		return val;
+	}
+
+	public void setVal(String val) {
+		this.val = val;
+	}
+
+	@Override
+	public String toString(){
+		StringBuffer sb =new StringBuffer();
+		if(field == null || "".equals(field)){
+			return "";
+		}
+		sb.append(this.field).append(" ").append(this.rule).append(" ").append(this.type).append(" ").append(this.val);
+		return sb.toString();
+	}
+}

+ 71 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/QueryRuleEnum.java

@@ -0,0 +1,71 @@
+package com.storlead.framework.common.system.query;
+
+import com.storlead.framework.common.util.ConvertUtils;
+
+/**
+ * Query 规则 常量
+ * @Author Scott
+ * @Date 2019年02月14日
+ */
+public enum QueryRuleEnum {
+
+    GT(">","gt","大于"),
+    GE(">=","ge","大于等于"),
+    LT("<","lt","小于"),
+    LE("<=","le","小于等于"),
+    EQ("=","eq","等于"),
+    NE("!=","ne","不等于"),
+    IN("IN","in","包含"),
+    LIKE("LIKE","like","全模糊"),
+    LEFT_LIKE("LEFT_LIKE","left_like","左模糊"),
+    RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
+    SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段");
+
+    private String value;
+
+    private String condition;
+
+    private String msg;
+
+    QueryRuleEnum(String value, String condition, String msg){
+        this.value = value;
+        this.condition = condition;
+        this.msg = msg;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public String getCondition() {
+		return condition;
+	}
+
+	public void setCondition(String condition) {
+		this.condition = condition;
+	}
+
+	public static QueryRuleEnum getByValue(String value){
+    	if(ConvertUtils.isEmpty(value)) {
+    		return null;
+    	}
+        for(QueryRuleEnum val :values()){
+            if (val.getValue().equals(value) || val.getCondition().equals(value)){
+                return val;
+            }
+        }
+        return  null;
+    }
+}

+ 88 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/thread/ThreadPoolUtil.java

@@ -0,0 +1,88 @@
+package com.storlead.framework.common.thread;
+
+
+import java.util.List;
+import java.util.concurrent.*;
+
+/**
+ * @program: sp-project-system
+ * @description: 线程池工具类
+ * @author: chenkq
+ * @create: 2021-09-13 08:55
+ */
+public class ThreadPoolUtil {
+    /**
+     * 核心线程池的数量,同时能够执行的线程数量
+     */
+    private final static int corePoolSize  = Runtime.getRuntime().availableProcessors() * 2 + 1;
+    /**
+     * 最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
+     */
+    private final static int maxPoolSize  = corePoolSize;
+    /**
+     * 存活时间
+     */
+    private final static long keepAliveTime = 60L;
+    private final static TimeUnit unit = TimeUnit.SECONDS;
+
+    /**
+     * 线程等待队列
+     */
+    private static LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(2000);
+
+
+
+    /**
+     * 线程池对象
+     */
+    private static ThreadPoolExecutor threadExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize,
+            keepAliveTime, unit, queue, new ThreadPoolExecutor.AbortPolicy());
+
+    public static ThreadPoolExecutor getThreadExecutor() {
+        return threadExecutor;
+    }
+
+
+    /**
+     * 向线程池提交一个任务,返回线程结果
+     * @param r
+     * @return
+     */
+    public static Future<?> submit(Callable<?> r) {
+        return threadExecutor.submit(r);
+    }
+
+    /**
+     * 向线程池提交一个任务,返回线程结果
+     * @param rs
+     * @return
+     */
+    public static List<Future<?>> submits(List rs) {
+        try {
+            List list = threadExecutor.invokeAll(rs);
+            return list;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 向线程池提交一个任务,不关心处理结果
+     * @param r
+     */
+    public static void execute(Runnable r) {
+        threadExecutor.execute(r);
+    }
+
+    /** 获取当前线程池线程数量 */
+    public static int getSize() {
+        return threadExecutor.getPoolSize();
+    }
+
+    /** 获取当前活动的线程数量 */
+    public static int getActiveCount() {
+        return threadExecutor.getActiveCount();
+    }
+}
+

+ 31 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/CloseUtil.java

@@ -0,0 +1,31 @@
+package com.storlead.framework.common.util;
+
+import java.io.Closeable;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-08-22 14:56
+ */
+public class CloseUtil {
+    public static void close(Closeable closeable) {
+        if (null != closeable) {
+            try {
+                closeable.close();
+            } catch (Exception e) {
+                // 静默关闭
+            }
+        }
+    }
+
+    public static void close(AutoCloseable closeable) {
+        if (null != closeable) {
+            try {
+                closeable.close();
+            } catch (Exception e) {
+                // 静默关闭
+            }
+        }
+    }
+}

+ 652 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ConvertUtils.java

@@ -0,0 +1,652 @@
+package com.storlead.framework.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.util.CollectionUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.sql.Date;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+public class ConvertUtils {
+	public static boolean isEmpty(Object object) {
+		if (object == null) {
+			return (true);
+		}
+		if ("".equals(object)) {
+			return (true);
+		}
+		if ("null".equals(object)) {
+			return (true);
+		}
+		return (false);
+	}
+
+	public static boolean isNotEmpty(Object object) {
+		if (object != null && !object.equals("") && !object.equals("null")) {
+			return (true);
+		}
+		return (false);
+	}
+
+	public static String decode(String strIn, String sourceCode, String targetCode) {
+		String temp = code2code(strIn, sourceCode, targetCode);
+		return temp;
+	}
+
+	public static String StrToUTF(String strIn, String sourceCode, String targetCode) {
+		strIn = "";
+		try {
+			strIn = new String(strIn.getBytes("ISO-8859-1"), "GBK");
+		} catch (UnsupportedEncodingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return strIn;
+
+	}
+
+	private static String code2code(String strIn, String sourceCode, String targetCode) {
+		String strOut = null;
+		if (strIn == null || (strIn.trim()).equals("")) {
+			return strIn;
+		}
+		try {
+			byte[] b = strIn.getBytes(sourceCode);
+			for (int i = 0; i < b.length; i++) {
+				System.out.print(b[i] + "  ");
+			}
+			strOut = new String(b, targetCode);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+		return strOut;
+	}
+
+	public static int getInt(String s, int defval) {
+		if (s == null || s == "") {
+			return (defval);
+		}
+		try {
+			return (Integer.parseInt(s));
+		} catch (NumberFormatException e) {
+			return (defval);
+		}
+	}
+
+	public static int getInt(String s) {
+		if (s == null || s == "") {
+			return 0;
+		}
+		try {
+			return (Integer.parseInt(s));
+		} catch (NumberFormatException e) {
+			return 0;
+		}
+	}
+
+	public static int getInt(String s, Integer df) {
+		if (s == null || s == "") {
+			return df;
+		}
+		try {
+			return (Integer.parseInt(s));
+		} catch (NumberFormatException e) {
+			return 0;
+		}
+	}
+
+	public static Integer[] getInts(String[] s) {
+		Integer[] integer = new Integer[s.length];
+		if (s == null) {
+			return null;
+		}
+		for (int i = 0; i < s.length; i++) {
+			integer[i] = Integer.parseInt(s[i]);
+		}
+		return integer;
+
+	}
+
+	public static double getDouble(String s, double defval) {
+		if (s == null || s == "") {
+			return (defval);
+		}
+		try {
+			return (Double.parseDouble(s));
+		} catch (NumberFormatException e) {
+			return (defval);
+		}
+	}
+
+	public static double getDou(Double s, double defval) {
+		if (s == null) {
+			return (defval);
+		}
+		return s;
+	}
+
+	/*public static Short getShort(String s) {
+		if (StringUtil.isNotEmpty(s)) {
+			return (Short.parseShort(s));
+		} else {
+			return null;
+		}
+	}*/
+
+	public static int getInt(Object object, int defval) {
+		if (isEmpty(object)) {
+			return (defval);
+		}
+		try {
+			return (Integer.parseInt(object.toString()));
+		} catch (NumberFormatException e) {
+			return (defval);
+		}
+	}
+
+	public static Integer getInt(Object object) {
+		if (isEmpty(object)) {
+			return null;
+		}
+		try {
+			return (Integer.parseInt(object.toString()));
+		} catch (NumberFormatException e) {
+			return null;
+		}
+	}
+
+	public static int getInt(BigDecimal s, int defval) {
+		if (s == null) {
+			return (defval);
+		}
+		return s.intValue();
+	}
+
+	public static Integer[] getIntegerArry(String[] object) {
+		int len = object.length;
+		Integer[] result = new Integer[len];
+		try {
+			for (int i = 0; i < len; i++) {
+				result[i] = Integer.valueOf(object[i].trim());
+				//new Integer(object[i].trim());
+			}
+			return result;
+		} catch (NumberFormatException e) {
+			return null;
+		}
+	}
+
+	public static String getString(String s) {
+		return (getString(s, ""));
+	}
+
+	/**
+	 * 转义成Unicode编码
+	 * @param s
+	 * @return
+	 */
+	/*public static String escapeJava(Object s) {
+		return StringEscapeUtils.escapeJava(getString(s));
+	}*/
+
+	public static String getString(Object object) {
+		if (isEmpty(object)) {
+			return "";
+		}
+		return (object.toString().trim());
+	}
+
+	public static String getString(int i) {
+		return (String.valueOf(i));
+	}
+
+	public static String getString(float i) {
+		return (String.valueOf(i));
+	}
+
+	public static String getString(String s, String defval) {
+		if (isEmpty(s)) {
+			return (defval);
+		}
+		return (s.trim());
+	}
+
+	public static String getString(Object s, String defval) {
+		if (isEmpty(s)) {
+			return (defval);
+		}
+		return (s.toString().trim());
+	}
+
+	public static long stringToLong(String str) {
+		Long test = Long.valueOf(0);
+		try {
+			test = Long.valueOf(str);
+		} catch (Exception e) {
+		}
+		return test.longValue();
+	}
+
+	/**
+	 * 获取本机IP
+	 */
+	public static String getIp() {
+		String ip = null;
+		try {
+			InetAddress address = InetAddress.getLocalHost();
+			ip = address.getHostAddress();
+
+		} catch (UnknownHostException e) {
+			e.printStackTrace();
+		}
+		return ip;
+	}
+
+	/**
+	 * 判断一个类是否为基本数据类型。
+	 *
+	 * @param clazz
+	 *            要判断的类。
+	 * @return true 表示为基本数据类型。
+	 */
+	private static boolean isBaseDataType(Class clazz) throws Exception {
+		return (clazz.equals(String.class) || clazz.equals(Integer.class) || clazz.equals(Byte.class) || clazz.equals(Long.class) || clazz.equals(Double.class) || clazz.equals(Float.class) || clazz.equals(Character.class) || clazz.equals(Short.class) || clazz.equals(BigDecimal.class) || clazz.equals(BigInteger.class) || clazz.equals(Boolean.class) || clazz.equals(Date.class) || clazz.isPrimitive());
+	}
+
+	/**
+	 * @param request
+	 *            IP
+	 * @return IP Address
+	 */
+//	public static String getIpAddrByRequest(HttpServletRequest request) {
+//		String ip = request.getHeader("x-forwarded-for");
+//		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+//			ip = request.getHeader("Proxy-Client-IP");
+//		}
+//		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+//			ip = request.getHeader("WL-Proxy-Client-IP");
+//		}
+//		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+//			ip = request.getRemoteAddr();
+//		}
+//		return ip;
+//	}
+
+	/**
+	 * @return 本机IP
+	 * @throws SocketException
+	 */
+	public static String getRealIp() throws SocketException {
+		String localip = null;// 本地IP,如果没有配置外网IP则返回它
+		String netip = null;// 外网IP
+
+		Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
+		InetAddress ip = null;
+		boolean finded = false;// 是否找到外网IP
+		while (netInterfaces.hasMoreElements() && !finded) {
+			NetworkInterface ni = netInterfaces.nextElement();
+			Enumeration<InetAddress> address = ni.getInetAddresses();
+			while (address.hasMoreElements()) {
+				ip = address.nextElement();
+				if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 外网IP
+					netip = ip.getHostAddress();
+					finded = true;
+					break;
+				} else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 内网IP
+					localip = ip.getHostAddress();
+				}
+			}
+		}
+
+		if (netip != null && !"".equals(netip)) {
+			return netip;
+		} else {
+			return localip;
+		}
+	}
+
+	/**
+	 * java去除字符串中的空格、回车、换行符、制表符
+	 *
+	 * @param str
+	 * @return
+	 */
+	public static String replaceBlank(String str) {
+		String dest = "";
+		if (str != null) {
+			Pattern p = Pattern.compile("\\s*|\t|\r|\n");
+			Matcher m = p.matcher(str);
+			dest = m.replaceAll("");
+		}
+		return dest;
+
+	}
+
+	public static String replaceTrimBlank(String str) {
+		String dest = "";
+		if (str != null) {
+			dest = str.replaceAll("^[\\s\\n\\r\\t]+|[\\s\\n\\r\\t]+$", "");
+			return dest;
+		}
+		return dest;
+	}
+
+	/**
+	 * 判断元素是否在数组内
+	 *
+	 * @param substring
+	 * @param source
+	 * @return
+	 */
+	public static boolean isIn(String substring, String[] source) {
+		if (source == null || source.length == 0) {
+			return false;
+		}
+		for (int i = 0; i < source.length; i++) {
+			String aSource = source[i];
+			if (aSource.equals(substring)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * 获取Map对象
+	 */
+	public static Map<Object, Object> getHashMap() {
+		return new HashMap<Object, Object>();
+	}
+
+	/**
+	 * SET转换MAP
+	 *
+	 * @param str
+	 * @return
+	 */
+	public static Map<Object, Object> SetToMap(Set<Object> setobj) {
+		Map<Object, Object> map = getHashMap();
+		for (Iterator iterator = setobj.iterator(); iterator.hasNext();) {
+			Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) iterator.next();
+			map.put(entry.getKey().toString(), entry.getValue() == null ? "" : entry.getValue().toString().trim());
+		}
+		return map;
+
+	}
+
+	public static boolean isInnerIP(String ipAddress) {
+		boolean isInnerIp = false;
+		long ipNum = getIpNum(ipAddress);
+		/**
+		 * 私有IP:A类 10.0.0.0-10.255.255.255 B类 172.16.0.0-172.31.255.255 C类 192.168.0.0-192.168.255.255 当然,还有127这个网段是环回地址
+		 **/
+		long aBegin = getIpNum("10.0.0.0");
+		long aEnd = getIpNum("10.255.255.255");
+		long bBegin = getIpNum("172.16.0.0");
+		long bEnd = getIpNum("172.31.255.255");
+		long cBegin = getIpNum("192.168.0.0");
+		long cEnd = getIpNum("192.168.255.255");
+		isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd) || ipAddress.equals("127.0.0.1");
+		return isInnerIp;
+	}
+
+	private static long getIpNum(String ipAddress) {
+		String[] ip = ipAddress.split("\\.");
+		long a = Integer.parseInt(ip[0]);
+		long b = Integer.parseInt(ip[1]);
+		long c = Integer.parseInt(ip[2]);
+		long d = Integer.parseInt(ip[3]);
+
+		long ipNum = a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
+		return ipNum;
+	}
+
+	private static boolean isInner(long userIp, long begin, long end) {
+		return (userIp >= begin) && (userIp <= end);
+	}
+
+	/**
+	 * 将下划线大写方式命名的字符串转换为驼峰式。
+	 * 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
+	 * 例如:hello_world->helloWorld
+	 *
+	 * @param name
+	 *            转换前的下划线大写方式命名的字符串
+	 * @return 转换后的驼峰式命名的字符串
+	 */
+	public static String camelName(String name) {
+		StringBuilder result = new StringBuilder();
+		// 快速检查
+		if (name == null || name.isEmpty()) {
+			// 没必要转换
+			return "";
+		} else if (!name.contains("_")) {
+			// 不含下划线,仅将首字母小写
+			//update-begin--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+			//update-begin--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+			return name.substring(0, 1).toLowerCase() + name.substring(1).toLowerCase();
+			//update-end--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+		}
+		// 用下划线将原始字符串分割
+		String camels[] = name.split("_");
+		for (String camel : camels) {
+			// 跳过原始字符串中开头、结尾的下换线或双重下划线
+			if (camel.isEmpty()) {
+				continue;
+			}
+			// 处理真正的驼峰片段
+			if (result.length() == 0) {
+				// 第一个驼峰片段,全部字母都小写
+				result.append(camel.toLowerCase());
+			} else {
+				// 其他的驼峰片段,首字母大写
+				result.append(camel.substring(0, 1).toUpperCase());
+				result.append(camel.substring(1).toLowerCase());
+			}
+		}
+		return result.toString();
+	}
+
+	/**
+	 * 将下划线大写方式命名的字符串转换为驼峰式。
+	 * 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
+	 * 例如:hello_world,test_id->helloWorld,testId
+	 *
+	 * @param name
+	 *            转换前的下划线大写方式命名的字符串
+	 * @return 转换后的驼峰式命名的字符串
+	 */
+	public static String camelNames(String names) {
+		if(names==null||names.equals("")){
+			return null;
+		}
+		StringBuffer sf = new StringBuffer();
+		String[] fs = names.split(",");
+		for (String field : fs) {
+			field = camelName(field);
+			sf.append(field + ",");
+		}
+		String result = sf.toString();
+		return result.substring(0, result.length() - 1);
+	}
+
+	//update-begin--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+	/**
+	 * 将下划线大写方式命名的字符串转换为驼峰式。(首字母写)
+	 * 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
+	 * 例如:hello_world->HelloWorld
+	 *
+	 * @param name
+	 *            转换前的下划线大写方式命名的字符串
+	 * @return 转换后的驼峰式命名的字符串
+	 */
+	public static String camelNameCapFirst(String name) {
+		StringBuilder result = new StringBuilder();
+		// 快速检查
+		if (name == null || name.isEmpty()) {
+			// 没必要转换
+			return "";
+		} else if (!name.contains("_")) {
+			// 不含下划线,仅将首字母小写
+			return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
+		}
+		// 用下划线将原始字符串分割
+		String camels[] = name.split("_");
+		for (String camel : camels) {
+			// 跳过原始字符串中开头、结尾的下换线或双重下划线
+			if (camel.isEmpty()) {
+				continue;
+			}
+			// 其他的驼峰片段,首字母大写
+			result.append(camel.substring(0, 1).toUpperCase());
+			result.append(camel.substring(1).toLowerCase());
+		}
+		return result.toString();
+	}
+	//update-end--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+
+	/**
+	 * 将驼峰命名转化成下划线
+	 * @param para
+	 * @return
+	 */
+	public static String camelToUnderline(String para){
+        if(para.length()<3){
+        	return para.toLowerCase();
+        }
+        StringBuilder sb=new StringBuilder(para);
+        int temp=0;//定位
+        //从第三个字符开始 避免命名不规范
+        for(int i=2;i<para.length();i++){
+            if(Character.isUpperCase(para.charAt(i))){
+                sb.insert(i+temp, "_");
+                temp+=1;
+            }
+        }
+        return sb.toString().toLowerCase();
+	}
+
+	/**
+	 * 随机数
+	 * @param place 定义随机数的位数
+	 */
+	public static String randomGen(int place) {
+		String base = "qwertyuioplkjhgfdsazxcvbnmQAZWSXEDCRFVTGBYHNUJMIKLOP0123456789";
+		StringBuffer sb = new StringBuffer();
+		Random rd = new Random();
+		for(int i=0;i<place;i++) {
+			sb.append(base.charAt(rd.nextInt(base.length())));
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 获取类的所有属性,包括父类
+	 *
+	 * @param object
+	 * @return
+	 */
+	public static Field[] getAllFields(Object object) {
+		Class<?> clazz = object.getClass();
+		List<Field> fieldList = new ArrayList<>();
+		while (clazz != null) {
+			fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
+			clazz = clazz.getSuperclass();
+		}
+		Field[] fields = new Field[fieldList.size()];
+		fieldList.toArray(fields);
+		return fields;
+	}
+
+	/**
+	  * 将map的key全部转成小写
+	 * @param list
+	 * @return
+	 */
+	public static List<Map<String, Object>> toLowerCasePageList(List<Map<String, Object>> list){
+		List<Map<String, Object>> select = new ArrayList<>();
+		for (Map<String, Object> row : list) {
+			 Map<String, Object> resultMap = new HashMap<>();
+			 Set<String> keySet = row.keySet();
+			 for (String key : keySet) {
+				 String newKey = key.toLowerCase();
+				 resultMap.put(newKey, row.get(key));
+			 }
+			 select.add(resultMap);
+		}
+		return select;
+	}
+
+	/**
+	 * 将entityList转换成modelList
+	 * @param fromList
+	 * @param tClass
+	 * @param <F>
+	 * @param <T>
+	 * @return
+	 */
+	public static<F,T> List<T> entityListToModelList(List<F> fromList, Class<T> tClass){
+		if(fromList.isEmpty() || fromList == null){
+			return null;
+		}
+		List<T> tList = new ArrayList<>();
+		for(F f : fromList){
+			T t = entityToModel(f, tClass);
+			tList.add(t);
+		}
+		return tList;
+	}
+
+	public static<F,T> T entityToModel(F entity, Class<T> modelClass) {
+		log.debug("entityToModel : Entity属性的值赋值到Model");
+		Object model = null;
+		if (entity == null || modelClass ==null) {
+			return null;
+		}
+
+		try {
+			model = modelClass.newInstance();
+		} catch (InstantiationException e) {
+			log.error("entityToModel : 实例化异常", e);
+		} catch (IllegalAccessException e) {
+			log.error("entityToModel : 安全权限异常", e);
+		}
+		BeanUtils.copyProperties(entity, model);
+		return (T)model;
+	}
+
+	public static String camelToUnderscore(String camelCase) {
+		return StringUtils.join(
+				StringUtils.splitByCharacterTypeCamelCase(camelCase), "_"
+		).toLowerCase();
+	}
+
+	public static List<Long> retainByIds(List<Long> ownerByIds, Set dataScope) {
+		if (CollectionUtils.isEmpty(ownerByIds)) {
+			return List.copyOf(dataScope);
+		}
+		if (CollectionUtils.isEmpty(dataScope)) {
+			return Arrays.asList(0L);
+		}
+		ownerByIds.retainAll(dataScope);
+		if (CollectionUtils.isEmpty(ownerByIds)) {
+			return Arrays.asList(0L);
+		}
+		return ownerByIds;
+	}
+
+
+}

+ 914 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtil.java

@@ -0,0 +1,914 @@
+package com.storlead.framework.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.Assert;
+
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+/**
+ * 时间/日期处理类
+ *
+ * @author blank
+ * @since 2018/5/17 上午10:16
+ */
+public class DateUtil {
+
+
+    /** 时间日期格式化到年月日时分秒. */
+    public static final String dateFormatYMDHMS = "yyyy-MM-dd HH:mm:ss";
+
+    /** 时间日期格式化到年月日时分秒无间隔 */
+    public static final String dateFormatYMDHMSSimple = "yyyyMMddHHmmss";
+
+    /** 时间日期格式化到年月日无间隔 */
+    public static final String dateFormatYMDSimple = "yyyyMMdd";
+
+    /** 时间日期格式化到年 */
+    public static final String dateFormatYear = "yyyy";
+
+    /** 时间日期格式化到月 */
+    public static final String dateFormatMonth = "MM";
+
+    /** 时间日期格式化到日 */
+    public static final String dateFormatDay = "dd";
+
+    /** 时间日期格式化到年月日. */
+    public static final String dateFormatYMD = "yyyy-MM-dd";
+
+    /** 时间日期格式化到年月日带中文. */
+    public static final String dateFormatYMDCN = "yyyy年MM月dd日";
+
+    /** 时间日期格式化到年月. */
+    public static final String dateFormatYM = "yyyy-MM";
+
+    /** 时间日期格式化到年月日时分. */
+    public static final String dateFormatYMDHM = "yyyy-MM-dd HH:mm";
+
+    /** 时间日期格式化到年月日时分. */
+    public static final String dateFormatYMDHMCN = "yyyy年MM月dd日 HH:mm";
+
+    /** 时间日期格式化到月日. */
+    public static final String dateFormatMD = "MM/dd";
+
+    /** 时分秒. */
+    public static final String dateFormatHMS = "HH:mm:ss";
+
+    /** 时分. */
+    public static final String dateFormatHM = "HH:mm";
+
+    /** 上午. */
+    public static final String AM = "AM";
+
+    /** 下午. */
+    public static final String PM = "PM";
+
+    /**
+     * 描述:String类型的日期时间转化为Date类型.
+     *
+     * @param strDate
+     *            String形式的日期时间
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @return Date Date类型日期时间
+     */
+    public static Date getDateByFormat(String strDate, String format) {
+        SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+        Date date = null;
+        try {
+            date = mSimpleDateFormat.parse(strDate);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return date;
+    }
+
+    /**
+     * 描述:获取偏移之后的Date.
+     *
+     * @param date
+     *            日期时间
+     * @param calendarField
+     *            Calendar属性,对应offset的值,
+     *            如(Calendar.DATE,表示+offset天,Calendar.HOUR_OF_DAY,表示+offset小时)
+     * @param offset
+     *            偏移(值大于0,表示+,值小于0,表示-)
+     * @return Date 偏移之后的日期时间
+     */
+    public static Date getDateByOffset(Date date, int calendarField, int offset) {
+        Calendar c = new GregorianCalendar();
+        try {
+            c.setTime(date);
+            c.add(calendarField, offset);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return c.getTime();
+    }
+
+    public static Date getDateByOffset(LocalDateTime localDateTime, int calendarField, int offset) {
+        Date date = Timestamp.valueOf(localDateTime);
+        return getDateByOffset(date, calendarField, offset);
+    }
+
+
+
+    /**
+     * 描述:获取指定日期时间的字符串(可偏移).
+     *
+     * @param strDate
+     *            String形式的日期时间
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @param calendarField
+     *            Calendar属性,对应offset的值,
+     *            如(Calendar.DATE,表示+offset天,Calendar.HOUR_OF_DAY,表示+offset小时)
+     * @param offset
+     *            偏移(值大于0,表示+,值小于0,表示-)
+     * @return String String类型的日期时间
+     */
+    public static String getStringByOffset(String strDate, String format, int calendarField, int offset) {
+        String mDateTime = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            c.setTime(mSimpleDateFormat.parse(strDate));
+            c.add(calendarField, offset);
+            mDateTime = mSimpleDateFormat.format(c.getTime());
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return mDateTime;
+    }
+
+    /**
+     * 描述:Date类型转化为String类型(可偏移).
+     *
+     * @param date
+     *            the date
+     * @param format
+     *            the format
+     * @param calendarField
+     *            the calendar field
+     * @param offset
+     *            the offset
+     * @return String String类型日期时间
+     */
+    public static String getStringByOffset(Date date, String format, int calendarField, int offset) {
+        String strDate = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            c.setTime(date);
+            c.add(calendarField, offset);
+            strDate = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+    }
+
+    /**
+     * 描述:Date类型转化为String类型.
+     *
+     * @param date
+     *            the date
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getStringByFormat(Date date, String format) {
+        SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+        String strDate = null;
+        try {
+            strDate = mSimpleDateFormat.format(date);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+    }
+
+    /**
+     * 描述:获取指定日期时间的字符串,用于导出想要的格式.
+     *
+     * @param strDate
+     *            String形式的日期时间,必须为yyyy-MM-dd HH:mm:ss格式
+     * @param format
+     *            输出格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @return String 转换后的String类型的日期时间
+     */
+    public static String getStringByFormat(String strDate, String format) {
+        String mDateTime = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(dateFormatYMDHMS);
+            c.setTime(mSimpleDateFormat.parse(strDate));
+            SimpleDateFormat mSimpleDateFormat2 = new SimpleDateFormat(format);
+            mDateTime = mSimpleDateFormat2.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return mDateTime;
+    }
+
+    /**
+     * 描述:获取milliseconds表示的日期时间的字符串.
+     *
+     * @param milliseconds
+     *            the milliseconds
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @return String 日期时间字符串
+     */
+    public static String getStringByFormat(long milliseconds, String format) {
+        String thisDateTime = null;
+        try {
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            thisDateTime = mSimpleDateFormat.format(milliseconds);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return thisDateTime;
+    }
+
+    /**
+     * 描述:获取表示当前日期时间的字符串.
+     *
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @return String String类型的当前日期时间
+     */
+    public static String getCurrentDate(String format) {
+        String curDateTime = null;
+        try {
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            Calendar c = new GregorianCalendar();
+            curDateTime = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return curDateTime;
+
+    }
+
+    /**
+     * 获取当前日期时间的字符串 格式为 yyyyMMdd
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2018-12-19 下午 12:01
+     */
+    public static String getCurrentDateString() {
+        return DateUtil.getCurrentDate(dateFormatYMDSimple);
+
+    }
+
+
+    /**
+     * 获取当前日期时间的字符串 格式为 yyyyMMddHHmmss
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2018-12-19 下午 12:01
+     */
+    public static String getCurrentDateYearMonthDayMinSecString() {
+        return DateUtil.getCurrentDate(dateFormatYMDHMSSimple);
+
+    }
+
+    /**
+     * 获取当前日期时间的字符串 格式为 yyyy年-MM月-dd日
+     *
+     * @return 格式为 yyyy年-MM月-dd日
+     */
+    public static String getCurrentDateCNString() {
+        return DateUtil.getCurrentDate(dateFormatYMDCN);
+
+    }
+
+    /**
+     * 获取当前日期 年 的字符串 格式为 yyyy
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2019-1-10 下午 4:30
+     */
+    public static String getCurrentDateYear() {
+        return DateUtil.getCurrentDate(dateFormatYear);
+    }
+
+    /**
+     * 获取当前日期 月 的字符串 格式为 MM
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2019-1-10 下午 4:31
+     */
+    public static String getCurrentDateMonth() {
+        return DateUtil.getCurrentDate(dateFormatMonth);
+    }
+
+    /**
+     * 获取当前日期 天 的字符串 格式为 dd
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2019-1-10 下午 4:32
+     */
+    public static String getCurrentDateDay() {
+        return DateUtil.getCurrentDate(dateFormatDay);
+    }
+
+    /**
+     * 返回当前系统时间毫秒值
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2018-12-19 下午 12:03
+     */
+    public static String getCurrentTimeMills(){
+        return System.currentTimeMillis() + "";
+    }
+
+
+    /**
+     * 描述:获取表示当前日期时间的字符串(可偏移).
+     *
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @param calendarField
+     *            Calendar属性,对应offset的值,
+     *            如(Calendar.DATE,表示+offset天,Calendar.HOUR_OF_DAY,表示+offset小时)
+     * @param offset
+     *            偏移(值大于0,表示+,值小于0,表示-)
+     * @return String String类型的日期时间
+     */
+    public static String getCurrentDateByOffset(String format, int calendarField, int offset) {
+        String mDateTime = null;
+        try {
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            Calendar c = new GregorianCalendar();
+            c.add(calendarField, offset);
+            mDateTime = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return mDateTime;
+
+    }
+
+    /**
+     * 描述:计算两个日期所差的天数.
+     *
+     * @param milliseconds1
+     *            the milliseconds1
+     * @param milliseconds2
+     *            the milliseconds2
+     * @return int 所差的天数
+     */
+    public static int getOffectDay(long milliseconds1, long milliseconds2) {
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTimeInMillis(milliseconds1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTimeInMillis(milliseconds2);
+        // 先判断是否同年
+        int y1 = calendar1.get(Calendar.YEAR);
+        int y2 = calendar2.get(Calendar.YEAR);
+        int d1 = calendar1.get(Calendar.DAY_OF_YEAR);
+        int d2 = calendar2.get(Calendar.DAY_OF_YEAR);
+        int maxDays = 0;
+        int day = 0;
+        if (y1 - y2 > 0) {
+            maxDays = calendar2.getActualMaximum(Calendar.DAY_OF_YEAR);
+            day = d1 - d2 + maxDays;
+        } else if (y1 - y2 < 0) {
+            maxDays = calendar1.getActualMaximum(Calendar.DAY_OF_YEAR);
+            day = d1 - d2 - maxDays;
+        } else {
+            day = d1 - d2;
+        }
+        return day;
+    }
+
+    /**
+     * 描述:计算两个日期所差的小时数.
+     *
+     * @param date1
+     *            第一个时间的毫秒表示
+     * @param date2
+     *            第二个时间的毫秒表示
+     * @return int 所差的小时数
+     */
+    public static int getOffsetHour(long date1, long date2) {
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTimeInMillis(date1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTimeInMillis(date2);
+        int h1 = calendar1.get(Calendar.HOUR_OF_DAY);
+        int h2 = calendar2.get(Calendar.HOUR_OF_DAY);
+        int h = 0;
+        int day = getOffectDay(date1, date2);
+        h = h1 - h2 + day * 24;
+        return h;
+    }
+
+    /**
+     * 描述:计算两个日期所差的分钟数.
+     *
+     * @param date1
+     *            第一个时间的毫秒表示
+     * @param date2
+     *            第二个时间的毫秒表示
+     * @return int 所差的分钟数
+     */
+    public static int getOffsetMinutes(long date1, long date2) {
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTimeInMillis(date1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTimeInMillis(date2);
+        int m1 = calendar1.get(Calendar.MINUTE);
+        int m2 = calendar2.get(Calendar.MINUTE);
+        int h = getOffsetHour(date1, date2);
+        int m = 0;
+        m = m1 - m2 + h * 60;
+        return m;
+    }
+
+    /**
+     * 描述:获取本周一.
+     *
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getFirstDayOfWeek(String format) {
+        return getDayOfWeek(format, Calendar.MONDAY);
+    }
+
+    /**
+     * 描述:获取本周日.
+     *
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getLastDayOfWeek(String format) {
+        return getDayOfWeek(format, Calendar.SUNDAY);
+    }
+
+    /**
+     * 描述:获取本周的某一天.
+     *
+     * @param format
+     *            the format
+     * @param calendarField
+     *            the calendar field
+     * @return String String类型日期时间
+     */
+    private static String getDayOfWeek(String format, int calendarField) {
+        String strDate = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            int week = c.get(Calendar.DAY_OF_WEEK);
+            if (week == calendarField) {
+                strDate = mSimpleDateFormat.format(c.getTime());
+            } else {
+                int offectDay = calendarField - week;
+                if (calendarField == Calendar.SUNDAY) {
+                    offectDay = 7 - Math.abs(offectDay);
+                }
+                c.add(Calendar.DATE, offectDay);
+                strDate = mSimpleDateFormat.format(c.getTime());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+    }
+
+    /**
+     * 描述:获取本月第一天.
+     *
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getFirstDayOfMonth(String format) {
+        String strDate = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            // 当前月的第一天
+            c.set(GregorianCalendar.DAY_OF_MONTH, 1);
+            strDate = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+
+    }
+
+    /**
+     * 描述:获取本月最后一天.
+     *
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getLastDayOfMonth(String format) {
+        String strDate = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            // 当前月的最后一天
+            c.set(Calendar.DATE, 1);
+            c.roll(Calendar.DATE, -1);
+            strDate = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+    }
+
+    /**
+     * 描述:获取表示当前日期的0点时间毫秒数.
+     *
+     * @return the first time of day
+     */
+    public static long getFirstTimeOfDay() {
+        Date date = null;
+        try {
+            String currentDate = getCurrentDate(dateFormatYMD);
+            date = getDateByFormat(currentDate + " 00:00:00", dateFormatYMDHMS);
+            return date.getTime();
+        } catch (Exception ignored) {
+        }
+        return -1;
+    }
+
+    /**
+     * 描述:获取表示当前日期24点时间毫秒数.
+     *
+     * @return the last time of day
+     */
+    public static long getLastTimeOfDay() {
+        Date date = null;
+        try {
+            String currentDate = getCurrentDate(dateFormatYMD);
+            date = getDateByFormat(currentDate + " 24:00:00", dateFormatYMDHMS);
+            return date.getTime();
+        } catch (Exception ignored) {
+        }
+        return -1;
+    }
+
+    /**
+     * 描述:判断是否是闰年()
+     * <p>
+     * (year能被4整除 并且 不能被100整除) 或者 year能被400整除,则该年为闰年.
+     *
+     * @param year
+     *            年代(如2012)
+     * @return boolean 是否为闰年
+     */
+    public static boolean isLeapYear(int year) {
+        return ((year % 4 == 0 && year % 400 != 0) || year % 400 == 0);
+    }
+
+    /**
+     * 描述:根据时间返回格式化后的时间的描述. 小于1小时显示多少分钟前 大于1小时显示今天+实际日期,大于今天全部显示实际时间
+     *
+     * @param strDate
+     *            the str date
+     * @param outFormat
+     *            the out format
+     * @return the string
+     */
+    public static String formatDateStr2Desc(String strDate, String outFormat) {
+
+        DateFormat df = new SimpleDateFormat(dateFormatYMDHMS);
+        Calendar c1 = Calendar.getInstance();
+        Calendar c2 = Calendar.getInstance();
+        try {
+            c2.setTime(df.parse(strDate));
+            c1.setTime(new Date());
+            int d = getOffectDay(c1.getTimeInMillis(), c2.getTimeInMillis());
+            if (d == 0) {
+                int h = getOffsetHour(c1.getTimeInMillis(), c2.getTimeInMillis());
+                if (h > 0) {
+                    return "今天" + getStringByFormat(strDate, dateFormatHM);
+                    // return h + "小时前";
+                } else if (h < 0) {
+                    // return Math.abs(h) + "小时后";
+                } else if (h == 0) {
+                    int m = getOffsetMinutes(c1.getTimeInMillis(), c2.getTimeInMillis());
+                    if (m > 0) {
+                        return m + "分钟前";
+                    } else if (m < 0) {
+                        // return Math.abs(m) + "分钟后";
+                    } else {
+                        return "刚刚";
+                    }
+                }
+
+            } else if (d > 0) {
+                if (d == 1) {
+                    // return "昨天"+getStringByFormat(strDate,outFormat);
+                } else if (d == 2) {
+                    // return "前天"+getStringByFormat(strDate,outFormat);
+                }
+            } else if (d < 0) {
+                if (d == -1) {
+                    // return "明天"+getStringByFormat(strDate,outFormat);
+                } else if (d == -2) {
+                    // return "后天"+getStringByFormat(strDate,outFormat);
+                } else {
+                    // return Math.abs(d) +
+                    // "天后"+getStringByFormat(strDate,outFormat);
+                }
+            }
+
+            String out = getStringByFormat(strDate, outFormat);
+            if (!StringUtils.isBlank(out)) {
+                return out;
+            }
+        } catch (Exception e) {
+        }
+
+        return strDate;
+    }
+
+    /**
+     * 取指定日期为星期几.
+     *
+     * @param strDate
+     *            指定日期
+     * @param inFormat
+     *            指定日期格式
+     * @return String 星期几
+     */
+    public static String getWeekNumber(String strDate, String inFormat) {
+        Assert.isTrue((StringUtils.isNoneBlank(strDate) && StringUtils.isNoneBlank(inFormat)), "strDate or inFormat can not be null!");
+
+        Date mDate = getDateByFormat(strDate, inFormat);
+        Calendar calendar = new GregorianCalendar();
+        calendar.setTime(mDate);
+
+        int intTemp = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+        switch (intTemp) {
+            case 0:
+                return "星期日";
+            case 1:
+                return "星期一";
+            case 2:
+                return "星期二";
+            case 3:
+                return "星期三";
+            case 4:
+                return "星期四";
+            case 5:
+                return "星期五";
+            case 6:
+                return "星期六";
+
+            default:
+                return "";
+        }
+    }
+
+    /**
+     * 根据给定的日期判断是否为上下午.
+     *
+     * @param strDate
+     *            the str date
+     * @param format
+     *            the format
+     * @return the time quantum  Calendar.get(Calendar.HOUR_OF_DAY)
+     */
+    @SuppressWarnings("unchecked")
+    public static String getTimeQuantum(String strDate, String format) {
+        Date mDate = getDateByFormat(strDate, format);
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(mDate);
+        if (calendar.get(Calendar.AM_PM) == Calendar.AM) {
+            return "AM";
+        } else {
+            return "PM";
+        }
+    }
+
+    /**
+     * 判断当前日期是否上下午.
+     * 如果true 表示是上午 false 表示下午
+     *
+     * @return the time quantum  Calendar.get(Calendar.HOUR_OF_DAY)
+     */
+    @SuppressWarnings("unchecked")
+    public static Boolean getIfTimeNowIsAM() {
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new Date());
+        if (calendar.get(Calendar.AM_PM) == Calendar.AM) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 根据给定的毫秒数算得时间的描述.
+     *
+     * @param milliseconds
+     *            the milliseconds
+     * @return the time description
+     */
+    public static String getTimeDescription(long milliseconds) {
+        if (milliseconds > 1000) {
+            // 大于一分
+            if (milliseconds / 1000 / 60 > 1) {
+                long minute = milliseconds / 1000 / 60;
+                long second = milliseconds / 1000 % 60;
+                return minute + "分" + second + "秒";
+            } else {
+                // 显示秒
+                return milliseconds / 1000 + "秒";
+            }
+        } else {
+            return milliseconds + "毫秒";
+        }
+    }
+
+    /**
+     * 解析时间按照小时:分:秒格式输出
+     *
+     * @param iTimeSeconds 秒
+     * @return
+     */
+    public static String getTime(int iTimeSeconds) {
+        String strUserTime = iTimeSeconds / 60 + " : "
+                + ((iTimeSeconds % 60) < 10 ? ("0" + iTimeSeconds % 60) : iTimeSeconds % 60);
+        return strUserTime;
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/23 12:01
+     *@Description:
+     * 获取本周一日期
+     *
+     */
+    public static Date getMondayOFWeek(Date date){
+        int mondayPlus = getMondayPlus(date);
+        GregorianCalendar currentDate = new GregorianCalendar();
+        currentDate.setTime(date);
+//        System.out.println(" 当前时间 :"+currentDate.getTime());
+        currentDate.add(GregorianCalendar.DATE, mondayPlus);
+        return currentDate.getTime();
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/23 12:02
+     *@Description:
+     * 获取本周日日期
+     *
+     */
+    public static Date getCurrentWeekday(Date date){
+        int mondayPlus = getMondayPlus(date);
+        GregorianCalendar currentDate = new GregorianCalendar();
+        currentDate.setTime(date);
+        currentDate.add(GregorianCalendar.DATE, mondayPlus+ 6);
+        return currentDate.getTime();
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/23 12:03
+     *@Description:
+     * 获取下周一的日期
+     *
+     */
+    public static Date getNextMonday(Date date) {
+        int mondayPlus = getMondayPlus(date);
+        GregorianCalendar currentDate = new GregorianCalendar();
+        currentDate.setTime(date);
+        currentDate.add(GregorianCalendar.DATE, mondayPlus + 7);
+        return currentDate.getTime();
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/23 12:03
+     *@Description:
+     * 获取下周日的日期
+     *
+     */
+    public static Date getNextSunday(Date date) {
+        int mondayPlus = getMondayPlus(date);
+        GregorianCalendar currentDate = new GregorianCalendar();
+        currentDate.setTime(date);
+        currentDate.add(GregorianCalendar.DATE, mondayPlus + 7 + 6);
+        return currentDate.getTime();
+    }
+
+    private static int getMondayPlus(Date date) {
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+        // 获得今天是一周的第几天,星期日是第一天,星期二是第二天......
+        // 因为按中国礼拜一作为第一天所以这里减1
+        int dayOfWeek = cd.get(Calendar.DAY_OF_WEEK) - 1;
+        if (dayOfWeek == 0) {
+            return -6;
+        } else {
+            return 1-dayOfWeek;
+        }
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/24 17:32
+     *@Description:
+     * 添加分钟
+     * @param date 从那个时间开始
+     * @param minute 延长的分钟
+     */
+    public static Date addMinute(Date date, Integer minute){
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.MINUTE,minute);
+        return calendar.getTime();
+    }
+
+    /**
+     * @Author: timo
+     * @Date: 2018/6/12 16:46
+     * @Description: 获取传入时间的月份的最后一天
+     * @param
+     * @return:
+     *
+     */
+    public static Date getLastDayByMonth(Date date){
+        Calendar ca = Calendar.getInstance();
+        ca.setTime(date);
+        ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
+        return ca.getTime();
+    }
+
+    public static Date getDateShort(Date date){
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
+        String strDate=formatter.format(date);
+        ParsePosition pos = new ParsePosition(0);
+        Date strtodate = formatter.parse(strDate, pos);
+        return strtodate;
+    }
+    public static void main(String[] args) {
+//        LocalDateTime dateTime1 = LocalDateTime.of(2023,  10, 1, 12, 0);
+//        LocalDateTime dateTime2 = LocalDateTime.of(2023,  10, 5, 14, 0);
+//
+//        // 计算两个 LocalDateTime 之间的天数差
+//        long daysDifference = ChronoUnit.DAYS.between(dateTime1,  dateTime2);
+//
+//        // 将结果转换为 Integer 类型
+//        Integer daysDifferenceInteger = (int) daysDifference;
+//
+//        // 输出结果
+//        System.out.println("Days  difference: " + daysDifferenceInteger);
+        System.out.println("Days  difference: " + getDateByOffset(LocalDateTime.now(), Calendar.DATE, 2));
+//        System.out.println("date : "+DateUtil.getStringByOffset(new Date(),"yyyy-MM-dd HH:mm:ss", Calendar.DATE,-5));
+//        // 本周一
+//        Date nowDate = DateUtil.getDateByFormat("2018-07-02",DateUtil.dateFormatYMD);
+//        Date firsDate = getMondayOFWeek(nowDate);
+//        String firsDateStr = DateUtil.getStringByFormat(firsDate,dateFormatYMD);
+//        System.out.println("firsDateStr : "+firsDateStr);
+//
+//        // 本周日
+//        Date lastCurDate = getCurrentWeekday(firsDate);
+//        String lastCurDateStr = DateUtil.getStringByFormat(lastCurDate,dateFormatYMD);
+//        System.out.println("lastCurDateStr : "+lastCurDateStr);
+//
+//        // 下周一
+//        Date nextMonday = getNextMonday(firsDate);
+//        String nextMondayStr = DateUtil.getStringByFormat(nextMonday,dateFormatYMD);
+//        System.out.println("nextMondayStr : "+nextMondayStr);
+//        // 下周日
+//        Date nextSunday = getNextSunday(firsDate);
+//        String nextSundayStr = DateUtil.getStringByFormat(nextSunday,dateFormatYMD);
+//        System.out.println("nextSundayStr : "+nextSundayStr);
+//
+//        Date toDay = new Date();
+//        String toDayStr = DateUtil.getStringByFormat(toDay,dateFormatYMD);
+//        System.out.println("toDayStr : "+toDayStr);
+
+
+
+    }
+
+}

+ 895 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtils.java

@@ -0,0 +1,895 @@
+package com.storlead.framework.common.util;
+
+import cn.hutool.core.util.RandomUtil;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.StringUtils;
+
+import java.beans.PropertyEditorSupport;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.ZoneId;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+/**
+ *
+ * 类描述:时间操作定义类
+ *
+ * @Author: 张代浩
+ * @Date:2012-12-8 12:15:03
+ * @Version 1.0
+ */
+@Log4j2
+public class DateUtils extends PropertyEditorSupport {
+	// 各种时间格式
+	public static final SimpleDateFormat date_sdf = new SimpleDateFormat("yyyy-MM-dd");
+
+	public static final SimpleDateFormat date_yyyy_mm = new SimpleDateFormat("yyyy-MM");
+	// 各种时间格式
+	public static final SimpleDateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");
+	// 各种时间格式
+	public static final SimpleDateFormat date_sdf_wz = new SimpleDateFormat("yyyy年MM月dd日");
+	public static final SimpleDateFormat time_sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+	public static final SimpleDateFormat yyyymmddhhmmss = new SimpleDateFormat("yyyyMMddHHmmss");
+	public static final SimpleDateFormat short_time_sdf = new SimpleDateFormat("HH:mm");
+	public static final SimpleDateFormat long_time_sdf = new SimpleDateFormat("HHmmss");
+	public static final SimpleDateFormat datetimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+	// 以毫秒表示的时间
+	private static final long DAY_IN_MILLIS = 24 * 3600 * 1000;
+	private static final long HOUR_IN_MILLIS = 3600 * 1000;
+	private static final long MINUTE_IN_MILLIS = 60 * 1000;
+	private static final long SECOND_IN_MILLIS = 1000;
+
+	// 指定模式的时间格式
+	private static SimpleDateFormat getSDFormat(String pattern) {
+		return new SimpleDateFormat(pattern);
+	}
+
+	/**
+	 * 当前日历,这里用中国时间表示
+	 *
+	 * @return 以当地时区表示的系统当前日历
+	 */
+	public static Calendar getCalendar() {
+		return Calendar.getInstance();
+	}
+
+	/**
+	 * 指定毫秒数表示的日历
+	 *
+	 * @param millis 毫秒数
+	 * @return 指定毫秒数表示的日历
+	 */
+	public static Calendar getCalendar(long millis) {
+		Calendar cal = Calendar.getInstance();
+		// --------------------cal.setTimeInMillis(millis);
+		cal.setTime(new Date(millis));
+		return cal;
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// getDate
+	// 各种方式获取的Date
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 当前日期
+	 *
+	 * @return 系统当前时间
+	 */
+	public static Date getDate() {
+		return new Date();
+	}
+
+	/**
+	 * 指定毫秒数表示的日期
+	 *
+	 * @param millis 毫秒数
+	 * @return 指定毫秒数表示的日期
+	 */
+	public static Date getDate(long millis) {
+		return new Date(millis);
+	}
+
+	/**
+	 * 时间戳转换为字符串
+	 *
+	 * @param time
+	 * @return
+	 */
+	public static String timestamptoStr(Timestamp time) {
+		Date date = null;
+		if (null != time) {
+			date = new Date(time.getTime());
+		}
+		return date2Str(date_sdf);
+	}
+
+	/**
+	 * 字符串转换时间戳
+	 *
+	 * @param str
+	 * @return
+	 */
+	public static Timestamp str2Timestamp(String str) {
+		Date date = str2Date(str, date_sdf);
+		return new Timestamp(date.getTime());
+	}
+
+	/**
+	 * 字符串转换成日期
+	 *
+	 * @param str
+	 * @param sdf
+	 * @return
+	 */
+	public static Date str2Date(String str, SimpleDateFormat sdf) {
+		if (null == str || "".equals(str)) {
+			return null;
+		}
+		Date date = null;
+		try {
+			date = sdf.parse(str);
+			return date;
+		} catch (ParseException e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+
+	/**
+	 * 日期转换为字符串
+	 *
+	 * @return 字符串
+	 */
+	public static String date2Str(SimpleDateFormat date_sdf) {
+		Date date = getDate();
+		if (null == date) {
+			return null;
+		}
+		return date_sdf.format(date);
+	}
+
+	/**
+	 * 格式化时间
+	 *
+	 * @param date
+	 * @param format
+	 * @return
+	 */
+	public static String dateformat(String date, String format) {
+		SimpleDateFormat sformat = new SimpleDateFormat(format);
+		Date _date = null;
+		try {
+			_date = sformat.parse(date);
+		} catch (ParseException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return sformat.format(_date);
+	}
+
+	/**
+	 * 日期转换为字符串
+	 *
+	 * @param date   日期
+	 * @return 字符串
+	 */
+	public static String date2Str(Date date, SimpleDateFormat date_sdf) {
+		if (null == date) {
+			return null;
+		}
+		return date_sdf.format(date);
+	}
+
+	/**
+	 * 日期转换为字符串
+	 *
+	 * @param format 日期格式
+	 * @return 字符串
+	 */
+	public static String getDate(String format) {
+		Date date = new Date();
+		SimpleDateFormat sdf = new SimpleDateFormat(format);
+		return sdf.format(date);
+	}
+
+	public static String getYestoday() {
+		DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
+		Calendar calendar=Calendar.getInstance();
+		calendar.set(Calendar.HOUR_OF_DAY,-24);
+		return dateFormat.format(calendar.getTime());
+	}
+
+	/**
+	 * 指定毫秒数的时间戳
+	 *
+	 * @param millis 毫秒数
+	 * @return 指定毫秒数的时间戳
+	 */
+	public static Timestamp getTimestamp(long millis) {
+		return new Timestamp(millis);
+	}
+
+	/**
+	 * 以字符形式表示的时间戳
+	 *
+	 * @param time 毫秒数
+	 * @return 以字符形式表示的时间戳
+	 */
+	public static Timestamp getTimestamp(String time) {
+		return new Timestamp(Long.parseLong(time));
+	}
+
+	/**
+	 * 系统当前的时间戳
+	 *
+	 * @return 系统当前的时间戳
+	 */
+	public static Timestamp getTimestamp() {
+		return new Timestamp(System.currentTimeMillis());
+	}
+
+	/**
+	 * 当前时间,格式 yyyy-MM-dd HH:mm:ss
+	 *
+	 * @return 当前时间的标准形式字符串
+	 */
+	public static String now() {
+		return datetimeFormat.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定日期的时间戳
+	 *
+	 * @param date 指定日期
+	 * @return 指定日期的时间戳
+	 */
+	public static Timestamp getTimestamp(Date date) {
+		return new Timestamp(date.getTime());
+	}
+
+	/**
+	 * 指定日历的时间戳
+	 *
+	 * @param cal 指定日历
+	 * @return 指定日历的时间戳
+	 */
+	public static Timestamp getCalendarTimestamp(Calendar cal) {
+		// ---------------------return new Timestamp(cal.getTimeInMillis());
+		return new Timestamp(cal.getTime().getTime());
+	}
+
+	public static Timestamp gettimestamp() {
+		Date dt = new Date();
+		DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+		String nowTime = df.format(dt);
+		Timestamp buydate = Timestamp.valueOf(nowTime);
+		return buydate;
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// getMillis
+	// 各种方式获取的Millis
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 系统时间的毫秒数
+	 *
+	 * @return 系统时间的毫秒数
+	 */
+	public static long getMillis() {
+		return System.currentTimeMillis();
+	}
+
+	/**
+	 * 指定日历的毫秒数
+	 *
+	 * @param cal 指定日历
+	 * @return 指定日历的毫秒数
+	 */
+	public static long getMillis(Calendar cal) {
+		// --------------------return cal.getTimeInMillis();
+		return cal.getTime().getTime();
+	}
+
+	/**
+	 * 指定日期的毫秒数
+	 *
+	 * @param date 指定日期
+	 * @return 指定日期的毫秒数
+	 */
+	public static long getMillis(Date date) {
+		return date.getTime();
+	}
+
+	/**
+	 * 指定时间戳的毫秒数
+	 *
+	 * @param ts 指定时间戳
+	 * @return 指定时间戳的毫秒数
+	 */
+	public static long getMillis(Timestamp ts) {
+		return ts.getTime();
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// formatDate
+	// 将日期按照一定的格式转化为字符串
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 默认方式表示的系统当前日期,具体格式:年-月-日
+	 *
+	 * @return 默认日期按“年-月-日“格式显示
+	 */
+	public static String formatDate() {
+		return date_sdf.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 默认方式表示的系统当前日期,具体格式:yyyy-MM-dd HH:mm:ss
+	 *
+	 * @return 默认日期按“yyyy-MM-dd HH:mm:ss“格式显示
+	 */
+	public static String formatDateTime() {
+		return datetimeFormat.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 获取时间字符串
+	 */
+	public static String getDataString(SimpleDateFormat formatstr) {
+		return formatstr.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:年-月-日
+	 *
+	 * @param cal 指定的日期
+	 * @return 指定日期按“年-月-日“格式显示
+	 */
+	public static String formatDate(Calendar cal) {
+		return date_sdf.format(cal.getTime());
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:年-月-日
+	 *
+	 * @param date 指定的日期
+	 * @return 指定日期按“年-月-日“格式显示
+	 */
+	public static String formatDate(Date date) {
+		return date_sdf.format(date);
+	}
+
+	/**
+	 * 指定毫秒数表示日期的默认显示,具体格式:年-月-日
+	 *
+	 * @param millis 指定的毫秒数
+	 * @return 指定毫秒数表示日期按“年-月-日“格式显示
+	 */
+	public static String formatDate(long millis) {
+		return date_sdf.format(new Date(millis));
+	}
+
+	/**
+	 * 默认日期按指定格式显示
+	 *
+	 * @param pattern 指定的格式
+	 * @return 默认日期按指定格式显示
+	 */
+	public static String formatDate(String pattern) {
+		return getSDFormat(pattern).format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定日期按指定格式显示
+	 *
+	 * @param cal     指定的日期
+	 * @param pattern 指定的格式
+	 * @return 指定日期按指定格式显示
+	 */
+	public static String formatDate(Calendar cal, String pattern) {
+		return getSDFormat(pattern).format(cal.getTime());
+	}
+
+	/**
+	 * 指定日期按指定格式显示
+	 *
+	 * @param date    指定的日期
+	 * @param pattern 指定的格式
+	 * @return 指定日期按指定格式显示
+	 */
+	public static String formatDate(Date date, String pattern) {
+		return getSDFormat(pattern).format(date);
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// formatTime
+	// 将日期按照一定的格式转化为字符串
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 默认方式表示的系统当前日期,具体格式:年-月-日 时:分
+	 *
+	 * @return 默认日期按“年-月-日 时:分“格式显示
+	 */
+	public static String formatTime() {
+		return time_sdf.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定毫秒数表示日期的默认显示,具体格式:年-月-日 时:分
+	 *
+	 * @param millis 指定的毫秒数
+	 * @return 指定毫秒数表示日期按“年-月-日 时:分“格式显示
+	 */
+	public static String formatTime(long millis) {
+		return time_sdf.format(new Date(millis));
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:年-月-日 时:分
+	 *
+	 * @param cal 指定的日期
+	 * @return 指定日期按“年-月-日 时:分“格式显示
+	 */
+	public static String formatTime(Calendar cal) {
+		return time_sdf.format(cal.getTime());
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:年-月-日 时:分
+	 *
+	 * @param date 指定的日期
+	 * @return 指定日期按“年-月-日 时:分“格式显示
+	 */
+	public static String formatTime(Date date) {
+		return time_sdf.format(date);
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// formatShortTime
+	// 将日期按照一定的格式转化为字符串
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 默认方式表示的系统当前日期,具体格式:时:分
+	 *
+	 * @return 默认日期按“时:分“格式显示
+	 */
+	public static String formatShortTime() {
+		return short_time_sdf.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定毫秒数表示日期的默认显示,具体格式:时:分
+	 *
+	 * @param millis 指定的毫秒数
+	 * @return 指定毫秒数表示日期按“时:分“格式显示
+	 */
+	public static String formatShortTime(long millis) {
+		return short_time_sdf.format(new Date(millis));
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:时:分
+	 *
+	 * @param cal 指定的日期
+	 * @return 指定日期按“时:分“格式显示
+	 */
+	public static String formatShortTime(Calendar cal) {
+		return short_time_sdf.format(cal.getTime());
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:时:分
+	 *
+	 * @param date 指定的日期
+	 * @return 指定日期按“时:分“格式显示
+	 */
+	public static String formatShortTime(Date date) {
+		return short_time_sdf.format(date);
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// parseDate
+	// parseCalendar
+	// parseTimestamp
+	// 将字符串按照一定的格式转化为日期或时间
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 根据指定的格式将字符串转换成Date 如输入:2003-11-19 11:20:20将按照这个转成时间
+	 *
+	 * @param src     将要转换的原始字符窜
+	 * @param pattern 转换的匹配格式
+	 * @return 如果转换成功则返回转换后的日期
+	 * @throws ParseException
+	 */
+	public static Date parseDate(String src, String pattern) throws ParseException {
+		return getSDFormat(pattern).parse(src);
+
+	}
+
+	/**
+	 * 根据指定的格式将字符串转换成Date 如输入:2003-11-19 11:20:20将按照这个转成时间
+	 *
+	 * @param src     将要转换的原始字符窜
+	 * @param pattern 转换的匹配格式
+	 * @return 如果转换成功则返回转换后的日期
+	 * @throws ParseException
+	 */
+	public static Calendar parseCalendar(String src, String pattern) throws ParseException {
+
+		Date date = parseDate(src, pattern);
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(date);
+		return cal;
+	}
+
+	public static String formatAddDate(String src, String pattern, int amount) throws ParseException {
+		Calendar cal;
+		cal = parseCalendar(src, pattern);
+		cal.add(Calendar.DATE, amount);
+		return formatDate(cal);
+	}
+
+	/**
+	 * 根据指定的格式将字符串转换成Date 如输入:2003-11-19 11:20:20将按照这个转成时间
+	 *
+	 * @param src     将要转换的原始字符窜
+	 * @param pattern 转换的匹配格式
+	 * @return 如果转换成功则返回转换后的时间戳
+	 * @throws ParseException
+	 */
+	public static Timestamp parseTimestamp(String src, String pattern) throws ParseException {
+		Date date = parseDate(src, pattern);
+		return new Timestamp(date.getTime());
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// dateDiff
+	// 计算两个日期之间的差值
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 计算两个时间之间的差值,根据标志的不同而不同
+	 *
+	 * @param flag   计算标志,表示按照年/月/日/时/分/秒等计算
+	 * @param calSrc 减数
+	 * @param calDes 被减数
+	 * @return 两个日期之间的差值
+	 */
+	public static int dateDiff(char flag, Calendar calSrc, Calendar calDes) {
+
+		long millisDiff = getMillis(calSrc) - getMillis(calDes);
+
+		if (flag == 'y') {
+			return (calSrc.get(calSrc.YEAR) - calDes.get(calDes.YEAR));
+		}
+
+		if (flag == 'd') {
+			return (int) (millisDiff / DAY_IN_MILLIS);
+		}
+
+		if (flag == 'h') {
+			return (int) (millisDiff / HOUR_IN_MILLIS);
+		}
+
+		if (flag == 'm') {
+			return (int) (millisDiff / MINUTE_IN_MILLIS);
+		}
+
+		if (flag == 's') {
+			return (int) (millisDiff / SECOND_IN_MILLIS);
+		}
+
+		return 0;
+	}
+	// 计算两个日期之间的差值
+	public static int daysBetween(Date one, Date two) {
+		long difference =  (one.getTime()-two.getTime())/86400000;
+		return (int)Math.abs(difference);
+	}
+
+
+	/**
+	 * String类型 转换为Date, 如果参数长度为10 转换格式”yyyy-MM-dd“ 如果参数长度为19 转换格式”yyyy-MM-dd
+	 * HH:mm:ss“ * @param text String类型的时间值
+	 */
+	@Override
+	public void setAsText(String text) throws IllegalArgumentException {
+		if (StringUtils.hasText(text)) {
+			try {
+				if (text.indexOf(":") == -1 && text.length() == 10) {
+					setValue(this.date_sdf.parse(text));
+				} else if (text.indexOf(":") > 0 && text.length() == 19) {
+					setValue(this.datetimeFormat.parse(text));
+				} else {
+					throw new IllegalArgumentException("Could not parse date, date format is error ");
+				}
+			} catch (ParseException ex) {
+				IllegalArgumentException iae = new IllegalArgumentException("Could not parse date: " + ex.getMessage());
+				iae.initCause(ex);
+				throw iae;
+			}
+		} else {
+			setValue(null);
+		}
+	}
+
+	public static int getYear() {
+		GregorianCalendar calendar = new GregorianCalendar();
+		calendar.setTime(getDate());
+		return calendar.get(Calendar.YEAR);
+	}
+	 /***
+	  * @Description: 获取日本月的第一天(本月)
+	  * @Param:
+	  * @return:
+	  * @Author: YPZ
+	  * @Date: 2023/5/19 14:48
+	  */
+	public static Date getMonthFirstDate() {
+		// 获取当前日期
+		Date currentDate = new Date();
+		// 创建Calendar实例
+		Calendar calendar = Calendar.getInstance();
+		// 设置日期为当前日期
+		calendar.setTime(currentDate);
+		// 将日期设置为该月的第一天
+		calendar.set(Calendar.DAY_OF_MONTH, 1);
+		String strNew = new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime());
+		try {
+			Date date =new SimpleDateFormat("yyyy-MM-dd").parse(strNew);
+			return date;
+		}catch (Exception e){
+			log.error("日期转换错误");
+		}
+		// 获取本月的开始时间
+
+		return calendar.getTime();
+	}
+
+	/***
+	  * @Description: 获取本月的最后一天(本月)
+	  * @Param:
+	  * @return:
+	  * @Author: YPZ
+	  * @Date: 2023/5/19 14:48
+	  */
+	public static Date getMonthLastDate() {
+		// 获取当前日期
+		Date currentDate = new Date();
+		// 创建Calendar实例
+		Calendar calendar = Calendar.getInstance();
+		// 设置日期为当前日期
+		calendar.setTime(currentDate);
+		// 将日期设置为该月的第一天
+		calendar.set(Calendar.DAY_OF_MONTH, 1);
+
+		// 将日期设置为该月的最后一天
+		calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
+		// 获取本月的结束时间
+//		return calendar.getTime();
+		String strNew = new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime());
+		try {
+			Date date =new SimpleDateFormat("yyyy-MM-dd").parse(strNew);
+			return date;
+		}catch (Exception e){
+			log.error("日期转换错误");
+		}
+		// 获取本月的开始时间
+
+		return calendar.getTime();
+	}
+
+	/**
+   * 获取当前日期所在季度的开始日期和结束日期
+   * 季度一年四季, 第一季度:1月-3月, 第二季度:4月-6月, 第三季度:7月-9月, 第四季度:10月-12月
+   * @param isFirst  true表示查询本季度开始日期  false表示查询本季度结束日期
+   * @return
+   */
+      public static Date getStartOrEndDayOfQuarter(Boolean isFirst){
+		         LocalDate today=LocalDate.now();
+		         LocalDate resDate = LocalDate.now();
+		         if (today == null) {
+			             today = resDate;
+			         }
+		         Month month = today.getMonth();
+		         Month firstMonthOfQuarter = month.firstMonthOfQuarter();
+		         Month endMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
+		         if (isFirst) {
+			             resDate = LocalDate.of(today.getYear(), firstMonthOfQuarter, 1);
+			         } else {
+			             resDate = LocalDate.of(today.getYear(), endMonthOfQuarter, endMonthOfQuarter.length(today.isLeapYear()));
+			         }
+		         return Date.from(resDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+		     }
+
+	/**
+	 * 通过时间秒毫秒数判断两个时间的间隔
+	 * @param date1
+	 * @param date2
+	 * @return
+	 */
+	public static int differentDaysByMillisecond(Date date1,Date date2)
+	{
+		int days = (int) ((date2.getTime() - date1.getTime()) / (1000*3600*24));
+		return days;
+	}
+
+	public static String getUploadFileName() {
+		return date2Str(long_time_sdf)+RandomUtil.randomNumbers(4);
+	}
+
+	public static String getUploadDir() {
+		return date2Str(yyyyMMdd);
+	}
+	/**
+	* @Description:  取日期的后一天
+	* @Param:
+	* @return:
+	* @Author: YPZ
+	* @Date: 2022/7/25
+	*/
+
+	public static Date nextDate(Date date) {
+		Calendar calendar = Calendar.getInstance(); //得到日历
+		calendar.setTime(date);
+		calendar.add(Calendar.DAY_OF_MONTH, 1);  //设置为后一天
+		Date tomorrow = calendar.getTime();   //得到后一天的时间
+		return tomorrow;
+
+	}
+
+	/**
+	* @Description:  取日期的前一天
+	* @Param:
+	* @return:
+	* @Author: YPZ
+	* @Date: 2022/7/25
+	*/
+
+	public static Date preDate(Date date) {
+		Calendar calendar = Calendar.getInstance(); //得到日历
+		calendar.setTime(date);
+		calendar.add(Calendar.DAY_OF_MONTH, -1);  //设置为前一天
+		Date tomorrow = calendar.getTime();   //得到前一天的时间
+		return tomorrow;
+
+	}
+
+	/**
+	* @Description:  取日期的最后一秒
+	* @Param:
+	* @return:
+	* @Author: YPZ
+	* @Date: 2022/7/25
+	*/
+
+	public static Date dateEndTime(Date date) {
+
+		try {
+			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+			String time=sdf.format(date);//将前端的日期转成String字符串
+			time+=" 23:59:59";//字符串拼接
+			SimpleDateFormat end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//将字符串格式化一下
+			return end.parse(time);
+		}catch (ParseException e){
+			return date;
+		}
+	}
+
+	/**
+	* @Description:  取日期的最后一秒
+	* @Param:
+	* @return:
+	* @Author: YPZ
+	* @Date: 2022/7/25
+	*/
+
+	public static Date dateEndTime(LocalDate localdate) {
+
+		ZoneId zone = ZoneId.systemDefault();
+		Instant instant = localdate.atStartOfDay().atZone(zone).toInstant();
+		Date date = Date.from(instant);
+		try {
+			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+			String time=sdf.format(date);//将前端的日期转成String字符串
+			time+=" 23:59:59";//字符串拼接
+			SimpleDateFormat end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//将字符串格式化一下
+			return end.parse(time);
+		}catch (ParseException e){
+			return null;
+		}
+	}
+
+
+
+	/**
+	 * 判断一年的第几周
+	 * @param datetime
+	 * @return
+	 * @throws ParseException
+	 */
+	public static Integer whatWeek(String datetime) {
+		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+		Date date = null;
+		try {
+			date = format.parse(datetime);
+		} catch (ParseException e) {
+			e.printStackTrace();
+		}
+		Calendar calendar = Calendar.getInstance();
+		calendar.setFirstDayOfWeek(Calendar.MONDAY);
+		calendar.setTime(date);
+		Integer weekNumbe = calendar.get(Calendar.WEEK_OF_YEAR);
+		return weekNumbe;
+	}
+	public static Integer weekTest(String datetime){
+		Calendar calendar = Calendar.getInstance();
+		//设置星期一为一周开始的第一天
+		calendar.setFirstDayOfWeek(Calendar.MONDAY);
+		//获得当前的时间戳
+		calendar.setTimeInMillis(System.currentTimeMillis());
+		//获得当前日期属于今年的第几周
+		Integer weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR);
+		return weekOfYear;
+	}
+	 /***
+	  * @Description: 获取日期几年前后的时间
+	  * @Param: years参照时间,年(之前为负数,之后为正数)
+	  * @return:
+	  * @Author: YPZ
+	  * @Date: 2022/11/22 14:38
+	  */
+	public static Date calYear(Date date,int years){
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.add(Calendar.YEAR,years);
+		return calendar.getTime();
+	}
+
+	public static void main(String[] args) {
+
+		System.out.println("本月第一天为:"+getMonthFirstDate());
+		System.out.println("本月最后一天为:"+getMonthLastDate());
+		System.out.println("本季度第一天为:"+getStartOrEndDayOfQuarter(true));
+		System.out.println("本季度最后一天为:"+getStartOrEndDayOfQuarter(false));
+//		System.out.println(getUploadDir());
+//		System.out.println(weekTest("2022-02-18"));
+//		LocalDateTime end = LocalDateTime.parse("2022-07-21 23:59:59", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+//
+//		long until = java.time.LocalDate.now().until(end, ChronoUnit.DAYS);
+//		System.out.println(until);
+//		try {
+//		LocalDate localDate1 = LocalDate.of(2022,8,15);
+//
+//		String beginTime = "2022-08-16 14:42:32";
+//		String endTime = "2022-08-17 14:42:32";
+//		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+//		Date date1 = format.parse(beginTime);
+//		Date date2 = format.parse(endTime);
+//
+//		LocalDate localDate2 = date1.toInstant()
+//				.atZone(ZoneId.systemDefault())
+//				.toLocalDate();
+//		Boolean isBefore = localDate1.isBefore(localDate2);
+//
+//			System.out.println(isBefore);
+//		}catch (Exception e){
+//
+//		}
+
+	}
+
+}

+ 367 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/FileUtil.java

@@ -0,0 +1,367 @@
+package com.storlead.framework.common.util;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.poi.excel.BigExcelWriter;
+import cn.hutool.poi.excel.ExcelUtil;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.xssf.streaming.SXSSFSheet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.security.MessageDigest;
+import java.text.DecimalFormat;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-08-22 14:49
+ */
+public class FileUtil extends cn.hutool.core.io.FileUtil {
+    private static final Logger log = LoggerFactory.getLogger(FileUtil.class);
+
+    /**
+     * 系统临时目录
+     * <br>
+     * windows 包含路径分割符,但Linux 不包含,
+     * 在windows \\==\ 前提下,
+     * 为安全起见 同意拼装 路径分割符,
+     * <pre>
+     *       java.io.tmpdir
+     *       windows : C:\Users/xxx\AppData\Local\Temp\
+     *       linux: /temp
+     * </pre>
+     */
+    public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;
+    /**
+     * 定义GB的计算常量
+     */
+    public static final int GB = 1024 * 1024 * 1024;
+    /**
+     * 定义MB的计算常量
+     */
+    public static final int MB = 1024 * 1024;
+    /**
+     * 定义KB的计算常量
+     */
+    public static final int KB = 1024;
+
+    /**
+     * 格式化小数
+     */
+    private static final DecimalFormat DF = new DecimalFormat("0.00");
+
+    public static final String IMAGE = "图片";
+    public static final String TXT = "文档";
+    public static final String MUSIC = "音乐";
+    public static final String VIDEO = "视频";
+    public static final String OTHER = "其他";
+
+
+    /**
+     * MultipartFile转File
+     */
+    public static File toFile(MultipartFile multipartFile) {
+        // 获取文件名
+        String fileName = multipartFile.getOriginalFilename();
+        // 获取文件后缀
+        String prefix = "." + getExtensionName(fileName);
+        File file = null;
+        try {
+            // 用uuid作为文件名,防止生成的临时文件重复
+            file = new File(SYS_TEM_DIR + IdUtil.simpleUUID() + prefix);
+            // MultipartFile to File
+            multipartFile.transferTo(file);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+        return file;
+    }
+
+    /**
+     * 获取文件扩展名,不带 .
+     */
+    public static String getExtensionName(String filename) {
+        if ((filename != null) && (filename.length() > 0)) {
+            int dot = filename.lastIndexOf('.');
+            if ((dot > -1) && (dot < (filename.length() - 1))) {
+                return filename.substring(dot + 1);
+            }
+        }
+        return filename;
+    }
+
+    /**
+     * Java文件操作 获取不带扩展名的文件名
+     */
+    public static String getFileNameNoEx(String filename) {
+        if ((filename != null) && (filename.length() > 0)) {
+            int dot = filename.lastIndexOf('.');
+            if ((dot > -1) && (dot < (filename.length()))) {
+                return filename.substring(0, dot);
+            }
+        }
+        return filename;
+    }
+
+    /**
+     * 文件大小转换
+     */
+    public static String getSize(long size) {
+        String resultSize;
+        if (size / GB >= 1) {
+            //如果当前Byte的值大于等于1GB
+            resultSize = DF.format(size / (float) GB) + "GB   ";
+        } else if (size / MB >= 1) {
+            //如果当前Byte的值大于等于1MB
+            resultSize = DF.format(size / (float) MB) + "MB   ";
+        } else if (size / KB >= 1) {
+            //如果当前Byte的值大于等于1KB
+            resultSize = DF.format(size / (float) KB) + "KB   ";
+        } else {
+            resultSize = size + "B   ";
+        }
+        return resultSize;
+    }
+
+    /**
+     * inputStream 转 File
+     */
+    static File inputStreamToFile(InputStream ins, String name){
+        File file = new File(SYS_TEM_DIR + name);
+        if (file.exists()) {
+            return file;
+        }
+        OutputStream os = null;
+        try {
+            os = new FileOutputStream(file);
+            int bytesRead;
+            int len = 8192;
+            byte[] buffer = new byte[len];
+            while ((bytesRead = ins.read(buffer, 0, len)) != -1) {
+                os.write(buffer, 0, bytesRead);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            CloseUtil.close(os);
+            CloseUtil.close(ins);
+        }
+        return file;
+    }
+
+    /**
+     * 将文件名解析成文件的上传路径
+     */
+    public static File upload(MultipartFile file, String filePath,String meMillis) {
+//        String name = getFileNameNoEx(file.getOriginalFilename());
+        String suffix = getExtensionName(file.getOriginalFilename());
+        String nowStr = meMillis;
+        try {
+            String fileName = nowStr + "." + suffix;
+            String path = filePath + fileName;
+            // getCanonicalFile 可解析正确各种路径
+            File dest = new File(path).getCanonicalFile();
+            // 检测是否存在目录
+            if (!dest.getParentFile().exists()) {
+                if (!dest.getParentFile().mkdirs()) {
+                    System.out.println("was not successful.");
+                }
+            }
+            // 文件写入
+            file.transferTo(dest);
+            return dest;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 导出excel
+     */
+    public static void downloadExcel(List<Map<String, Object>> list, HttpServletResponse response) throws IOException {
+        String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
+        File file = new File(tempPath);
+        BigExcelWriter writer = ExcelUtil.getBigWriter(file);
+        // 一次性写出内容,使用默认样式,强制输出标题
+        writer.write(list, true);
+        SXSSFSheet sheet = (SXSSFSheet)writer.getSheet();
+        //上面需要强转SXSSFSheet  不然没有trackAllColumnsForAutoSizing方法
+        sheet.trackAllColumnsForAutoSizing();
+        //列宽自适应
+        writer.autoSizeColumnAll();
+        //response为HttpServletResponse对象
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
+        //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
+        response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
+        ServletOutputStream out = response.getOutputStream();
+        // 终止后删除临时文件
+        file.deleteOnExit();
+        writer.flush(out, true);
+        //此处记得关闭输出Servlet流
+        IoUtil.close(out);
+    }
+
+    public static String getFileType(String type) {
+        String documents = "txt doc pdf ppt pps xlsx xls docx";
+        String music = "mp3 wav wma mpa ram ra aac aif m4a";
+        String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
+        String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
+        if (image.contains(type)) {
+            return "images";
+        } else if (documents.contains(type)) {
+            return "txt";
+        } else if (music.contains(type)) {
+            return "music";
+        } else if (video.contains(type)) {
+            return "video";
+        } else {
+            return "other";
+        }
+    }
+    public static String getFileTypeName(String type) {
+        String documents = "txt doc pdf ppt pps xlsx xls docx";
+        String music = "mp3 wav wma mpa ram ra aac aif m4a";
+        String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
+        String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
+        if (image.contains(type)) {
+            return IMAGE;
+        } else if (documents.contains(type)) {
+            return TXT;
+        } else if (music.contains(type)) {
+            return MUSIC;
+        } else if (video.contains(type)) {
+            return VIDEO;
+        } else {
+            return OTHER;
+        }
+    }
+
+    public static void checkSize(long maxSize, long size) throws Exception {
+        // 1M
+        int len = 1024 * 1024 * 500;
+        if (size > (maxSize * len)) {
+            throw new Exception("文件超出规定大小");
+        }
+    }
+
+    /**
+     * 判断两个文件是否相同
+     */
+    public static boolean check(File file1, File file2) {
+        String img1Md5 = getMd5(file1);
+        String img2Md5 = getMd5(file2);
+        if(img1Md5 != null){
+            return img1Md5.equals(img2Md5);
+        }
+        return false;
+    }
+
+    /**
+     * 判断两个文件是否相同
+     */
+    public static boolean check(String file1Md5, String file2Md5) {
+        return file1Md5.equals(file2Md5);
+    }
+
+    private static byte[] getByte(File file) {
+        // 得到文件长度
+        byte[] b = new byte[(int) file.length()];
+        InputStream in = null;
+        try {
+            in = new FileInputStream(file);
+            try {
+                System.out.println(in.read(b));
+            } catch (IOException e) {
+                log.error(e.getMessage(), e);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        } finally {
+            CloseUtil.close(in);
+        }
+        return b;
+    }
+
+    private static String getMd5(byte[] bytes) {
+        // 16进制字符
+        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+        try {
+            MessageDigest mdTemp = MessageDigest.getInstance("MD5");
+            mdTemp.update(bytes);
+            byte[] md = mdTemp.digest();
+            int j = md.length;
+            char[] str = new char[j * 2];
+            int k = 0;
+            // 移位 输出字符串
+            for (byte byte0 : md) {
+                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
+                str[k++] = hexDigits[byte0 & 0xf];
+            }
+            return new String(str);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 下载文件
+     *
+     * @param request  /
+     * @param response /
+     * @param file     /
+     */
+    public static void downloadFile(HttpServletRequest request, HttpServletResponse response, File file, boolean deleteOnExit) {
+        response.setCharacterEncoding(request.getCharacterEncoding());
+        response.setContentType("application/octet-stream");
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(file);
+            response.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
+            IOUtils.copy(fis, response.getOutputStream());
+            response.flushBuffer();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                    if (deleteOnExit) {
+                        file.deleteOnExit();
+                    }
+                } catch (IOException e) {
+                    log.error(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+//    public static String formatFileSize(long size) {
+//        if (Objects.isNull(size)) {
+//            return "0 B";
+//        }
+//        if (size < 1024) {
+//            return size + " B";
+//        } else if (size < 1024 * 1024) {
+//            return String.format("%.2f KB", size / 1024.0);
+//        } else if (size < 1024 * 1024 * 1024) {
+//            return String.format("%.2f MB", size / (1024.0 * 1024));
+//        } else {
+//            return String.format("%.2f GB", size / (1024.0 * 1024 * 1024));
+//        }
+//    }
+    public static String getMd5(File file) {
+        return getMd5(getByte(file));
+    }
+}

+ 150 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/HtmlUtils.java

@@ -0,0 +1,150 @@
+package com.storlead.framework.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.beans.PropertyEditorSupport;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * 类描述:时间操作定义类
+ *
+ * @Author:
+ * @Date:2012-12-8 12:15:03
+ * @Version 1.0
+ */
+public class HtmlUtils extends PropertyEditorSupport {
+
+
+	/**
+	 * content
+	 *
+	 */
+//	public static String userContent(String content) {
+//		// <p>段落替换为换行
+//		content = content.replaceAll("<p .*?>", "\r\n");
+//// <br><br/>替换为换行
+//		content = content.replaceAll("<br\\s*/?>", "\r\n");
+//// 去掉其它的<>之间的东西
+//		content = content.replaceAll("\\<.*?>", "");
+//		content = content.replaceAll("&nbsp;", " ");
+//// 还原HTML
+//// content = HTMLDecoder.decode(content);
+//		return content;
+//	}
+
+
+	 /***
+	  * @Description: 不处理HTML标签
+	  * @Param:
+	  * @return:
+	  * @Author: YPZ
+	  * @Date: 2022/11/29 14:32
+	  */
+	 public static String userContent(Object var) {
+		 if (Objects.isNull(var)){
+			 return "";
+		 }
+	 	 String inputString = var.toString();
+		 if (StringUtils.isNumeric(inputString)) {
+			 return inputString;
+		 }
+		 if(StringUtils.isBlank(inputString)){
+			 return inputString;
+		 }
+		 if (inputString.indexOf("http") >= 0 || inputString.indexOf("<img") >= 0) {
+			 return inputString;
+		 }
+		 return clearContent(inputString);
+	 }
+	/**
+	 * 去除inputString中的HTML/CSS/JS标签
+	 */
+	public static String clearContent(String inputString) {
+		if(StringUtils.isBlank(inputString)){
+			return inputString;
+		}
+
+		String htmlStr = inputString;
+		String textStr = "";
+		Pattern p_script;
+		Matcher m_script;
+		Pattern p_style;
+		Matcher m_style;
+		Pattern p_html;
+		Matcher m_html;
+		Pattern p_html1;
+		Matcher m_html1;
+
+		try {
+			String regEx_script = "<[//s]*?script[^>]*?>[//s//S]*?<[//s]*?///[//s]*?script[//s]*?>";
+			String regEx_style = "<[//s]*?style[^>]*?>[//s//S]*?<[//s]*?///[//s]*?style[//s]*?>";
+			String regEx_html = "<[^>]+>";
+			String regEx_html1 = "<[^>]+";
+
+			p_script = Pattern.compile(regEx_script, Pattern.CASE_INSENSITIVE);
+
+			m_script = p_script.matcher(htmlStr);
+			htmlStr = m_script.replaceAll("");
+
+			p_style = Pattern.compile(regEx_style, Pattern.CASE_INSENSITIVE);
+
+			m_style = p_style.matcher(htmlStr);
+
+			htmlStr = m_style.replaceAll("");
+
+			p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
+
+			m_html = p_html.matcher(htmlStr);
+
+			htmlStr = m_html.replaceAll("");
+
+			p_html1 = Pattern.compile(regEx_html1, Pattern.CASE_INSENSITIVE);
+
+			m_html1 = p_html1.matcher(htmlStr);
+
+			htmlStr = m_html1.replaceAll("");
+
+			htmlStr = htmlStr.replaceAll("&nbsp;", " ");
+			textStr = htmlStr;
+
+		} catch (Exception e) {
+			System.err.println("Html2Text: " + e.getMessage());
+		}
+		return textStr;
+	}
+
+
+
+	/**
+	 * @Description:  统计有效字符长度
+	 * @Param:
+	 * @return:
+	 * @Author: YPZ
+	 * @Date: 2022-7-4
+	 */
+
+	public static int userContentSize(String content) {
+		// <p>段落替换为换行
+		content = content.replaceAll("<p .*?>", "");
+// <br><br/>替换为换行
+		content = content.replaceAll("<br\\s*/?>", "");
+// 去掉其它的<>之间的东西
+		content = content.replaceAll("\\<.*?>", "");
+		content = content.replaceAll("&nbsp;", "");
+// 还原HTML
+// content = HTMLDecoder.decode(content);
+		return content.length();
+	}
+
+	public static void main(String[] args) {
+//		String content = "<p>一、SP21014.01,&nbsp; 网表与客户光纤那部分代码合成编译后测试仍有问题,时钟域太多,得去掉一部分;</p><p>二、SP22001.00,DDR3多通道读功能还有问题;</p><p>三、SP19021.01,&nbsp; 30000个文件情况下列表性能测试;</p>";
+		String content = "<p><span style=\"color: rgb(96, 98, 102); background-color: rgb(245, 247, 250); font-size: 13px;\">年度目标3</span></p>";
+		System.out.println(userContent(content));
+		System.out.println(userContent(content).length());
+		System.out.println(userContentSize(content));
+	}
+
+}

+ 151 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/HttpUtil.java

@@ -0,0 +1,151 @@
+package com.storlead.framework.common.util;
+
+import cn.hutool.http.HttpRequest;
+import com.alibaba.fastjson.JSONObject;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-14 15:17
+ */
+public class HttpUtil {
+
+    /**
+     * 发送GET请求并返回响应内容
+     * @param url
+     * @param body 请求体
+     * @param headers 请求头
+     * @param timeout 超时时间
+     * @return
+     * @throws IOException
+     */
+    public static String get(String url, Map<String, String> body, Map<String, String> headers, int timeout) throws IOException {
+        Map<String, String> header = new HashMap<>();
+        header.put("Content-Type", "application/json;charset=UTF-8");
+        header.put("Accept", "application/json;charset=UTF-8");
+        if (Objects.nonNull(headers)) {
+            header.putAll(headers);
+        }
+        HttpRequest httpRequest = HttpRequest.get(url).addHeaders(header).body(JSONObject.toJSONString(body));
+        if (timeout > 0) {
+            httpRequest.setConnectionTimeout(timeout);
+            httpRequest.setReadTimeout(timeout);
+        }
+        String response = httpRequest.execute().body();
+        return decodeUnicode(response);
+    }
+
+    /**
+     * 发送POST请求并返回响应内容
+     * @param url
+     * @param body 请求体
+     * @param headers 请求头
+     * @param timeout 超时时间
+     * @return
+     * @throws IOException
+     */
+    public static String post(String url, Map<String, Object> body,Map<String, String> headers, int timeout) throws IOException {
+        Map<String, String> header = new HashMap<>();
+        header.put("Content-Type","application/json;charset=UTF-8");
+        header.put("Accept","application/json;charset=UTF-8");
+        if (Objects.nonNull(headers)) {
+            header.putAll(headers);
+        }
+        HttpRequest httpRequest = HttpRequest.post(url).addHeaders(header).body(JSONObject.toJSONString(body));
+        if (timeout > 0) {
+            httpRequest.setConnectionTimeout(timeout);
+            httpRequest.setReadTimeout(timeout);
+        }
+        String response = httpRequest.execute().body();
+
+        return decodeUnicode(response);
+    }
+
+    /**
+     * http 请求数据返回 json 中中文字符为 unicode 编码转汉字转码
+     * @param theString
+     * @return
+     */
+    public static String decodeUnicode(String theString) {
+        char aChar;
+        int len = theString.length();
+        StringBuffer outBuffer = new StringBuffer(len);
+        for (int x = 0; x < len;) {
+            aChar = theString.charAt(x++);
+            if (aChar == '\\') {
+                aChar = theString.charAt(x++);
+                if (aChar == 'u') {
+                    int value = 0;
+                    for (int i = 0; i < 4; i++) {
+                        aChar = theString.charAt(x++);
+                        switch (aChar) {
+                            case '0':
+                            case '1':
+                            case '2':
+                            case '3':
+                            case '4':
+                            case '5':
+                            case '6':
+                            case '7':
+                            case '8':
+                            case '9':
+                                value = (value << 4) + aChar - '0';
+                                break;
+                            case 'a':
+                            case 'b':
+                            case 'c':
+                            case 'd':
+                            case 'e':
+                            case 'f':
+                                value = (value << 4) + 10 + aChar - 'a';
+                                break;
+                            case 'A':
+                            case 'B':
+                            case 'C':
+                            case 'D':
+                            case 'E':
+                            case 'F':
+                                value = (value << 4) + 10 + aChar - 'A';
+                                break;
+                            default:
+                                throw new IllegalArgumentException(
+                                        "Malformed   \\uxxxx   encoding.");
+                        }
+
+                    }
+                    outBuffer.append((char) value);
+                } else {
+                    if (aChar == 't')
+                        aChar = '\t';
+                    else if (aChar == 'r')
+                        aChar = '\r';
+                    else if (aChar == 'n')
+                        aChar = '\n';
+                    else if (aChar == 'f')
+                        aChar = '\f';
+                    outBuffer.append(aChar);
+                }
+            } else {
+                outBuffer.append(aChar);
+            }
+        }
+        return outBuffer.toString();
+    }
+
+    public static void main(String[] args) {
+//        try {
+//            Map<String, Object> parameters = new HashMap<>();
+//            parameters.put("activationCode", "activationCode");
+//            String response = HttpUtil.sendPost("http://localhost:10010/api/tenant/license/getLicenseCode", parameters,5000);
+//            log.error("response = "+response);
+//        }catch (Exception e) {
+//            log.error("error = ",e);
+//        }
+    }
+}

+ 529 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/LocalDateUtils.java

@@ -0,0 +1,529 @@
+package com.storlead.framework.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.time.*;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.util.*;
+
+import static java.time.ZoneId.SHORT_IDS;
+/**
+ * @program: sp-sales
+ * @description: jdk8日期判断工具类
+ * @author: xiaojiawei
+ * @create: 2022-01-18 09:57
+ **/
+@Slf4j
+public class LocalDateUtils {
+
+
+    public static Date LocalDate2Date(LocalDate date){
+        ZoneId zone = ZoneId.systemDefault();
+        Instant instant = date.atStartOfDay().atZone(zone).toInstant();
+        Date da = Date.from(instant);
+        return da;
+    }
+    public static LocalDate date2LocalDate(Date date){
+        Instant instant =date.toInstant();
+        ZoneId zone = ZoneId.systemDefault();
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
+        LocalDate localDate= localDateTime.toLocalDate();
+        return localDate;
+    }
+    public static LocalDateTime date2LocalDateTime(Date date){
+        Instant instant = date.toInstant();
+        ZoneId zoneId = ZoneId.systemDefault();
+
+        LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();
+        return localDateTime;
+    }
+
+    public static Date localDateTime2date(LocalDateTime localDateTime){
+        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+    /**
+     * 从时间戳转换成 LocalDateTime
+     * @param timestamp
+     * @return
+     */
+    private static LocalDateTime timestamp(Long timestamp){
+        Date date = new Date(timestamp);
+        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.of(SHORT_IDS.get("CTT")));
+    }
+
+    public static boolean isBetween(LocalDate date ,LocalDate t1,LocalDate t2){
+        if(t1.isAfter(t2)){
+            LocalDate tmp = t1;
+            t1=t2;
+            t2=tmp;
+        }
+        if(date.isBefore(t1)){
+            return false;
+        }
+        if(date.isAfter(t2)){
+            return false;
+        }
+        return true;
+    }
+    /**
+     * @Description: 计算两个日期相隔的几周 ,e.g 本周日和下周一 相隔一周
+     * @Param: [t1, t2]
+     * @return: long  相隔几周
+     * @Author: xiaojiawei
+     * @Date: 2022/1/18
+     */
+    public static long interWeeks(LocalDate t1, LocalDate t2){
+        if(t1.isAfter(t2)){
+            LocalDate tmp = t1;
+            t1=t2;
+            t2=tmp;
+        }
+        t1 = t1.with(ChronoField.DAY_OF_WEEK, DayOfWeek.MONDAY.getValue());
+        long weeks =  ChronoUnit.WEEKS.between(t1,t2);
+        return weeks;
+    }
+    /**
+     * 以second为主进行判断
+     * 以first为开始时间,second为结束时间
+     * 判断两个时间是否在通过一个周内。
+     *
+     * @param first
+     * @param second
+     */
+    private static Boolean checkWeekBetween(LocalDateTime first, LocalDateTime second){
+        //两个时间差不超过7天,
+        Period period = Period.between(first.toLocalDate(),second.toLocalDate());
+        int years = period.getYears();
+        log.info("years:{}",years);
+        if(years > 0){
+            return false;
+        }
+        int months = period.getMonths();
+        log.info("months:{}",months);
+        if(months > 0){
+            return false;
+        }
+        int days = period.getDays();
+        log.info("days:{}",days);
+        if(days == 0){
+//          表明是同一天
+            return true;
+        }
+        if(days > 7  || days < -7){
+//            两个时间差 超出了7天
+            return false;
+        }
+        int firstDayOfWeek = first.getDayOfWeek().getValue();
+        int secondDayOfWeek = second.getDayOfWeek().getValue();
+        if(secondDayOfWeek == 1){
+            if(oneDay(firstDayOfWeek,secondDayOfWeek,days)){
+                return true;
+            }else {
+                return false;
+            }
+        }
+        if(secondDayOfWeek == 7){
+            if(sevenDay(firstDayOfWeek,secondDayOfWeek,days)){
+                return true;
+            }else {
+                return false;
+            }
+        }
+        if(otherDay(firstDayOfWeek,secondDayOfWeek,days)){
+            return true;
+        }else{
+            return false;
+        }
+    }
+
+    /**
+     * secondDayOfWeek 是所在星期的第一天
+     * 星期的第一天 数据处理
+     * @return
+     */
+    private static Boolean oneDay(int firstDayOfWeek,int secondDayOfWeek,int days){
+        if(days > 0 ){
+            // 表明 first 比second 小 不在同一周
+            return false;
+        }else {
+            //  表明 first 比second 大
+            if(secondDayOfWeek - days == firstDayOfWeek){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 星期的第7天的时候处理数据
+     * @return
+     */
+    private static Boolean sevenDay(int firstDayOfWeek,int secondDayOfWeek,int days){
+//        second 是周日
+        if(firstDayOfWeek + days == secondDayOfWeek){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 其他天的数据处理
+     * @return
+     */
+    private static Boolean otherDay(int firstDayOfWeek,int secondDayOfWeek,int days){
+        if(days < 0){
+            //表明 first 比 second 大
+            if((secondDayOfWeek - days) == firstDayOfWeek){
+                //两者是 一周内
+                return true;
+            }
+        }else {
+            //表明 first 比 second 小
+            if(firstDayOfWeek + days == secondDayOfWeek){
+                //两者是 一周内
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取几月份的第几周 [X月份第X周]
+     *
+     * @param sourceTime 这个推荐取00:00:00时间
+     * @return
+     */
+    public static Map<String, Object> getMonthNoAndWeekNo(LocalDateTime sourceTime) {
+        Map<String, Object> map = new HashMap<>();
+        String monthNoAndWeekNo = null;
+        //获取当月的第一天
+        LocalDateTime firstDayOfMonth = getFirstLocalDayOfMonth(sourceTime);
+
+        //获取月第一天开始的周一,从当月第一天开始找
+        LocalDateTime firstMondayOfMonth = firstDayOfMonth;
+        for (int i = 0; i < 6; i++) {
+            DayOfWeek dayOfWeekTemp = firstMondayOfMonth.getDayOfWeek();
+            if (dayOfWeekTemp.equals(DayOfWeek.MONDAY)) {
+                break;
+            }
+            //往后推一天
+            firstMondayOfMonth = firstMondayOfMonth.plusDays(1);
+        }
+
+        //比较当月的第一个星期一 < = 参数时间
+        if (!firstMondayOfMonth.isBefore(sourceTime)) {
+            //如果当月的第一个周一大于参数时间,则要计算到上个月份去
+            //获取上一个月的第一个周一
+            LocalDateTime lastMontLocalDateTime = sourceTime.minusMonths(1);
+            //上个月的第一天
+            LocalDateTime firstDayOfMonth_last = getFirstLocalDayOfMonth(lastMontLocalDateTime);
+            //从上个月的第一天开始找周一
+            LocalDateTime firstMondayOfMonth_last = firstDayOfMonth_last;
+            for (int i = 0; i < 6; i++) {
+                DayOfWeek dayOfWeekTemp = firstMondayOfMonth_last.getDayOfWeek();
+                if (dayOfWeekTemp.equals(DayOfWeek.MONDAY)) {
+                    break;
+                }
+                //往后推一天
+                firstMondayOfMonth_last = firstMondayOfMonth_last.plusDays(1);
+            }
+
+            //  计算两个时间间隔天数 (上月第一个周一 减去 当前时间)
+            Duration duration = Duration.between(firstMondayOfMonth_last, sourceTime);
+            long diffDays = duration.toDays(); //相差的天数
+            //第几周weekNo
+            long weekNo = (diffDays / 7) + 1;
+            //月份
+            int monthNo = firstMondayOfMonth_last.getMonth().getValue();//汉字版月份
+            String monthChinese = getMonthChinese(monthNo);
+            monthNoAndWeekNo = monthChinese + "月份" + "第" + weekNo + "周";
+            map.put("yearNo",firstMondayOfMonth_last.getYear());
+            map.put("monthNo", monthNo);
+            map.put("weekNo", weekNo);
+            map.put("monthNoAndWeekNo", monthNoAndWeekNo);
+
+        } else {
+            //当月第一个周一在当前时间之前 firstMondayOfMonth<=sourceTime
+            //计算两个时间间隔天数
+            int dayOfMonthFirstMonday = firstMondayOfMonth.getDayOfMonth();
+            int dayOfMonthSourceTime = sourceTime.getDayOfMonth();
+
+            int diffDays = dayOfMonthSourceTime - dayOfMonthFirstMonday;
+            //第几周weekNo
+            int weekNo = (diffDays / 7) + 1;
+            //月份
+            int monthNo = sourceTime.getMonth().getValue();
+            map.put("yearNo",sourceTime.getYear());
+            map.put("monthNo", monthNo);
+            map.put("weekNo", weekNo);
+            //汉字版月份
+            String monthChinese = getMonthChinese(monthNo);
+            monthNoAndWeekNo = monthChinese + "月份" + "第" + weekNo + "周";
+            map.put("monthNoAndWeekNo", monthNoAndWeekNo);
+        }
+
+        return map;
+    }
+    /**
+     * 获取汉字版月份
+     *
+     * @param monthNo 第几月
+     * @return
+     */
+    private static String getMonthChinese(int monthNo) {
+        switch (monthNo) {
+            case 1:
+                return "一";
+            case 2:
+                return "二";
+            case 3:
+                return "三";
+            case 4:
+                return "四";
+            case 5:
+                return "五";
+            case 6:
+                return "六";
+            case 7:
+                return "七";
+            case 8:
+                return "八";
+            case 9:
+                return "九";
+            case 10:
+                return "十";
+            case 11:
+                return "十一";
+            case 12:
+                return "十二";
+            default:
+                break;
+        }
+        return null;
+    }
+    /**
+     * 获取某月第一天的00:00:00
+     *
+     * @return
+     */
+    public static LocalDateTime getFirstLocalDayOfMonth(LocalDateTime localDateTime) {
+        return localDateTime.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
+    }
+
+    public static Date LocalDateTimeTodate(LocalDateTime localDateTime) {
+        Date date = Date.from( localDateTime.atZone( ZoneId.systemDefault()).toInstant());
+        return date;
+    }
+    /**
+     * 获取当前日期所在季度的开始日期和结束日期
+     * 季度一年四季, 第一季度:1月-3月, 第二季度:4月-6月, 第三季度:7月-9月, 第四季度:10月-12月
+     * @param isFirst  true表示查询本季度开始日期  false表示查询本季度结束日期
+     * @return
+     */
+    public static LocalDate getStartOrEndDayOfQuarter(Boolean isFirst){
+        LocalDate today=LocalDate.now();
+        LocalDate resDate = LocalDate.now();
+        if (today == null) {
+            today = resDate;
+        }
+        Month month = today.getMonth();
+        Month firstMonthOfQuarter = month.firstMonthOfQuarter();
+        Month endMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
+        if (isFirst) {
+            resDate = LocalDate.of(today.getYear(), firstMonthOfQuarter, 1);
+        } else {
+            resDate = LocalDate.of(today.getYear(), endMonthOfQuarter, endMonthOfQuarter.length(today.isLeapYear()));
+        }
+        return resDate;
+    }
+    /**
+     * @Description: 获取当前季度,根据月转换到季度
+     * @Param:
+     * @return:
+     * @Author: YPZ
+     * @Date: 2022-6-30
+     */
+    public static int getQuarterOfYear() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new Date());
+        return calendar.get(Calendar.MONTH) / 3 + 1;
+    }
+
+    /**
+     * @Description: 获取时间对应季度
+     * @Param:
+     * @return:
+     * @Author: YPZ
+     * @Date: 2022-6-30
+     */
+    public static int getQuarter(LocalDateTime localDateTime) {
+        return (localDateTime.getMonthValue()-1)/3 + 1;
+    }
+    /**
+     * @Description: 根据季度获取第一天/最后一天,从季度到第一个月,再到月的第一天
+     * @Param: year,quarter,isFirst(true表示查询本季度开始日期  false表示查询本季度结束日期)
+     * @return:
+     * @Author: YPZ
+     * @Date: 2022-6-30
+     */
+    public static LocalDate getStartDayOfQuarter(int year, int quarter,Boolean isFirst) {
+
+        int startMonth = (quarter - 1) * 3;
+
+        int lastMonth = quarter * 3 - 1;
+
+        // 根据月获取开始时间
+        Calendar cal = Calendar.getInstance();
+
+        cal.set(Calendar.YEAR, year);
+        if(isFirst) {
+            cal.set(Calendar.MONTH, startMonth);
+            cal.set(Calendar.DAY_OF_MONTH, 1);
+        }else {
+            cal.set(Calendar.MONTH, lastMonth);
+            cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+        }
+
+        cal.set(Calendar.HOUR, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+
+        return cal.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+    }
+
+     /***
+      * @Description: 获取两个localDate间的所有日期(从小到大排序)
+      * @Param:
+      * @return:
+      * @Author: YPZ
+      * @Date: 2022/9/7 17:55
+      */
+    public static  List<LocalDate> getAscDateList(LocalDate startDate,LocalDate endDate) {
+        List<LocalDate> result = new ArrayList<>();
+        if(endDate.compareTo(startDate) < 0 ){
+            return  result;
+        }
+        while (true){
+            result.add(startDate);
+            if(startDate.compareTo(endDate) >= 0){
+                break;
+            }
+            startDate = startDate.plusDays(1);
+        }
+        return result;
+    }
+    /***
+      * @Description: java获取两个LocalDate时间段内,每隔固定天数的所有日期
+      * @Param:
+      * @return:
+      * @Author: YPZ
+      * @Date: 2023/9/19 11:26
+      */
+    public static List<LocalDate> getEveryOtherDayBetweenDates(LocalDate startDate, LocalDate endDate,Integer days) {
+        List<LocalDate> result = new ArrayList<>();
+        days = days +1;
+        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(days)) {
+            result.add(date);
+        }
+        return result;
+    }
+
+    // 静态方法,用于计算日期范围内的每隔两周的周一和周日
+    public static List<LocalDate> getMondaysAndSundays(LocalDate startDate, LocalDate endDate,int weeks) {
+        weeks = weeks+1;
+        List<LocalDate> result = new ArrayList<>();
+
+        // 初始日期调整到当前周的周一
+        startDate = startDate.with(DayOfWeek.MONDAY);
+
+        // 如果第一周的周一小于开始日期,将开始日期添加到结果列表
+        if (!startDate.isBefore(startDate)) {
+
+            result.add(startDate);
+        }
+
+        // 计算两周的时间间隔
+        long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
+
+        // 计算每隔两周的日期
+        for (long i = weeks*7; i <= daysBetween; i += weeks*7) {
+            LocalDate monday = startDate.plusDays(i);
+            result.add(monday);
+        }
+
+        return result;
+    }
+
+
+    public static void main(String[] args) {
+//        System.out.println(getStartOrEndDayOfQuarter(false));
+//        //获取当前年份
+//        Calendar calendar = Calendar.getInstance();
+//        int year = calendar.get(Calendar.YEAR);
+//        // 获取当前季度
+//        int month = calendar.get(Calendar.MONTH);
+//        int quarter = month%3==0?month/3:month/3+1;
+//        System.out.println(year);
+//        System.out.println(quarter);
+//        System.out.println(getStartDayOfQuarter(2021, 3, true));
+//        System.out.println(getStartDayOfQuarter(2021, 3, false));
+//
+//        LocalDateTime start = LocalDateTime.now();
+//        int year = start.getYear();
+//        int quarter = getQuarter(start);
+//
+//
+//        System.out.println("年度为:"+year);
+//        System.out.println("季度为:"+quarter);
+//        String dateStr = "2022-08-29";
+//
+//        LocalDate localDate = LocalDate.parse(dateStr , DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+//
+//        if(localDate.equals(localDate.with(TemporalAdjusters.lastDayOfMonth()))){
+//            System.out.println("是月底");
+//        }
+//        if(localDate.equals(localDate.with(DayOfWeek.SUNDAY))){
+//            System.out.println("是周日");
+//        }
+//
+//        String beginDateStr = "2022-08-29";
+//        LocalDate beginDate = LocalDate.parse(beginDateStr , DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+//        String endDateStr = "2022-09-09";
+//        LocalDate endDate = LocalDate.parse(endDateStr , DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+//        List<LocalDate> localDateList = getAscDateList(beginDate,endDate);
+//        localDateList.forEach(d->System.out.println(d));
+
+//        LocalDate startDate = LocalDate.of(2022, 1, 1);
+//        LocalDate endDate = LocalDate.of(2022, 1, 10);
+//        List<LocalDate> result = getEveryOtherDayBetweenDates(startDate, endDate,1);
+//        for (LocalDate date : result) {
+//            System.out.println(date);
+//        }
+
+//        LocalDate startDate = LocalDate.of(2023, 9, 16);
+//        LocalDate endDate = LocalDate.of(2023, 12, 30);
+//
+//        List<LocalDate> dateRanges = getMondaysAndSundays(startDate, endDate,2);
+//
+//        for (LocalDate vo : dateRanges) {
+//            System.out.println("Start Date: " + vo);
+//        }
+
+        LocalDate startDate = LocalDate.of(2023, 1, 2);
+        LocalDate endDate = LocalDate.of(2028, 11, 26);
+
+        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusYears(2)) {
+            LocalDate firstDayOfMonth = date.withDayOfYear(1);
+            LocalDate lastDayOfMonth = date.withDayOfYear(date.lengthOfYear());
+            System.out.println("月份开始时间:" + firstDayOfMonth);
+            System.out.println("月份结束时间:" + lastDayOfMonth);
+            System.out.println();
+        }
+
+
+
+
+    }
+}

+ 47 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/MD5Util.java

@@ -0,0 +1,47 @@
+package com.storlead.framework.common.util;
+
+import java.security.MessageDigest;
+
+public class MD5Util {
+
+	public static String byteArrayToHexString(byte b[]) {
+		StringBuffer resultSb = new StringBuffer();
+		for (int i = 0; i < b.length; i++){
+			resultSb.append(byteToHexString(b[i]));
+		}
+		return resultSb.toString();
+	}
+
+	private static String byteToHexString(byte b) {
+		int n = b;
+		if (n < 0) {
+			n += 256;
+		}
+		int d1 = n / 16;
+		int d2 = n % 16;
+		return hexDigits[d1] + hexDigits[d2];
+	}
+
+	public static String MD5Encode(String origin, String charsetname) {
+		String resultString = null;
+		try {
+			resultString = new String(origin);
+			MessageDigest md = MessageDigest.getInstance("MD5");
+			if (charsetname == null || "".equals(charsetname)) {
+				resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
+			} else {
+				resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
+			}
+		} catch (Exception exception) {
+		}
+		return resultString;
+	}
+
+	private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
+			"6", "7", "8", "9", "A", "B", "C", "D", "E", "F" };
+
+	public static void main(String[] args) {
+		System.out.println(MD5Encode("otnt3isebpmz", "utf-8"));
+	}
+
+}

+ 227 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/OssBootUtil.java

@@ -0,0 +1,227 @@
+package com.storlead.framework.common.util;
+
+import com.aliyun.oss.ClientConfiguration;
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.common.auth.DefaultCredentialProvider;
+import com.aliyun.oss.model.CannedAccessControlList;
+import com.aliyun.oss.model.OSSObject;
+import com.aliyun.oss.model.PutObjectResult;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.tomcat.util.http.fileupload.FileItemStream;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+
+/**
+ * @Description: 阿里云 oss 上传工具类(高依赖版)
+ * @Date: 2019/5/10
+ */
+@Slf4j
+public class OssBootUtil {
+
+    private static Boolean loading;
+    private static String endPoint;
+    private static String accessKeyId;
+    private static String accessKeySecret;
+    private static String bucketName;
+
+    public static void setLoading(Boolean loading) {
+        OssBootUtil.loading = loading;
+    }
+
+    public static void setEndPoint(String endPoint) {
+        OssBootUtil.endPoint = endPoint;
+    }
+
+    public static void setAccessKeyId(String accessKeyId) {
+        OssBootUtil.accessKeyId = accessKeyId;
+    }
+
+    public static void setAccessKeySecret(String accessKeySecret) {
+        OssBootUtil.accessKeySecret = accessKeySecret;
+    }
+
+    public static void setBucketName(String bucketName) {
+        OssBootUtil.bucketName = bucketName;
+    }
+
+    /**
+     * oss 工具客户端
+     */
+    private static OSSClient ossClient = null;
+    private static String FILE_URL;
+
+    /**
+     * 上传文件至阿里云 OSS
+     * 文件上传成功,返回文件完整访问路径
+     * 文件上传失败,返回 null
+     *
+     * @param file    待上传文件
+     * @param fileDir 文件保存目录
+     * @return oss 中的相对文件路径
+     */
+    public static String upload(MultipartFile file, String fileDir) {
+        initOSS(endPoint, accessKeyId, accessKeySecret);
+        StringBuilder fileUrl = new StringBuilder();
+        try {
+            String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.'));
+            String preffix = file.getOriginalFilename().substring(0,file.getOriginalFilename().lastIndexOf('.'));
+            String fileName = preffix+DateUtils.getUploadFileName() + suffix;
+            if (!fileDir.endsWith("/")) {
+                fileDir = fileDir.concat("/");
+            }
+            fileUrl = fileUrl.append(fileDir + fileName);
+
+            String fileUrl1 = "https://" + bucketName + "." + endPoint + "/" + fileUrl;
+            //FILE_URL = staticDomain + "/" + fileUrl;
+            PutObjectResult result = ossClient.putObject(bucketName, fileUrl.toString(), file.getInputStream());
+            // 设置权限(公开读)
+            ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
+            if (result != null) {
+                log.info("------OSS文件上传成功------" + fileUrl);
+            }
+            return fileUrl1;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+
+    public static String upload(MultipartFile file, String fileDir,String newBucketName,String newEndPoint) {
+
+        initOSS(endPoint, accessKeyId, accessKeySecret);
+        StringBuilder fileUrl = new StringBuilder();
+        try {
+            String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.'));
+            String preffix = file.getOriginalFilename().substring(0,file.getOriginalFilename().lastIndexOf('.'));
+            String fileName = preffix+DateUtils.getUploadFileName() + suffix;
+            if (!fileDir.endsWith("/")) {
+                fileDir = fileDir.concat("/");
+            }
+            fileUrl = fileUrl.append(fileDir + fileName);
+            String fileUrl1  = "https://" + newBucketName + "." + newEndPoint + "/" + fileUrl;
+            //FILE_URL = staticDomain + "/" + fileUrl;
+            PutObjectResult result = ossClient.putObject(newBucketName, fileUrl.toString(), file.getInputStream());
+            // 设置权限(公开读)
+            ossClient.setBucketAcl(newBucketName, CannedAccessControlList.PublicRead);
+            if (result != null) {
+                log.info("------OSS文件上传成功------" + fileUrl);
+            }
+            return fileUrl1;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+
+    /**
+     * 上传文件至阿里云 OSS
+     * 文件上传成功,返回文件完整访问路径
+     * 文件上传失败,返回 null
+     *
+     * @param file    待上传文件
+     * @param fileDir 文件保存目录
+     * @return oss 中的相对文件路径
+     */
+    public static String upload(FileItemStream file, String fileDir) {
+        initOSS(endPoint, accessKeyId, accessKeySecret);
+        StringBuilder fileUrl = new StringBuilder();
+        try {
+            String suffix = file.getName().substring(file.getName().lastIndexOf('.'));
+            String fileName = UUID.randomUUID().toString().replace("-", "") + suffix;
+            if (!fileDir.endsWith("/")) {
+                fileDir = fileDir.concat("/");
+            }
+            fileUrl = fileUrl.append(fileDir + fileName);
+
+            String fileUrl1  = "https://" + bucketName + "." + endPoint + "/" + fileUrl;
+            //FILE_URL = staticDomain + "/" + fileUrl;
+            PutObjectResult result = ossClient.putObject(bucketName, fileUrl.toString(), file.openStream());
+            // 设置权限(公开读)
+            ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
+            if (result != null) {
+                System.out.println("------OSS文件上传成功------" + fileUrl);
+            }
+            return fileUrl1;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 根据文件名下载文件 获取文件的流
+     * 这里需要注意的就是 这里查找的oss文件目录是共有的目录 如果需要查找别的文件目录得话请勿使用该方法
+     *
+     * @param fileName 文件的名称 这里需要注意了这里的文件名是上传oss时候的文件名
+     * @return null 表示文件不存在
+     * @author timo
+     * @date 2019/1/10 21:46
+     */
+    public static InputStream downloadObjFileToInputStream(String fileName) {
+        return downloadObjFileToInputStream(bucketName, fileName);
+    }
+
+    /**
+     * 根据文件名下载文件
+     * 获取文件的流
+     *
+     * @param fileName   文件的名称 这里需要注意了这里的文件名是上传oss时候的文件名
+     * @param bucketName oss文件所在的目录
+     * @return null 表示文件不存在
+     * @author timo
+     * @date 2019/1/10 21:46
+     */
+    public static InputStream downloadObjFileToInputStream(String bucketName, String fileName) {
+        try {
+            OSSObject ossObject = new OSSClient(endPoint, accessKeyId, accessKeySecret).getObject(bucketName, fileName);
+            if (ossObject == null) {
+                throw new FileNotFoundException(fileName + " not in " + bucketName);
+            }
+            return ossObject.getObjectContent();
+        } catch (Exception e) {
+            e.getStackTrace();
+            log.info("获取文件失败:" + e.toString() + e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 删除文件
+     * @param url
+     */
+    public static void deleteUrl(String url) {
+        String bucketUrl = "https://" + bucketName + "." + endPoint + "/";
+        //String bucketUrl = staticDomain + "/";
+        url = url.replace(bucketUrl,"");
+        ossClient.deleteObject(bucketName, url);
+    }
+
+    /**
+     * 删除文件
+     * @param fileName
+     */
+    public static void delete(String fileName) {
+        ossClient.deleteObject(bucketName, fileName);
+    }
+
+    /**
+     * 初始化 oss 客户端
+     *
+     * @return
+     */
+    private static OSSClient initOSS(String endpoint, String accessKeyId, String accessKeySecret) {
+        if (ossClient == null) {
+            ossClient = new OSSClient(endpoint,
+                    new DefaultCredentialProvider(accessKeyId, accessKeySecret),
+                    new ClientConfiguration());
+        }
+        return ossClient;
+    }
+
+}

+ 232 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/PingyinUtil.java

@@ -0,0 +1,232 @@
+package com.storlead.framework.common.util;
+
+import net.sourceforge.pinyin4j.PinyinHelper;
+import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
+import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
+import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
+import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
+
+import java.text.Collator;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-09 17:58
+ */
+public class PingyinUtil {
+    /**
+     * 获取汉字串拼音,英文字符不变
+     */
+    public static String getLowFullSpell(String chinese) {
+        chinese = filterSpecialChars(chinese);
+        StringBuffer pybf = new StringBuffer();
+        char[] arr = chinese.toCharArray();
+        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
+        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
+        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+        for (int i = 0; i < arr.length; i++) {
+            if (arr[i] > 128) {
+                try {
+                    pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);
+                } catch (BadHanyuPinyinOutputFormatCombination e) {
+                    e.printStackTrace();
+                }
+            } else {
+                pybf.append(arr[i]);
+            }
+        }
+        return pybf.toString();
+    }
+
+    /**
+     * 获取汉字串拼音,英文字符不变
+     */
+    public static String getFullSpell(String chinese) {
+        chinese = filterSpecialChars(chinese);
+        StringBuffer pybf = new StringBuffer();
+        char[] arr = chinese.toCharArray();
+        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
+        defaultFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);
+        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+        for (int i = 0; i < arr.length; i++) {
+            if (arr[i] > 128) {
+                try {
+                    pybf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]);
+                } catch (BadHanyuPinyinOutputFormatCombination e) {
+                    e.printStackTrace();
+                }
+            } else {
+                pybf.append(arr[i]);
+            }
+        }
+        return pybf.toString();
+    }
+
+    /**
+     * 将集合数据按照汉字首字母分组排序
+     */
+    public static Map<String, Object> screenManufacturer(List<String> list) {
+        try {
+            Collator com = Collator.getInstance(Locale.CHINA);
+            // 按字母排序
+            Collections.sort(list, com);
+            // 输出26个字母
+            Map<String, Object> map = new TreeMap<>();
+            for (int i = 1; i < 27; i++) {
+                String word = String.valueOf((char)(96 + i)).toUpperCase();
+                // 循环找出 首字母一样的数据
+                List<String> letter = new ArrayList<>();
+                for (String str : list) {
+//                    String pybf = getFullSpell(str);
+                    String pybf = getFullPinyin(str);
+                    String zm = pybf.substring(0, 1);
+                    if (word.equals(zm)) {
+                        letter.add(str);
+                    }
+                }
+                map.put(word, letter);
+            }
+            return map;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+//    /**
+//     * 将集合数据按照汉字首字母分组排序
+//     */
+//    public static List<WxUserVO> wxUserList(List<UserInfo> userList) {
+//        try {
+//            List<String> userNameList = new ArrayList<>();
+//            Map<String,UserInfo> userInfoMap = new HashMap<>();
+//            for (UserInfo u:userList) {
+//                userNameList.add(u.getRealName()+u.getId());
+//                userInfoMap.put(u.getRealName()+u.getId(),u);
+//            }
+//
+//            Collator com = Collator.getInstance(Locale.CHINA);
+//            // 按字母排序
+//            Collections.sort(userNameList, com);
+//            // 输出26个字母
+//            List<WxUserVO> wxUserVOList = new ArrayList<>();
+//            for (int i = 1; i < 27; i++) {
+//                String word = String.valueOf((char)(96 + i)).toUpperCase();
+//                // 循环找出 首字母一样的数据
+//                List<UserInfo> userInfoList = new ArrayList<>();
+//                for (String str : userNameList) {
+////                    String pybf = getFullSpell(str);
+//                    String pybf = getFullPinyin(str);
+//                    String zm = pybf.substring(0, 1);
+//                    if (word.equals(zm)) {
+//                        userInfoList.add(userInfoMap.get(str));
+//                    }
+//                }
+//                WxUserVO wxUserVO = new WxUserVO();
+//                wxUserVO.setLetter(word);
+//                wxUserVO.setData(userInfoList);
+//                wxUserVOList.add(wxUserVO);
+//            }
+//            return wxUserVOList;
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//        }
+//        return null;
+//    }
+
+
+    /***
+     * @Description: 姓氏多音字解决方案
+     * @Param:
+     * @return:
+     * @Author: YPZ
+     * @Date: 2023/3/23 11:26
+     */
+    /**
+     * 多音字拼音
+     */
+    public static Map<String, String> FIRST_NAME_PINYIN = new HashMap<>();
+    static {
+        FIRST_NAME_PINYIN.put("繁", "PO");
+        FIRST_NAME_PINYIN.put("区", "OU");
+        FIRST_NAME_PINYIN.put("仇", "QIU");
+        FIRST_NAME_PINYIN.put("种", "CHONG");
+        FIRST_NAME_PINYIN.put("单", "SHAN");
+        FIRST_NAME_PINYIN.put("解", "XIE");
+        FIRST_NAME_PINYIN.put("查", "ZHA");
+        FIRST_NAME_PINYIN.put("曾", "ZENG");
+        FIRST_NAME_PINYIN.put("秘", "BI");
+        FIRST_NAME_PINYIN.put("乐", "YUE");
+        FIRST_NAME_PINYIN.put("重", "CHONG");
+        FIRST_NAME_PINYIN.put("朴", "PIAO");
+        FIRST_NAME_PINYIN.put("缪", " MIAO");
+        FIRST_NAME_PINYIN.put("冼", " XIAN");
+        FIRST_NAME_PINYIN.put("翟", "ZHAI");
+        FIRST_NAME_PINYIN.put("折", "SHE");
+        FIRST_NAME_PINYIN.put("黑", "HE");
+        FIRST_NAME_PINYIN.put("盖", "GE");
+        FIRST_NAME_PINYIN.put("沈", "SHEN");
+        FIRST_NAME_PINYIN.put("尉迟", "YUCHI");
+        FIRST_NAME_PINYIN.put("万俟", "MOQI");
+    }
+
+    /**
+     * @Description: 过滤掉中文特殊字符
+     * @throws PatternSyntaxException
+     */
+    public static String filterSpecialChars(String target) throws PatternSyntaxException
+    {
+        String regEx = "[\\uac00-\\ud7af\\u3130–\\u318F]*[\\u0800-\\u4e00`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?_]";
+        Pattern pat = Pattern.compile(regEx);
+        Matcher mat = pat.matcher(target);
+        return mat.replaceAll("").trim();
+    }
+    /**
+     * 获得完整拼音
+     * 解决姓多音字问题
+     *
+     * @param keywords
+     * @return
+     */
+    public static String getFullPinyin(String keywords) {
+        if(keywords == null || "".equals(keywords)) {
+            return keywords;
+        }
+        for (Map.Entry<String, String> map : FIRST_NAME_PINYIN.entrySet()) {
+            if (keywords.startsWith(map.getKey())) {
+                String suffix = keywords.substring(map.getKey().length());
+                return map.getValue() + getFullSpell(suffix);
+            }
+        }
+        return getFullSpell(keywords);
+    }
+
+    public static String getInitials(String chinese) {
+        StringBuilder initials = new StringBuilder();
+        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
+        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
+        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+
+        for (char ch : chinese.toCharArray()) {
+            if (Character.toString(ch).matches("[\u4E00-\u9FA5]")) { // 判断是否为汉字
+                try {
+                    String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format);
+                    if (pinyinArray != null && pinyinArray.length > 0) {
+                        initials.append(pinyinArray[0].charAt(0));
+                    }
+                } catch (BadHanyuPinyinOutputFormatCombination e) {
+
+                }
+            } else {
+                initials.append(ch); // 非汉字直接附加
+            }
+        }
+
+        return initials.toString();
+    }
+}

+ 100 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RandomCodeUtil.java

@@ -0,0 +1,100 @@
+package com.storlead.framework.common.util;
+
+import org.apache.commons.lang3.RandomUtils;
+import org.springframework.util.Assert;
+
+import java.util.Random;
+
+/**
+ * 随机数工具生成类
+ *
+ * @author blank
+ * @since 2018/5/17 上午10:36
+ */
+public class RandomCodeUtil {
+
+
+    /**
+     * 生成4位数随机验证码
+     */
+    public static int getCheckCode() {
+        Random random = new Random(System.currentTimeMillis());
+        int num = random.nextInt(9999) % 10000;
+        return num < 1000 ? 1000 + num : num;
+    }
+
+    /**
+     * 返回 1000-9999之间的4位随机数
+     *
+     * @return 1000-9999之间的4位随机数
+     * @author blank
+     * @since 2018/5/17 上午10:42
+     */
+    public static int RandomCheckCode() {
+        int num = RandomUtils.nextInt(0, 9999);
+        return num < 1000 ? 1000 + num : num;
+    }
+
+    /**
+     * 返回指定[1~9]位数的随机数
+     *
+     * @param numLength 位数
+     * @return 指定位数的随机数
+     * @author blank
+     * @since 2018/5/17 上午10:49
+     */
+    public static int randomCode(int numLength) {
+        Assert.isTrue(numLength >= 1 && numLength <= 9, " Due the Integer Number limit the numLength can neither smaller than 1 nor larger than 9!");
+
+        int startNum;
+        int endNum;
+        switch (numLength) {
+
+            case 1:
+                startNum = 0;
+                endNum = 9;
+                break;
+            case 2:
+                startNum = 10;
+                endNum = 99;
+                break;
+            case 3:
+                startNum = 100;
+                endNum = 999;
+                break;
+            case 5:
+                startNum = 10000;
+                endNum = 99999;
+                break;
+            case 6:
+                startNum = 100000;
+                endNum = 999999;
+                break;
+            case 7:
+                startNum = 1000000;
+                endNum = 9999999;
+                break;
+            case 8:
+                startNum = 10000000;
+                endNum = 99999999;
+                break;
+            case 9:
+                startNum = 100000000;
+                endNum = 999999999;
+                break;
+
+            default:
+                startNum = 1000;
+                endNum = 9999;
+                break;
+        }
+
+        return RandomUtils.nextInt(startNum, endNum);
+    }
+
+
+    public static void main(String[] args) {
+        System.out.println(randomCode(19));
+
+    }
+}

+ 26 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RandomGenerateHelper.java

@@ -0,0 +1,26 @@
+package com.storlead.framework.common.util;
+
+import java.util.Random;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-09 10:33
+ */
+public class RandomGenerateHelper {
+
+    public static String generateRandomString(int length) {
+        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        Random random = new Random();
+        StringBuilder sb = new StringBuilder(length);
+
+        for (int i = 0; i < length; i++) {
+            int index = random.nextInt(characters.length());
+            sb.append(characters.charAt(index));
+        }
+
+        return sb.toString();
+    }
+
+}

+ 202 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RsaUtils.java

@@ -0,0 +1,202 @@
+package com.storlead.framework.common.util;
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * @from fhadmin.cn
+ * @description Rsa 工具类,公钥私钥生成,加解密
+ * @date 2020-05-18
+ **/
+public class RsaUtils {
+
+
+//    public static final String PUBLIC_KEY = "RSAPublicKey";
+//
+//    public static final String PRIVATE_KEY = "RSAPrivateKey";
+
+    private static final String SRC = "123456";
+    /**
+     * 公钥
+     */
+    public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCOlrKBglhJkU/bfgEEv7zuo4y5RiScg3XeDTBrHHePoixDOvyvBsiAc8b33bjin9j77EzRusJT2jjJV1hnHIbu5HkMd3T5JK00gMviCiSbfCkS4/tf3CnFMWLn31YcBgdCOFoiKh/JpGEvTx192LWSKNupDkR6bgpcRvWr5Yw2swIDAQAB";
+    /**
+     * 私钥  私钥不需要在特殊处理在java中直接调用即可
+     */
+    public static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAI6WsoGCWEmRT9t+AQS/vO6jjLlGJJyDdd4NMGscd4+iLEM6/K8GyIBzxvfduOKf2PvsTNG6wlPaOMlXWGcchu7keQx3dPkkrTSAy+IKJJt8KRLj+1/cKcUxYuffVhwGB0I4WiIqH8mkYS9PHX3YtZIo26kORHpuClxG9avljDazAgMBAAECgYA3mZWjoILytOHx0bFt+6IdX9LYz/wot643VudBbQlGDdO6p36udTOruvmj32ZfoDsJCPxvb6ak8dvgztle46XNW4tXRtjKdS9skyA4yDRHXWDZjeKpBeNc3vVl1xpf6zWoVgSsD75JMeNLPWb8+nayInDaU8kM7YZmK/MdC2reCQJBAOi1qDH7eiRgLPjR201GscRRLsFupNrc7s+eX+huKhVJi7DiSQ2ukKry6twvZI9b1O3rVVDQBaFvSJyXTl4ojMUCQQCc3ARKcVGU4h0nVr1+qCmsipNvjY2Hl6QuMcFPbfNOlIuRIANNFZUDrxHsVMkUvcdv0mWVxXi12j2qdLb19F0XAkEAw5tuthTcppbxNnWwEVTLOGnFE3Mdv5rWYk6N76IqXZpkgVq0bXu+vvNR16M+tAJNCXA3VqaFFR2lu3qztRIAwQJAer5D8Ui5MQq7C3R5tem7KpQJiOo4jJjh1XADt7bvBFeC2x401xYDVC2jlc5Gxx82N89oxIkQyySqyn6oSNBeIwJBAIyLnhPkRSSS8zQl6OuRzzdIulZFzmGqb+v9ocwoJzQz4dv795G7/61rT2pdp/1bcsXfu/vWQaiHQcuaBULKSmU=";
+
+    public static void main(String[] args) throws Exception {
+        System.out.println("\n");
+        RsaKeyPair keyPair = generateKeyPair();
+        System.out.println("公钥:" + keyPair.getPublicKey());
+        System.out.println("私钥:" + keyPair.getPrivateKey());
+        System.out.println("\n");
+        test1(keyPair);
+        System.out.println("\n");
+        test2(keyPair);
+        System.out.println("\n");
+    }
+
+    /**
+     * 公钥加密私钥解密
+     */
+    private static void test1(RsaKeyPair keyPair) throws Exception {
+        System.out.println("***************** 公钥加密私钥解密开始 *****************");
+//        String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
+//        String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
+        String text1 = encryptByPublicKey(RsaUtils.PUBLIC_KEY, RsaUtils.SRC);
+        String text2 = decryptByPrivateKey(RsaUtils.PRIVATE_KEY, text1);
+        System.out.println("加密前:" + RsaUtils.SRC);
+        System.out.println("加密后:" + text1);
+        System.out.println("解密后:" + text2);
+        if (RsaUtils.SRC.equals(text2)) {
+            System.out.println("解密字符串和原始字符串一致,解密成功");
+        } else {
+            System.out.println("解密字符串和原始字符串不一致,解密失败");
+        }
+        System.out.println("***************** 公钥加密私钥解密结束 *****************");
+    }
+
+    /**
+     * 私钥加密公钥解密
+     * @throws Exception /
+     */
+    private static void test2(RsaKeyPair keyPair) throws Exception {
+        System.out.println("***************** 私钥加密公钥解密开始 *****************");
+        String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);
+        String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
+        System.out.println("加密前:" + RsaUtils.SRC);
+        System.out.println("加密后:" + text1);
+        System.out.println("解密后:" + text2);
+        if (RsaUtils.SRC.equals(text2)) {
+            System.out.println("解密字符串和原始字符串一致,解密成功");
+        } else {
+            System.out.println("解密字符串和原始字符串不一致,解密失败");
+        }
+        System.out.println("***************** 私钥加密公钥解密结束 *****************");
+    }
+
+    /**
+     * 公钥解密
+     * @from fhadmin.cn
+     * @param publicKeyText 公钥
+     * @param text 待解密的信息
+     * @return /
+     * @throws Exception /
+     */
+    public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
+        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, publicKey);
+        byte[] result = cipher.doFinal(Base64.decodeBase64(text));
+        return new String(result);
+    }
+
+    /**
+     * 私钥加密
+     * @from fhadmin.cn
+     * @param privateKeyText 私钥
+     * @param text 待加密的信息
+     * @return /
+     * @throws Exception /
+     */
+    public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
+        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
+        byte[] result = cipher.doFinal(text.getBytes());
+        return Base64.encodeBase64String(result);
+    }
+
+    /**
+     * 私钥解密
+     * @from fhadmin.cn
+     * @param privateKeyText 私钥
+     * @param text 待解密的文本
+     * @return /
+     * @throws Exception /
+     */
+    public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
+        PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, privateKey);
+        byte[] result = cipher.doFinal(Base64.decodeBase64(text));
+        return new String(result);
+    }
+
+    /**
+     * 公钥加密
+     * @from fhadmin.cn
+     * @param publicKeyText 公钥
+     * @param text 待加密的文本
+     * @return /
+     */
+    public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
+        X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+        byte[] result = cipher.doFinal(text.getBytes());
+        return Base64.encodeBase64String(result);
+    }
+
+    /**
+     * 构建RSA密钥对
+     * @from fhadmin.cn
+     * @return /
+     * @throws NoSuchAlgorithmException /
+     */
+    public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(1024);
+        KeyPair keyPair = keyPairGenerator.generateKeyPair();
+        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+        String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
+        String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
+        RsaKeyPair pair = new RsaKeyPair(publicKeyString, privateKeyString);
+        return pair;
+    }
+
+
+    /**
+     * RSA密钥对对象
+     */
+    public static class RsaKeyPair {
+
+        private final String publicKey;
+        private final String privateKey;
+
+        public RsaKeyPair(String publicKey, String privateKey) {
+            this.publicKey = publicKey;
+            this.privateKey = privateKey;
+        }
+
+        public String getPublicKey() {
+            return publicKey;
+        }
+
+        public String getPrivateKey() {
+            return privateKey;
+        }
+
+    }
+}

+ 106 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SnowFlake.java

@@ -0,0 +1,106 @@
+package com.storlead.framework.common.util;
+
+/**
+ * Twitter的分布式自增ID雪花算法snowflake
+ * @author MENG
+ * @create 2018-08-23 10:21
+ **/
+public class SnowFlake {
+
+    /**
+     * 起始的时间戳
+     */
+    private final static long START_STMP = 1480166465631L;
+
+    /**
+     * 每一部分占用的位数
+     */
+    private final static long SEQUENCE_BIT = 12; //序列号占用的位数
+    private final static long MACHINE_BIT = 5;   //机器标识占用的位数
+    private final static long DATACENTER_BIT = 5;//数据中心占用的位数
+
+    /**
+     * 每一部分的最大值
+     */
+    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
+    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
+    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
+
+    /**
+     * 每一部分向左的位移
+     */
+    private final static long MACHINE_LEFT = SEQUENCE_BIT;
+    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
+    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
+
+    private long datacenterId;  //数据中心
+    private long machineId;     //机器标识
+    private long sequence = 0L; //序列号
+    private long lastStmp = -1L;//上一次时间戳
+
+    public SnowFlake(long datacenterId, long machineId) {
+        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
+            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
+        }
+        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
+            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
+        }
+        this.datacenterId = datacenterId;
+        this.machineId = machineId;
+    }
+
+    /**
+     * 产生下一个ID
+     *
+     * @return
+     */
+    public synchronized long nextId() {
+        long currStmp = getNewstmp();
+        if (currStmp < lastStmp) {
+            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
+        }
+
+        if (currStmp == lastStmp) {
+            //相同毫秒内,序列号自增
+            sequence = (sequence + 1) & MAX_SEQUENCE;
+            //同一毫秒的序列数已经达到最大
+            if (sequence == 0L) {
+                currStmp = getNextMill();
+            }
+        } else {
+            //不同毫秒内,序列号置为0
+            sequence = 0L;
+        }
+
+        lastStmp = currStmp;
+
+        return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
+                | datacenterId << DATACENTER_LEFT       //数据中心部分
+                | machineId << MACHINE_LEFT             //机器标识部分
+                | sequence;                             //序列号部分
+    }
+
+    private long getNextMill() {
+        long mill = getNewstmp();
+        while (mill <= lastStmp) {
+            mill = getNewstmp();
+        }
+        return mill;
+    }
+
+    private long getNewstmp() {
+        return System.currentTimeMillis();
+    }
+
+    public static void main(String[] args) {
+        SnowFlake snowFlake = new SnowFlake(1, 1);
+        long start = System.currentTimeMillis();
+        for (int i = 0; i < 1000; i++) {
+            System.out.println(snowFlake.nextId());
+        }
+
+        System.out.println(System.currentTimeMillis() - start);
+
+
+    }
+}

+ 48 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SpringCompareUtil.java

@@ -0,0 +1,48 @@
+package com.storlead.framework.common.util;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-18 18:02
+ */
+public class SpringCompareUtil {
+
+
+    /**
+     * 包含匹配
+     * @param var1
+     * @param var2
+     * @return
+     */
+    public static boolean includeEqual(Integer var1,Integer... var2) {
+        if (var1 == null || var2 == null) {
+            return false;
+        }
+        for (Integer i : var2) {
+            if (var1.equals(i)) {
+                return true;
+            }
+        }
+        return false;
+    };
+
+
+    /**
+     * @description: 完全匹配
+     * @param var1
+     * @param var2
+     * @return
+     */
+    public static boolean fullEqual(Integer var1,Integer... var2) {
+        if (var1 == null || var2 == null) {
+            return false;
+        }
+        for (Integer i : var2) {
+            if (!var1.equals(i)) {
+                return false;
+            }
+        }
+        return true;
+    };
+}

+ 95 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SpringContextUtils.java

@@ -0,0 +1,95 @@
+package com.storlead.framework.common.util;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Component
+public class SpringContextUtils implements ApplicationContextAware {
+
+	/**
+	 * 上下文对象实例
+	 */
+	private static ApplicationContext applicationContext;
+
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		SpringContextUtils.applicationContext = applicationContext;
+	}
+
+	/**
+	 * 获取applicationContext
+	 *
+	 * @return
+	 */
+	public static ApplicationContext getApplicationContext() {
+		return applicationContext;
+	}
+
+	/**
+	  * 获取HttpServletRequest
+	 */
+	public static HttpServletRequest getHttpServletRequest() {
+		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+	}
+
+	public static String getDomain(){
+		HttpServletRequest request = getHttpServletRequest();
+		StringBuffer url = request.getRequestURL();
+		return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
+	}
+
+	public static String getOrigin(){
+		HttpServletRequest request = getHttpServletRequest();
+		return request.getHeader("Origin");
+	}
+
+	/**
+	 * 通过name获取 Bean.
+	 *
+	 * @param name
+	 * @return
+	 */
+	public static Object getBean(String name) {
+		return getApplicationContext().getBean(name);
+	}
+
+	/**
+	 * 通过class获取Bean.
+	 *
+	 * @param clazz
+	 * @param       <T>
+	 * @return
+	 */
+	public static <T> T getBean(Class<T> clazz) {
+		return getApplicationContext().getBean(clazz);
+	}
+
+	/**
+	 * 通过name,以及Clazz返回指定的Bean
+	 *
+	 * @param name
+	 * @param clazz
+	 * @param       <T>
+	 * @return
+	 */
+	public static <T> T getBean(String name, Class<T> clazz) {
+		return getApplicationContext().getBean(name, clazz);
+	}
+
+	/**
+	 * 获取当前环境
+	 *
+	 * @return 对应 profile name [dev,test,prod]
+	 * @author blank
+	 * @since 2018-10-10 上午11:24
+	 */
+	public static String getActiveProfile() {
+		return applicationContext.getEnvironment().getActiveProfiles()[0];
+	}
+}

+ 103 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SqlInjectionUtil.java

@@ -0,0 +1,103 @@
+package com.storlead.framework.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * sql注入处理工具类
+ *
+ * @author zhoujf
+ */
+@Slf4j
+public class SqlInjectionUtil {
+	final static String xssStr = "'|and |exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|,";
+
+	/**
+	 * sql注入过滤处理,遇到注入关键字抛异常
+	 *
+	 * @param value
+	 * @return
+	 */
+	public static void filterContent(String value) {
+		if (value == null || "".equals(value)) {
+			return;
+		}
+		value = value.toLowerCase();// 统一转为小写
+		String[] xssArr = xssStr.split("\\|");
+		for (int i = 0; i < xssArr.length; i++) {
+			if (value.indexOf(xssArr[i]) > -1) {
+				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
+				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			}
+		}
+		return;
+	}
+
+	/**
+	 * sql注入过滤处理,遇到注入关键字抛异常
+	 *
+	 * @param values
+	 * @return
+	 */
+	public static void filterContent(String[] values) {
+		String[] xssArr = xssStr.split("\\|");
+		for (String value : values) {
+			if (value == null || "".equals(value)) {
+				return;
+			}
+			value = value.toLowerCase();// 统一转为小写
+			for (int i = 0; i < xssArr.length; i++) {
+				if (value.indexOf(xssArr[i]) > -1) {
+					log.error("请注意,值可能存在SQL注入风险!---> {}", value);
+					throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+				}
+			}
+		}
+		return;
+	}
+
+	/**
+	 * @特殊方法(不通用) 仅用于字典条件SQL参数,注入过滤
+	 * @param value
+	 * @return
+	 */
+	@Deprecated
+	public static void specialFilterContent(String value) {
+		String specialXssStr = "exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|";
+		String[] xssArr = specialXssStr.split("\\|");
+		if (value == null || "".equals(value)) {
+			return;
+		}
+		value = value.toLowerCase();// 统一转为小写
+		for (int i = 0; i < xssArr.length; i++) {
+			if (value.indexOf(xssArr[i]) > -1) {
+				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
+				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			}
+		}
+		return;
+	}
+
+
+	/**
+	 * @特殊方法(不通用) 仅用于Online报表SQL解析,注入过滤
+	 * @param value
+	 * @return
+	 */
+	@Deprecated
+	public static void specialFilterContentForOnlineReport(String value) {
+		String specialXssStr = "exec |insert |delete |update |drop |chr |mid |master |truncate |char |declare |";
+		String[] xssArr = specialXssStr.split("\\|");
+		if (value == null || "".equals(value)) {
+			return;
+		}
+		value = value.toLowerCase();// 统一转为小写
+		for (int i = 0; i < xssArr.length; i++) {
+			if (value.indexOf(xssArr[i]) > -1) {
+				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
+				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			}
+		}
+		return;
+	}
+
+}

+ 201 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/StringUtil.java

@@ -0,0 +1,201 @@
+package com.storlead.framework.common.util;
+
+import cn.hutool.core.util.StrUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * @program: storelad
+ * @description: String工具类
+ * @author: chenkq
+ * @create: 2022-09-29 11:24
+ **/
+public class StringUtil extends StringUtils {
+
+    public static String trimSpace(String str, String space) {
+        if (str.startsWith(space)) {
+            str = str.substring(space.length());
+        }
+        if (str.endsWith(space)) {
+            str = str.substring(0, str.length() - space.length());
+        }
+        return str;
+    }
+
+    public static String removePrefixAndSuffix(String str, String prefix, String suffix) {
+        if (str.startsWith(prefix)) {
+            str = str.substring(prefix.length());
+        }
+        if (str.endsWith(suffix)) {
+            str = str.substring(0, str.length() - suffix.length());
+        }
+        return str;
+    }
+
+    /**
+     * 判断两个字符串是否相等,除去html标签 两端空格和换行
+     * @param s1 s1
+     * @param s2 s2
+     * @return: @return boolean
+     * @author: xiajiawei
+     * @date: 2022-09-29
+     */
+    public static  boolean ignoreHtmlSpaceEnterEquals(String s1,String s2){
+        if(s1==null&&s2==null){
+            return true;
+        }
+        if(s1 == null||s2==null){
+            return false;
+        }
+        s1 = HtmlUtils.userContent(s1)
+                .replace("/r", "")
+                .replace("/n", "")
+                .trim();
+        s2 = HtmlUtils.userContent(s2)
+                .replace("/r", "")
+                .replace("/n", "")
+                .trim();
+        return Objects.equals(s1,s2);
+    }
+
+    public static List<Long> stringToLongList(String commaSeparatedString) {
+        List<Long> longList = new ArrayList<>();
+        if (commaSeparatedString != null && !commaSeparatedString.isEmpty()) {
+            String[] strings = commaSeparatedString.split(",");
+            for (String s : strings) {
+                // 去除字符串两侧的空白字符,并尝试转换为Long
+                Long number = Long.parseLong(s.trim());
+                longList.add(number);
+            }
+        }
+        return longList;
+    }
+
+    //List<Long>转逗号分割的字符串
+    public static String longListToString(List<Long> longList) {
+        if (CollectionUtils.isEmpty(longList)){
+            return null;
+        }
+        String listString = String.join(",", longList.stream().map(Object::toString).toArray(String[]::new));
+
+        return listString;
+    }
+    //List<String>转逗号分割的字符串
+    public static String stringListToString(List<String> stringList) {
+        if (CollectionUtils.isEmpty(stringList)){
+            return null;
+        }
+        String listString = String.join(",", stringList);
+
+        return listString;
+    }
+    //逗号分割的字符串转List<Long>
+    public static List<Long> stringToStringList(String commaSeparatedString) {
+        if (ObjectUtils.isEmpty(commaSeparatedString)){
+            return new ArrayList<>();
+        }
+        List<Long> longList = Arrays.stream(commaSeparatedString.split(","))
+                .map(Long::parseLong)
+                .collect(Collectors.toList());
+        return longList;
+    }
+    //逗号分割的字符串转List<String>
+    public static List<String> stringToList(String commaSeparatedString) {
+        if (ObjectUtils.isEmpty(commaSeparatedString)){
+            return new ArrayList<>();
+        }
+        List<String> stringList = Arrays.stream(commaSeparatedString.split(","))
+                .collect(Collectors.toList());
+        return stringList;
+    }
+
+    public static boolean containsAllIds(List<Long> subScopeIdList, String subScopeId) {
+        // Split the comma-separated string into a list of strings
+        if (ObjectUtils.isEmpty(subScopeId)){
+            return false;
+        }
+        List<String> subScopeIdStrList = Arrays.asList(subScopeId.split(","));
+
+        // Convert the list of strings to a list of longs
+        List<Long> subScopeIdLongList = subScopeIdStrList.stream()
+                .map(Long::parseLong)
+                .collect(Collectors.toList());
+
+        // Check if subScopeIdList contains all elements of subScopeIdLongList
+        return subScopeIdList.containsAll(subScopeIdLongList);
+    }
+
+
+
+    public static String extractUsernames(String recipients) {
+        try {
+            if (StrUtil.isBlank(recipients)) {
+                return  "";
+            }
+            String regex = "(\\S+)@";
+
+            // 创建一个匹配模式
+            Pattern pattern = Pattern.compile(regex);
+            Matcher matcher = pattern.matcher(recipients);
+            List<String> usernames = new ArrayList<>();
+            // 查找所有匹配的部分
+            while (matcher.find()) {
+                usernames.add(matcher.group(1)); // 获取 @ 前的部分
+            }
+            // 将结果列表用逗号连接
+            return String.join(",", usernames);
+        }catch (Exception e) {
+            return "";
+        }
+    }
+
+
+    public static String updateStringList(String ids, List<Long> idListAll, List<Long> idList) {
+        // Step 1: Parse string A into a List<Long>
+        if (ObjectUtils.isEmpty(ids)){
+            return idList.stream()
+                    .map(Object::toString)
+                    .collect((Collectors.joining(",")));
+        }
+        List<Long> parsedA = Arrays.stream(ids.split(","))
+                .map(Long::parseLong)
+                .collect(Collectors.toList());
+
+        // Step 2: Remove elements from B from parsedA
+        Set<Long> setB = new HashSet<>(idListAll);
+        List<Long> updatedA = parsedA.stream()
+                .filter(element -> !setB.contains(element))
+                .collect(Collectors.toList());
+
+        // Step 3: Add elements from C to updatedA
+        updatedA.addAll(idList);
+
+        // Step 4: Convert updatedA back to a comma-separated string
+        String result = updatedA.stream()
+                .map(Object::toString)
+                .collect((Collectors.joining(",")));
+        return result;
+    }
+
+    public static void main(String[] args) {
+//        System.out.println(stringListToString(null));
+        String A = "1,3,5,6,7";
+        List<Long> B = Arrays.asList(1L, 2L, 3L, 4L);
+        List<Long> C = Arrays.asList(2L, 3L, 4L);
+        System.out.println(updateStringList(A, B, C));
+    }
+
+    public static String concatRegexp(List<Long> ids){
+        if(ids.size()==0){
+            return "-";
+        }else {
+            return ids.stream().map(String::valueOf).collect(Collectors.joining("|"));
+        }
+    }
+}

+ 29 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SystemUtils.java

@@ -0,0 +1,29 @@
+package com.storlead.framework.common.util;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @program: storlead-storlead-mail-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-06-17 15:47
+ */
+public class SystemUtils {
+    /**
+     * 在filter里输出json格式
+     * @param json  json的字符串
+     * */
+    public static void printJson(HttpServletResponse response, String json){
+        try {
+            response.setContentType("application/json");
+            response.setCharacterEncoding("UTF-8");
+            response.getWriter().print(json);
+            response.getWriter().flush();
+            response.getWriter().close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+    }
+}

+ 47 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/UrlChainBlackAndWhiteUtil.java

@@ -0,0 +1,47 @@
+package com.storlead.framework.common.util;
+
+import com.storlead.framework.common.properties.UrlChainDefinitionPorperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-07-04 16:24
+ */
+@Component
+public class UrlChainBlackAndWhiteUtil {
+    @Autowired
+    private UrlChainDefinitionPorperties properties;
+
+    public String getContextPath() {
+        return properties.getContextPath();
+    }
+
+    /**
+     * 白名单URL
+     * @param uri
+     * @return
+     */
+    public boolean IsWhiteUri(String uri)
+    {
+        String [] AUTH_WHITELIST = properties.getAuthWhitelist();
+        uri = uri.replace(properties.getContextPath()+"/","/");
+        for (int i = 0; i < AUTH_WHITELIST.length; i++)
+        {
+            if (AUTH_WHITELIST[i].length() > 0)
+            {
+                String whiteuri = AUTH_WHITELIST[i];
+                whiteuri = whiteuri.trim();
+                AntPathMatcher antPathMatcher = new AntPathMatcher();
+                boolean match = antPathMatcher.match(whiteuri,uri);
+                if (match) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}

+ 372 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/WordUtils.java

@@ -0,0 +1,372 @@
+package com.storlead.framework.common.util;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Base64;
+import java.util.Map;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-18 17:50
+ */
+public class WordUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(WordUtils.class);
+    private static WordUtils service = null;
+
+    public WordUtils() {
+        super();
+    }
+
+    public static WordUtils getInstance() {
+        if(service == null) {
+            service = new WordUtils();
+        }
+        return service;
+    }
+
+    /**
+     * 导出至word
+     * @param ftl ftl模板地址
+     * @param fileName 文件名
+     * @param dataMap 导出数据
+     * @param response response
+     */
+    public void exportDocFile(String ftl, String fileName, Map<String, Object> dataMap, HttpServletResponse response){
+        Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
+        configuration.setDefaultEncoding("UTF-8");
+
+        OutputStreamWriter oWriter = null;
+        Writer out = null;
+        try {
+            ClassPathResource resource = new ClassPathResource(ftl);
+            File file = File.createTempFile("StatementTemp",".ftl");
+            FileUtils.copyURLToFile(resource.getURL(), file);
+            String templateFile = file.getAbsolutePath();
+            templateFile = pathReplace(templateFile);
+            if(null == templateFile){
+                log.error("导出失败,模板不存在");
+                return;
+            }
+            String ftlPath = templateFile.substring(0, templateFile.lastIndexOf("/"));
+            log.info("模板路径:{}", ftlPath);
+            // FTL文件所存在的位置--绝对路径
+            configuration.setDirectoryForTemplateLoading(new File(ftlPath));
+            //configuration.setClassForTemplateLoading(this.getClass(), ftlPath);
+            String ftlFile = templateFile.substring(templateFile.lastIndexOf("/")+1);
+            log.info("模板临时文件名称:{}", ftlFile);
+            // 模板文件名
+            Template template = configuration.getTemplate(ftlFile);
+
+            response.reset();
+            response.setContentType("application/msword; charset=utf-8");
+            String filename = fileName + ".doc";
+            response.setHeader("Content-Disposition", "attachment; filename="+ urlEncode(filename));
+            oWriter = new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8);
+            out = new BufferedWriter(oWriter);
+            template.process(dataMap, out);
+        } catch (Exception e) {
+            log.error("导出word失败:", e);
+        } finally {
+            try {
+                if(null != out){
+                    out.close();
+                }
+                if(null != oWriter){
+                    oWriter.close();
+                }
+            } catch (IOException e) {
+                log.error("Writer or OutputStreamWriter close failed:{}", e.getMessage());
+            }
+        }
+    }
+
+
+
+    /**
+     * 导出至word
+     * @param ftl ftl模板地址
+     * @param fileName 文件名
+     * @param dataMap 导出数据
+     * @param response response
+     */
+    public ResponseEntity<FileSystemResource> repExportDocFile(String ftl, String fileName, Map<String, Object> dataMap) {
+        Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
+        configuration.setDefaultEncoding("UTF-8");
+
+        try {
+            // 1. 获取模板文件
+            ClassPathResource resource = new ClassPathResource(ftl);
+            File tempTemplate = File.createTempFile("StatementTemp", ".ftl");
+            FileUtils.copyURLToFile(resource.getURL(), tempTemplate);
+
+            String templatePath = pathReplace(tempTemplate.getAbsolutePath());
+            if (templatePath == null) {
+                log.error("导出失败,模板不存在");
+                return ResponseEntity.notFound().build();
+            }
+
+            String ftlDir = templatePath.substring(0, templatePath.lastIndexOf("/"));
+            configuration.setDirectoryForTemplateLoading(new File(ftlDir));
+            String ftlFileName = templatePath.substring(templatePath.lastIndexOf("/") + 1);
+            Template template = configuration.getTemplate(ftlFileName);
+
+            // 2. 生成 Word 临时文件
+            File tempWordFile = File.createTempFile("Export-", ".doc");
+            try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempWordFile), StandardCharsets.UTF_8))) {
+                template.process(dataMap, out);
+            }
+
+            // 3. 返回 Word 文件
+            FileSystemResource resourceBody = new FileSystemResource(tempWordFile);
+            String downloadFileName = fileName + ".doc";
+
+//            response.setContentType("application/msword; charset=utf-8");
+//            String filename = fileName + ".doc";
+//            response.setHeader("Content-Disposition", "attachment; filename="+ urlEncode(filename));
+
+            return ResponseEntity.ok()
+                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + urlEncode(downloadFileName))
+//                    .header("application/octet-stream", "attachment; filename=" + urlEncode(downloadFileName))
+                    .contentType(MediaType.parseMediaType("application/msword"))
+                    .contentLength(resourceBody.contentLength())
+                    .body(resourceBody);
+
+        } catch (Exception e) {
+            log.error("导出word失败:", e);
+            return ResponseEntity.status(500).build();
+        }
+    }
+
+    public String exportDocToUrl(String ftl, String fileName, Map<String, Object> dataMap) {
+        try {
+            // 生成 Word 文件
+            Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
+            configuration.setDefaultEncoding("UTF-8");
+
+            ClassPathResource resource = new ClassPathResource(ftl);
+            File tempTemplate = File.createTempFile("Template-", ".ftl");
+            FileUtils.copyURLToFile(resource.getURL(), tempTemplate);
+
+            String templatePath = pathReplace(tempTemplate.getAbsolutePath());
+            String ftlDir = templatePath.substring(0, templatePath.lastIndexOf("/"));
+            configuration.setDirectoryForTemplateLoading(new File(ftlDir));
+            String ftlFileName = templatePath.substring(templatePath.lastIndexOf("/") + 1);
+
+            Template template = configuration.getTemplate(ftlFileName);
+
+            // 1. 导出目录(容器内路径)
+            File exportDir = new File("/app/tempfiles");
+            if (!exportDir.exists()) {
+                exportDir.mkdirs();
+            }
+
+            File targetFile = new File(exportDir, fileName + ".doc");
+
+            try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), StandardCharsets.UTF_8))) {
+                template.process(dataMap, out);
+            }
+
+            // 2. 拼接 URL(这里假设映射后访问路径是 /export/)
+            return "/static/file/" + fileName + ".doc";
+
+        } catch (Exception e) {
+            log.error("生成word失败:", e);
+            return null;
+        }
+    }
+
+
+    public void exportDocFileTest(String ftl, String fileName, Map<String, Object> dataMap, HttpServletResponse response){
+        Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
+        configuration.setDefaultEncoding("UTF-8");
+
+        try {
+            // 直接从类路径加载FTL资源
+            ClassPathResource resource = new ClassPathResource(ftl);
+            URL templateUrl = resource.getURL();
+
+            // 注意:这里需要确保Freemarker可以直接从URL加载模板,或者您需要将其内容复制到字符串或ByteArrayInputStream
+            // 但对于大多数情况,如果模板在类路径中,您可以直接使用setClassLoaderForTemplateLoading
+            configuration.setClassForTemplateLoading(this.getClass().getClassLoader().getClass(), "");
+
+            // 获取模板
+            Template template = configuration.getTemplate(ftl);
+
+            // 设置响应头
+            response.reset();
+            response.setContentType("application/octet-stream; charset=utf-8");
+            String encodedFileName = java.net.URLEncoder.encode(fileName + ".doc", StandardCharsets.UTF_8.toString());
+            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
+
+            // 使用BufferedWriter写入响应
+            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8))) {
+                template.process(dataMap, writer);
+                writer.flush(); // 确保所有数据都被写入响应
+            }
+
+        } catch (IOException | TemplateException e) {
+            // 处理异常,例如记录日志或返回错误响应
+            // 注意:在实际应用中,您可能想要将异常信息返回给客户端或进行其他错误处理
+            System.err.println("导出word失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 创建doc文件
+     * @param ftl src/main/resources/ftl/xxx.ftl
+     * @param dataMap 导出数据
+     * @param exportPath  eg: /tmp/test/test123.doc
+     * @param loadType  设置路径加载方式。1-绝对路径,2-项目相对路径
+     * @return {@link File}
+     * @ver v1.0.0
+     */
+    public File createDocFile(String ftl, Map<String, Object> dataMap, String exportPath, int loadType) {
+        File file = new File(ftl);
+        String templateFile = file.getAbsolutePath();
+        Template t = null;
+        Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
+        configuration.setDefaultEncoding("UTF-8");
+        try {
+            templateFile = pathReplace(templateFile);
+            if(null == templateFile){
+                log.error("模板不存在!");
+                return null;
+            }
+            String ftlPath = templateFile.substring(0, templateFile.lastIndexOf("/"));
+            if(loadType == 1) {
+                // FTL文件所存在的位置
+                configuration.setDirectoryForTemplateLoading(new File(ftlPath));
+            }else {
+                //以类加载的方式查找模版文件路径
+                configuration.setClassForTemplateLoading(this.getClass(), ftlPath);
+            }
+
+            String ftlFile = templateFile.substring(templateFile.lastIndexOf("/")+1);
+            // 模板文件名
+            t = configuration.getTemplate(ftlFile);
+
+            File outFile = new File(exportPath);
+            Writer out =  new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(outFile.toPath())));
+            t.process(dataMap, out);
+        } catch (Exception e) {
+            log.error("导出word文档出错:{}", e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     *  把路径的\替换成/
+     * @param path 路径
+     * @return {@link String}
+     * @ver v1.0.0
+     */
+    private static String pathReplace(String path) {
+        if(!ObjectUtils.isEmpty(path)) {
+            path = path.replace("\\",  "/");
+//                path = path.replace("\"", "/");
+        }
+        return path;
+    }
+
+    /**
+     * 图片转base64
+     * @param imgStr 图片 http 地址
+     * @return {@link String}
+     * @ver v1.0.0
+     */
+    public static String getImgStrToBase64(String imgStr) {
+        if(StringUtils.isEmpty(imgStr)){
+            return "";
+        }
+        log.info("imgStr:===>{}",imgStr);
+        InputStream inputStream = null;
+        ByteArrayOutputStream outputStream = null;
+        byte[] buffer = null;
+        try {
+            //判断网络链接图片文件/本地目录图片文件
+            if (imgStr.startsWith("http://") || imgStr.startsWith("https://")) {
+                // 创建URL
+                URL url = new URL(imgStr);
+                // 创建链接
+                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+                conn.setRequestMethod("GET");
+                conn.setConnectTimeout(5000);
+                inputStream = conn.getInputStream();
+                outputStream = new ByteArrayOutputStream();
+                // 将内容读取内存中
+                buffer = new byte[1024];
+                int len = -1;
+                while ((len = inputStream.read(buffer)) != -1) {
+                    outputStream.write(buffer, 0, len);
+                }
+                buffer = outputStream.toByteArray();
+            } else {
+                inputStream = Files.newInputStream(Paths.get(imgStr));
+                int count = 0;
+                while (count == 0) {
+                    count = inputStream.available();
+                }
+                buffer = new byte[count];
+                inputStream.read(buffer);
+            }
+            // 对字节数组Base64编码
+            return new String(Base64.getDecoder().decode(buffer));
+        }catch (Exception e) {
+            log.error("图片转换失败:{}", e.getMessage());
+            return "";
+        } finally {
+            if (inputStream != null) {
+                try {
+                    // 关闭inputStream流
+                    inputStream.close();
+                } catch (IOException e) {
+                    log.error("InputStream 关闭失败:{}", e.getMessage());
+                }
+            }
+            if (outputStream != null) {
+                try {
+                    // 关闭outputStream流
+                    outputStream.close();
+                } catch (IOException e) {
+                    log.error("ByteArrayOutputStream 关闭失败:{}", e.getMessage());
+                }
+            }
+        }
+    }
+
+
+    /**
+     * URL 编码, Encode默认为UTF-8.
+     */
+    public static String urlEncode(String part) {
+        try {
+            return URLEncoder.encode(part, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            log.error("编码失败:{}", e.getMessage());
+            return "";
+        }
+    }
+}

+ 219 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/WordUtilsTest.java

@@ -0,0 +1,219 @@
+package com.storlead.framework.common.util;
+
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.Version;
+import org.springframework.core.io.ClassPathResource;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * @author running
+ * @description
+ * @date 2020/9/22
+ */
+
+
+public class WordUtilsTest {
+
+    private static Configuration configuration = null;
+
+    static {
+        configuration = new Configuration(new Version("2.3.30"));
+        configuration.setDefaultEncoding("utf-8");
+        //以下配置只能在本地使用,linux环境下无法获取地址,注掉
+//        try {
+//            ResourceLoader resourceLoader = new DefaultResourceLoader();
+        //指定模板目录在类路径:WEB-INF/classes
+//            Resource resource = resourceLoader.getResource("/");
+//            File file = resource.getFile();
+        //设置要解析的模板所在的目录,并加载模板文件
+//            configuration.setDirectoryForTemplateLoading(file);
+        ///设置包装器,并将对象包装为数据模型
+        configuration.setObjectWrapper(new DefaultObjectWrapper(new Version("2.3.30")));
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//        }
+    }
+
+    private WordUtilsTest() {
+        throw new AssertionError();
+    }
+
+    /**
+     * 导出单个word
+     *
+     * @param map      数据
+     * @param title    文件名
+     * @param ftlFile  模板文件
+     * @param response 响应
+     */
+    public static void exportWord(Map map, String title, String ftlFile, HttpServletResponse response) {
+
+        File file = null;
+        InputStream fin = null;
+        ServletOutputStream out = null;
+        try {
+            //Template freemarkerTemplate = configuration.getTemplate(ftlFile, "UTF-8");
+            ClassPathResource classPathResource = new ClassPathResource(ftlFile);
+            InputStream inputStream = classPathResource.getInputStream();
+            InputStreamReader reader = new InputStreamReader(inputStream);
+            Template freemarkerTemplate = new Template(ftlFile, reader, configuration);
+            // 调用工具类的createDoc方法生成Word文档
+            String fileName = title + ".doc";
+            file = createDoc(map, freemarkerTemplate, fileName);
+            fin = new FileInputStream(file);
+
+            response.setCharacterEncoding("utf-8");
+            response.setContentType("application/msword");
+            // 设置浏览器以下载的方式处理该文件名
+
+            response.setHeader("Content-Disposition", "attachment;filename="
+                    .concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
+
+            out = response.getOutputStream();
+            // 缓冲区
+            byte[] buffer = new byte[1024];
+            int bytesToRead = -1;
+            // 通过循环将读入的Word文件的内容输出到浏览器中
+            while ((bytesToRead = fin.read(buffer)) != -1) {
+                out.write(buffer, 0, bytesToRead);
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (fin != null) {
+                    fin.close();
+                }
+                if (out != null) {
+                    out.close();
+                }
+                // 删除临时文件
+                if (file != null) {
+                    file.delete();
+                }
+            } catch (Exception e2) {
+                e2.printStackTrace();
+            }
+        }
+    }
+
+
+    /**
+     * 压缩包方式导出多个word
+     * 由于一次请求浏览器只能响应一次,想导出多个必须打包,亲测for循环导出只能导一个
+     * 如果想做到分别单独下载,那就得用插件啦,这里不提供插件的做法
+     * 思路:生成临时目录-在临时目录生成word-将临时目录打zip包-zip文件下载-删除临时目录和zip包,
+     * 回收系统资源
+     *
+     * @param mapList
+     * @param titleList
+     * @param ftlFile
+     */
+    public static void exportWordBatch(List<Map<String, Object>> mapList, List<String> titleList, String ftlFile,
+                                       HttpServletResponse response, HttpServletRequest request) {
+        File file = null;
+        File zipfile = null;
+        File directory = null;
+        InputStream fin = null;
+        ServletOutputStream out = null;
+        response.setCharacterEncoding("utf-8");
+        response.setContentType("application/octet-stream");
+        response.addHeader("Content-Disposition", "attachment;filename=" + System.currentTimeMillis() + ".zip");
+
+        try {
+            //以此方式,在Linux环境下也可获取到模板文件
+            ClassPathResource classPathResource = new ClassPathResource(ftlFile);
+            InputStream inputStream = classPathResource.getInputStream();
+            InputStreamReader reader = new InputStreamReader(inputStream);
+            Template freemarkerTemplate = new Template(ftlFile, reader, configuration);
+            out = response.getOutputStream();
+            //根据当前时间创建临时目录
+            String path = request.getRealPath("/resources/word/" + System.currentTimeMillis());
+            directory = new File(path);
+            directory.mkdirs();
+            for (int i = 0; i < mapList.size(); i++) {
+                Map<String, Object> map = mapList.get(i);
+                String title = titleList.get(i);
+                // 调用工具类的createDoc方法在临时目录下生成Word文档
+                file = createDoc(map, freemarkerTemplate, directory.getPath() + "/" + title + ".doc");
+            }
+            //压缩目录
+            ZipUtil.createZip(path, path + "zip.zip");
+            //根据路径获取刚生成的zip包文件
+            zipfile = new File(path + "zip.zip");
+            fin = new FileInputStream(zipfile);
+            // 缓冲区
+            byte[] buffer = new byte[1024];
+            int bytesToRead = -1;
+            // 通过循环将读入的Word文件的内容输出到浏览器中
+            while ((bytesToRead = fin.read(buffer)) != -1) {
+                out.write(buffer, 0, bytesToRead);
+            }
+            response.flushBuffer();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (fin != null) {
+                    fin.close();
+                }
+                if (out != null) {
+                    out.close();
+                }
+                if (zipfile != null) {
+                    zipfile.delete();
+                }
+                if (directory != null) {
+                    //递归删除目录及目录下文件
+                    ZipUtil.deleteFile(directory);
+                }
+            } catch (Exception e2) {
+                e2.printStackTrace();
+            }
+
+        }
+    }
+
+    //生成word文档方法
+    private static File createDoc(Map<?, ?> dataMap, Template template, String filename) {
+
+        File f = new File(filename);
+        Template t = template;
+        Writer w = null;
+        FileOutputStream fos = null;
+        try {
+            // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
+            fos = new FileOutputStream(f);
+            w = new OutputStreamWriter(fos, UTF_8);
+            //不要偷懒写成下面酱紫: 否则无法关闭fos流,打zip包时存取被拒抛异常
+            //w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
+            t.process(dataMap, w);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            throw new RuntimeException(ex);
+        } finally {
+            try {
+                fos.close();
+                w.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+        }
+        return f;
+    }
+
+}
+

+ 211 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ZipUtil.java

@@ -0,0 +1,211 @@
+package com.storlead.framework.common.util;
+
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author running
+ * @description
+ * @date 2020/9/22
+ */
+public class ZipUtil {
+
+    public static void zip(String inputFileName, String zipFileName)
+            throws Exception {
+        zip(zipFileName, new File(inputFileName));
+    }
+
+    private static void zip(String zipFileName, File inputFile)
+            throws Exception {
+        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
+                zipFileName));
+        zip(out, inputFile, "");
+        System.out.println("zip done");
+        out.close();
+    }
+
+    private static void zip(ZipOutputStream out, File f, String base)
+            throws Exception {
+        if (f.isDirectory()) {
+            File[] fl = f.listFiles();
+            out.putNextEntry(new ZipEntry(base + "/"));
+            base = base.length() == 0 ? "" : base + "/";
+            for (int i = 0; i < fl.length; i++) {
+                zip(out, fl[i], base + fl[i].getName());
+            }
+        } else {
+            out.putNextEntry(new ZipEntry(base));
+            FileInputStream in = new FileInputStream(f);
+            int b;
+            //System.out.println(base);
+            while ((b = in.read()) != -1) {
+                out.write(b);
+            }
+            in.close();
+        }
+    }
+
+    /**
+     * 创建ZIP文件
+     *
+     * @param sourcePath 文件或文件夹路径
+     * @param zipPath    生成的zip文件存在路径(包括文件名)
+     */
+    public static void createZip(String sourcePath, String zipPath) {
+        FileOutputStream fos = null;
+        ZipOutputStream zos = null;
+        try {
+            fos = new FileOutputStream(zipPath);
+            zos = new ZipOutputStream(fos);
+            writeZip(new File(sourcePath), "", zos);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (zos != null) {
+                    zos.close();
+                    //压缩成功后,删除打包前的文件
+                    deleteFile(new File(sourcePath));
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static void writeZip(File file, String parentPath,
+                                 ZipOutputStream zos) {
+        if (file.exists()) {
+            // 处理文件夹
+            if (file.isDirectory()) {
+                parentPath += file.getName() + File.separator;
+                File[] files = file.listFiles();
+                for (File f : files) {
+                    writeZip(f, parentPath, zos);
+                }
+            } else {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(file);
+                    ZipEntry ze = new ZipEntry(parentPath + file.getName());
+                    zos.putNextEntry(ze);
+                    byte[] content = new byte[1024];
+                    int len;
+                    while ((len = fis.read(content)) != -1) {
+                        zos.write(content, 0, len);
+                        zos.flush();
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                } finally {
+                    try {
+                        if (fis != null) {
+                            fis.close();
+                        }
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    public static void copyResource(List<String> oldResPath, String newResPath) {
+        for (int m = 0; m < oldResPath.size(); m++) {
+            try {
+                // 如果文件夹不存在 则建立新文件夹
+                (new File(newResPath)).mkdirs();
+                File a = new File(oldResPath.get(m));
+                // 如果已经是具体文件,读取
+                if (a.isFile()) {
+                    FileInputStream input = new FileInputStream(a);
+                    FileOutputStream output = new FileOutputStream(newResPath + "/" + (a.getName()).toString());
+                    byte[] b = new byte[1024 * 4];
+                    int len;
+                    while ((len = input.read(b)) != -1) {
+                        output.write(b, 0, len);
+                    }
+                    output.flush();
+                    output.close();
+                    input.close();
+                    // 如果文件夹下还存在文件,遍历,直到得到具体的文件
+                } else {
+                    String[] file = a.list();
+                    File temp = null;
+                    for (int i = 0; i < file.length; i++) {
+                        if (oldResPath.get(m).endsWith(File.separator)) {
+                            temp = new File(oldResPath.get(m) + file[i]);
+                        } else {
+                            temp = new File(oldResPath.get(m) + File.separator + file[i]);
+                        }
+
+                        if (temp.isFile()) {
+                            FileInputStream input = new FileInputStream(temp);
+                            FileOutputStream output = new FileOutputStream(newResPath + "/" + (temp.getName()).toString());
+                            byte[] b = new byte[1024 * 4];
+                            int len;
+                            while ((len = input.read(b)) != -1) {
+                                output.write(b, 0, len);
+                            }
+                            output.flush();
+                            output.close();
+                            input.close();
+                        }
+                        if (temp.isDirectory()) {
+                            List<String> oldChildPath = new ArrayList<String>();
+                            oldChildPath.add(oldResPath.get(m) + "/" + file[i]);
+                            newResPath = newResPath + "/" + file[i];
+                            // 如果是子文件夹 递归循环
+                            copyResource(oldChildPath, newResPath);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 删除文件夹
+     *
+     * @param file
+     */
+    public static void deleteFile(File file) {
+        // 判断文件是否存在
+        if (file.exists()) {
+            // 判断是否是文件
+            if (file.isFile()) {
+                file.delete();
+            } else if (file.isDirectory()) {
+                // 否则如果它是一个目录
+                // 声明目录下所有的文件 files[];
+                File files[] = file.listFiles();
+                // 遍历目录下所有的文件
+                for (int i = 0; i < files.length; i++) {
+                    // 把每个文件 用这个方法进行迭代
+                    deleteFile(files[i]);
+                }
+            }
+            file.delete();
+        }
+    }
+
+    /**
+     * 时间格式化
+     *
+     * @return
+     */
+    public static String dateToString() {
+        Date d = new Date();
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
+        String time = formatter.format(d);
+        return time;
+    }
+}
+

+ 87 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/encryptor/AccessKeyEncryptor.java

@@ -0,0 +1,87 @@
+package com.storlead.framework.common.util.encryptor;
+
+/**
+ * @program: sp-salary-system
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-27 18:41
+ */
+import cn.hutool.core.util.StrUtil;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.Base64;
+
+public class AccessKeyEncryptor {
+
+    private static final String ALGORITHM = "AES";
+    private static final int KEY_SIZE = 128;
+
+    private SecretKey secretKey;
+
+    public AccessKeyEncryptor(String secret) {
+        if (StrUtil.isBlank(secret)) {
+            secret = "-storlead$209912";
+        }
+        this.secretKey = new SecretKeySpec(secret.getBytes(), ALGORITHM);
+    }
+
+    public static AccessKeyEncryptor getAccessKeyEncryptor(String secret) {
+        return new AccessKeyEncryptor(secret);
+    }
+
+    /**
+     * 加密
+     * @param accessKey
+     * @return
+     * @throws Exception
+     */
+    public String encrypt(String accessKey) {
+        try {
+            Cipher cipher = Cipher.getInstance(ALGORITHM);
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+            byte[] encryptedBytes = cipher.doFinal(accessKey.getBytes());
+            return Base64.getEncoder().encodeToString(encryptedBytes);
+        }catch (Exception e) {
+            return "";
+        }
+    }
+
+    /**
+     * 解密
+     * @param encryptedAccessKey
+     * @return
+     * @throws Exception
+     */
+    public String decrypt(String encryptedAccessKey) {
+        try {
+            Cipher cipher = Cipher.getInstance(ALGORITHM);
+            cipher.init(Cipher.DECRYPT_MODE, secretKey);
+            byte[] decodedBytes = Base64.getDecoder().decode(encryptedAccessKey);
+            byte[] decryptedBytes = cipher.doFinal(decodedBytes);
+            return new String(decryptedBytes);
+        }catch (Exception e) {
+            return encryptedAccessKey;
+        }
+    }
+
+
+    public static void main(String[] args) {
+        try {
+            String secret = "";
+            AccessKeyEncryptor encryptor = new AccessKeyEncryptor(secret);
+
+            String accessKey = "-Chenkq1992";
+            System.out.println("Original AccessKey: " + accessKey);
+
+            String encryptedAccessKey = encryptor.encrypt(accessKey);
+            System.out.println("Encrypted AccessKey: " + encryptedAccessKey);
+
+            String decryptedAccessKey = encryptor.decrypt(encryptedAccessKey);
+            System.out.println("Decrypted AccessKey: " + decryptedAccessKey);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 78 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/CodeGenerate.java

@@ -0,0 +1,78 @@
+package com.storlead.framework.common.util.generate;
+
+import cn.hutool.core.util.StrUtil;
+import com.storlead.framework.common.constant.CodeGenerateInterface;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @program: sp-project-system
+ * @description:
+ * @author: chenkq
+ * @create: 2021-09-11 11:13
+ */
+public class CodeGenerate {
+
+
+    public static void main(String[] args) {
+        for (int i = 0; i < 200; i++) {
+            System.out.println(CodeGenerate.generate10Code(CodeGenerateInterface.ORDER_COST_CODE));
+        }
+    }
+
+//    public static String Generate8Code(String moduleKey) {
+//        String id = IdWorker.nextId().toString();
+//        System.out.println(id);
+//        String code = StrUtil.sub(id, 8, id.length());
+//        System.out.println(code);
+//        return moduleKey + code;
+//    }
+//
+//    public static String Generate12Code(String moduleKey) {
+//        String id = IdWorker.nextId().toString();
+//        System.out.println(id);
+//        String code = StrUtil.sub(id, id.length()-12, id.length());
+//        System.out.println(code);
+//        return moduleKey + code;
+//    }
+
+    public static String GenerateFullCode() {
+        String code = IdWorker.nextId().toString();
+        return code;
+    }
+
+    /**
+     * 生成模块对于的Code
+     *
+     * @param moduleKey  模块Key
+     * @return encodeStr 生成的唯一的Code
+     * @throws Exception
+     */
+    public static String generate10Code(String moduleKey) {
+        String id = IdWorker.nextId().toString();
+        String code = StrUtil.sub(id, id.length() - 6, id.length());
+        SimpleDateFormat sdf1 = new SimpleDateFormat("yyMM");
+        String time = sdf1.format(new Date(System.currentTimeMillis()));
+        String encodeStr = CodeGenerateInterface.CODE_KEY + moduleKey + time + code;
+        return encodeStr;
+    }
+
+    public static String generate14Code(String moduleKey) {
+        String id = IdWorker.nextId().toString();
+        String code = StrUtil.sub(id, id.length() - 10, id.length());
+        SimpleDateFormat sdf1 = new SimpleDateFormat("yyMM");
+        String time = sdf1.format(new Date(System.currentTimeMillis()));
+        String encodeStr = CodeGenerateInterface.CODE_KEY + moduleKey + time + code;
+        return encodeStr;
+    }
+
+    public static String generate12Code(String moduleKey) {
+        String id = IdWorker.nextId().toString();
+        String code = StrUtil.sub(id, id.length() - 8, id.length());
+        SimpleDateFormat sdf1 = new SimpleDateFormat("yyMM");
+        String time = sdf1.format(new Date(System.currentTimeMillis()));
+        String encodeStr = CodeGenerateInterface.CODE_KEY + moduleKey + time + code;
+        return encodeStr;
+    }
+}

+ 29 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/DefaultIdentifierGenerator.java

@@ -0,0 +1,29 @@
+package com.storlead.framework.common.util.generate;
+
+/**
+ * @program: sp-project-system
+ * @description:
+ * @author: chenkq
+ * @create: 2021-09-11 14:18
+ */
+public class DefaultIdentifierGenerator implements IdentifierGenerator {
+
+    private final Sequence sequence;
+
+    public DefaultIdentifierGenerator() {
+        this.sequence = new Sequence();
+    }
+
+    public DefaultIdentifierGenerator(long workerId, long dataCenterId) {
+        this.sequence = new Sequence(workerId, dataCenterId);
+    }
+
+    public DefaultIdentifierGenerator(Sequence sequence) {
+        this.sequence = sequence;
+    }
+
+    @Override
+    public Long nextId() {
+        return this.sequence.nextId();
+    }
+}

+ 36 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/IdWorker.java

@@ -0,0 +1,36 @@
+package com.storlead.framework.common.util.generate;
+
+import java.time.format.DateTimeFormatter;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * @program: sp-project-system
+ * @description: 编码生成
+ * @author: chenkq
+ * @create: 2021-09-11 09:30
+ */
+public class IdWorker {
+    private static IdentifierGenerator IDENTIFIER_GENERATOR = new DefaultIdentifierGenerator();
+    public static final DateTimeFormatter MILLISECOND = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
+
+    public IdWorker() {
+    }
+
+    public static void initSequence(long workerId, long dataCenterId) {
+        IDENTIFIER_GENERATOR = new DefaultIdentifierGenerator(workerId, dataCenterId);
+    }
+
+    public static void setIdentifierGenerator(IdentifierGenerator identifierGenerator) {
+        IDENTIFIER_GENERATOR = identifierGenerator;
+    }
+
+    public static String get32UUID() {
+        ThreadLocalRandom random = ThreadLocalRandom.current();
+        return (new UUID(random.nextLong(), random.nextLong())).toString().replace("-", "");
+    }
+
+    public static Long nextId() {
+        return IDENTIFIER_GENERATOR.nextId();
+    }
+}

+ 10 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/IdentifierGenerator.java

@@ -0,0 +1,10 @@
+package com.storlead.framework.common.util.generate;
+
+public interface IdentifierGenerator {
+
+    Long nextId();
+
+    default String nextUUID() {
+        return IdWorker.get32UUID();
+    }
+}

+ 126 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/Sequence.java

@@ -0,0 +1,126 @@
+package com.storlead.framework.common.util.generate;
+
+import cn.hutool.core.date.SystemClock;
+import cn.hutool.core.lang.Assert;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * @program: sp-project-system
+ * @description: 雪花算法工具类
+ * @author: chenkq
+ * @create: 2021-09-11 14:13
+ */
+public class Sequence {
+
+    private static final Logger logger = LoggerFactory.getLogger(Sequence.class);
+    private final long twepoch = 1288834974657L;
+    private final long workerIdBits = 5L;
+    private final long datacenterIdBits = 5L;
+    private final long maxWorkerId = 31L;
+    private final long maxDatacenterId = 31L;
+    private final long sequenceBits = 12L;
+    private final long workerIdShift = 12L;
+    private final long datacenterIdShift = 17L;
+    private final long timestampLeftShift = 22L;
+    private final long sequenceMask = 4095L;
+    private final long workerId;
+    private final long datacenterId;
+    private long sequence = 0L;
+    private long lastTimestamp = -1L;
+
+    public Sequence() {
+        this.datacenterId = getDatacenterId(31L);
+        this.workerId = getMaxWorkerId(this.datacenterId, 31L);
+    }
+
+    public Sequence(long workerId, long datacenterId) {
+        Assert.isFalse(workerId > 31L || workerId < 0L, String.format("worker Id can't be greater than %d or less than 0", 31L), new Object[0]);
+        Assert.isFalse(datacenterId > 31L || datacenterId < 0L, String.format("datacenter Id can't be greater than %d or less than 0", 31L), new Object[0]);
+        this.workerId = workerId;
+        this.datacenterId = datacenterId;
+    }
+
+    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
+        StringBuilder mpid = new StringBuilder();
+        mpid.append(datacenterId);
+        String name = ManagementFactory.getRuntimeMXBean().getName();
+        if (StringUtils.isNotBlank(name)) {
+            mpid.append(name.split("@")[0]);
+        }
+
+        return (long)(mpid.toString().hashCode() & '\uffff') % (maxWorkerId + 1L);
+    }
+
+    protected static long getDatacenterId(long maxDatacenterId) {
+        long id = 0L;
+
+        try {
+            InetAddress ip = InetAddress.getLocalHost();
+            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
+            if (network == null) {
+                id = 1L;
+            } else {
+                byte[] mac = network.getHardwareAddress();
+                if (null != mac) {
+                    id = (255L & (long)mac[mac.length - 2] | 65280L & (long)mac[mac.length - 1] << 8) >> 6;
+                    id %= maxDatacenterId + 1L;
+                }
+            }
+        } catch (Exception var7) {
+            logger.warn(" getDatacenterId: " + var7.getMessage());
+        }
+
+        return id;
+    }
+
+    public synchronized long nextId() {
+        long timestamp = this.timeGen();
+        if (timestamp < this.lastTimestamp) {
+            long offset = this.lastTimestamp - timestamp;
+            if (offset > 5L) {
+                throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
+            }
+
+            try {
+                this.wait(offset << 1);
+                timestamp = this.timeGen();
+                if (timestamp < this.lastTimestamp) {
+                    throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
+                }
+            } catch (Exception var6) {
+                throw new RuntimeException(var6);
+            }
+        }
+
+        if (this.lastTimestamp == timestamp) {
+            this.sequence = this.sequence + 1L & 4095L;
+            if (this.sequence == 0L) {
+                timestamp = this.tilNextMillis(this.lastTimestamp);
+            }
+        } else {
+            this.sequence = ThreadLocalRandom.current().nextLong(1L, 3L);
+        }
+
+        this.lastTimestamp = timestamp;
+        return timestamp - 1288834974657L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence;
+    }
+
+    protected long tilNextMillis(long lastTimestamp) {
+        long timestamp;
+        for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
+        }
+
+        return timestamp;
+    }
+
+    protected long timeGen() {
+        return SystemClock.now();
+    }
+}

+ 144 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/sso/CryptoZipUtil.java

@@ -0,0 +1,144 @@
+package com.storlead.framework.common.util.sso;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * @program: storlead-centre-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-11-21 16:03
+ */
+public class CryptoZipUtil {
+
+    private static final String AES_CIPHER = "AES/CBC/PKCS5Padding";
+    private static final String IV = "1234567890abcdef"; // 16字节固定 IV
+    public static final String SALT_KEY = "G4YfTqX6Kh8cHQy5";
+
+
+
+    // ====================================
+    // Map → URL 参数 → GZIP → AES → Base64
+    // ====================================
+    public static String encryptParams(Map<String, String> params, String key) {
+        StringBuilder sb = new StringBuilder();
+        params.forEach((k, v) -> {
+            try {
+                sb.append(URLEncoder.encode(k, "UTF-8"))
+                        .append("=")
+                        .append(URLEncoder.encode(v, "UTF-8"))
+                        .append("&");
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        String paramString = sb.substring(0, sb.length() - 1);
+
+        // 1. GZIP 压缩
+        byte[] gzipData = gzip(paramString.getBytes(StandardCharsets.UTF_8));
+
+        // 2. AES 加密
+        byte[] encryptedBytes = aesEncrypt(gzipData, key);
+
+        // 3. Base64 URL 安全编码
+        return Base64.getUrlEncoder().encodeToString(encryptedBytes);
+    }
+
+    // ====================================
+    // Base64 → AES 解密 → GZIP 解压 → Map
+    // ====================================
+    public static Map<String, String> decryptParams(String encrypted, String key) {
+        try {
+            byte[] encryptedBytes = Base64.getUrlDecoder().decode(encrypted);
+
+            // 1. AES 解密
+            byte[] gzipBytes = aesDecrypt(encryptedBytes, key);
+
+            // 2. GZIP 解压
+            String decrypted = new String(gunzip(gzipBytes), StandardCharsets.UTF_8);
+
+            // 3. URL 参数解析
+            Map<String, String> map = new HashMap<>();
+            for (String pair : decrypted.split("&")) {
+                String[] kv = pair.split("=");
+                if (kv.length == 2) {
+                    map.put(
+                            URLDecoder.decode(kv[0], "UTF-8"),
+                            URLDecoder.decode(kv[1], "UTF-8")
+                    );
+                }
+            }
+            return map;
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // GZIP 压缩
+    private static byte[] gzip(byte[] data) {
+        try {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            try (GZIPOutputStream gzip = new GZIPOutputStream(bos)) {
+                gzip.write(data);
+            }
+            return bos.toByteArray();
+        } catch (Exception e) {
+            throw new RuntimeException("GZIP 压缩失败", e);
+        }
+    }
+
+    // GZIP 解压
+    private static byte[] gunzip(byte[] data) {
+        try {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            GZIPInputStream gis = new GZIPInputStream(new java.io.ByteArrayInputStream(data));
+            byte[] buffer = new byte[1024];
+            int len;
+            while ((len = gis.read(buffer)) > 0) {
+                bos.write(buffer, 0, len);
+            }
+            return bos.toByteArray();
+        } catch (Exception e) {
+            throw new RuntimeException("GZIP 解压失败", e);
+        }
+    }
+
+    // AES 加密
+    private static byte[] aesEncrypt(byte[] content, String key) {
+        try {
+            Cipher cipher = Cipher.getInstance(AES_CIPHER);
+            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
+            IvParameterSpec iv = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
+            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
+            return cipher.doFinal(content);
+        } catch (Exception e) {
+            throw new RuntimeException("AES 加密失败", e);
+        }
+    }
+
+    // AES 解密
+    private static byte[] aesDecrypt(byte[] encrypted, String key) {
+        try {
+            Cipher cipher = Cipher.getInstance(AES_CIPHER);
+            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
+            IvParameterSpec iv = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
+            cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
+            return cipher.doFinal(encrypted);
+        } catch (Exception e) {
+            throw new RuntimeException("AES 解密失败", e);
+        }
+    }
+}
+

+ 62 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/sso/HttpSecurityCheckUtil.java

@@ -0,0 +1,62 @@
+package com.storlead.framework.common.util.sso;
+
+import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @program: storlead-centre-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-11-21 15:01
+ */
+public class HttpSecurityCheckUtil {
+
+    private static final String SALT_KEY = "G4YfTqX6Kh8cHQy5";
+    /**
+     * 除数
+     */
+    public static long divisor = 100861;
+
+    public static String makeSign(Map<String, String> sginArgMap) {
+        List<Map.Entry<String, String>> entryList = new ArrayList<>(sginArgMap.entrySet());
+        entryList.sort(Map.Entry.comparingByKey());
+        /**
+         * 构建字符串
+         */
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : entryList) {
+            if ("sign".equals(entry.getKey())) {
+                continue;
+            }
+            if (sb.length() > 0) {
+                sb.append("&");
+            }
+            sb.append(entry.getKey()).append("=").append(entry.getValue());
+        }
+        String dataToEncrypt = sb.toString();
+        String sign = AccessKeyEncryptor.getAccessKeyEncryptor(SALT_KEY).encrypt(dataToEncrypt);
+        return  sign;
+    }
+
+    public static String buildUrlParams(Map<String, String> paramMap) {
+        if (paramMap == null || paramMap.isEmpty()) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        for (Map.Entry<String, String> entry : paramMap.entrySet()) {
+            if (entry.getValue() == null) continue; // 忽略 null
+            if (sb.length() > 0) sb.append("&");
+
+            sb.append(entry.getKey())
+                    .append("=")
+                    .append(entry.getValue());
+        }
+
+        return sb.toString();
+    }
+}

+ 20 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NeContains.java

@@ -0,0 +1,20 @@
+package com.storlead.framework.common.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+@Constraint(validatedBy = NotContainsValidator.class) // 绑定校验器
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface  NeContains {
+    String message() default "邮箱格式不正确";
+
+    // 用来指定不能包含的字符串
+    String value();
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

+ 31 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NeContainsValidator.java

@@ -0,0 +1,31 @@
+package com.storlead.framework.common.validation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-12-11 11:04
+ */
+public class NeContainsValidator implements ConstraintValidator<NeContains, String> {
+
+    private String forbiddenString;
+
+    @Override
+    public void initialize(NeContains constraintAnnotation) {
+        // 获取注解中定义的不能包含的字符串
+        this.forbiddenString = constraintAnnotation.value();
+    }
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        // 空值直接返回 true,不参与校验
+        if (value == null) {
+            return false;
+        }
+        // 判断字符串是否包含指定的字符串
+        return value.indexOf(forbiddenString) > 0;
+    }
+}

+ 28 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NotContains.java

@@ -0,0 +1,28 @@
+package com.storlead.framework.common.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+import java.lang.annotation.*;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-10-17 10:49
+ */
+
+@Constraint(validatedBy = NotContainsValidator.class) // 绑定校验器
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface NotContains {
+    String message() default "字符串不能包含指定的子字符串";
+
+    // 用来指定不能包含的字符串
+    String value();
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

+ 31 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NotContainsValidator.java

@@ -0,0 +1,31 @@
+package com.storlead.framework.common.validation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-10-17 10:52
+ */
+public class NotContainsValidator implements ConstraintValidator<NotContains, String> {
+
+    private String forbiddenString;
+
+    @Override
+    public void initialize(NotContains constraintAnnotation) {
+        // 获取注解中定义的不能包含的字符串
+        this.forbiddenString = constraintAnnotation.value();
+    }
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        // 空值直接返回 true,不参与校验
+        if (value == null) {
+            return true;
+        }
+        // 判断字符串是否包含指定的字符串
+        return !value.contains(forbiddenString);
+    }
+}

+ 55 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/FeignResult.java

@@ -0,0 +1,55 @@
+package com.storlead.framework.common.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2024-03-17 11:26
+ */
+
+@Data
+public class FeignResult<T> implements Serializable{
+    private static final long serialVersionUID = 8225645185075835261L;
+
+    /**
+     * 是否处理成功
+     */
+    private boolean isSuccess;
+    /**
+     * 状态码, 默认1000是成功
+     */
+    protected int code;
+    /**
+     * 响应信息, 来说明响应情况
+     */
+    protected String msg;
+    /**
+     * 响应的具体数据
+     */
+    private T data;
+
+    public FeignResult() {
+        this.isSuccess = true;
+        this.code = 200;
+        this.msg = "成功";
+        this.data = null;
+    }
+
+    public FeignResult(int code,String msg, T data) {
+        this.code = code;
+        this.msg = msg;
+        this.data = data;
+    }
+
+    public static <T> FeignResult<T> failure(String msg) {
+        return new FeignResult<>(0,msg, null);
+    }
+
+    public static <T> FeignResult<T> success(String msg) {
+        return new FeignResult<>(0,msg, null);
+    }
+}

+ 37 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/StaffVo.java

@@ -0,0 +1,37 @@
+package com.storlead.framework.common.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-18 18:05
+ */
+@Data
+@ApiModel("员工头像类")
+public class StaffVo implements Serializable {
+
+    @ApiModelProperty(value = "分公司ID")
+    private Long subCompanyId;
+
+    @ApiModelProperty(value = "部门ID")
+    private Long deptId;
+
+    @ApiModelProperty(value = "名称ID")
+    private String deptName;
+
+    @ApiModelProperty(value = "员工ID")
+    private Long staffId;
+
+    @ApiModelProperty(value = "员工名称")
+    private String staffName;
+
+    @ApiModelProperty(value = "员工头像")
+    private String staffPhoto;
+
+}

+ 55 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/menu/TreeVo.java

@@ -0,0 +1,55 @@
+package com.storlead.framework.common.vo.menu;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-11 10:17
+ */
+@Data
+public class TreeVo {
+    private Long id;
+    private String treeId = UUID.randomUUID().toString();
+    private Long parentId;
+    private Long menuId;
+    private String title;
+    /**
+     * 存放当前对象,1:菜单资源,2:接口按钮资源
+     */
+    private Object arg;
+    /**
+     * 存放规则引擎:只有type = 2 才可能会有
+     */
+    private Object engine;
+    private Integer rank;
+    private Boolean checked;
+
+    private Boolean _showChildren = false;
+    /**
+     * 存放当前对象,1:菜单资源,2:接口按钮资源
+     */
+    private Integer type;
+
+    /**
+     * 是否展开
+     */
+    private Boolean expand = false;
+
+    /**
+     *
+     */
+    private Boolean disableCheckbox = false;
+
+    /**
+     *  是否可以选择
+     */
+    private Boolean disabled = true;
+
+    private List<TreeVo> children;
+}
+

+ 119 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/user/UserVO.java

@@ -0,0 +1,119 @@
+package com.storlead.framework.common.vo.user;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+import java.util.Date;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-04-23 10:29
+ */
+@Data
+public class UserVO {
+
+    private Long id;
+
+    @ApiModelProperty(value = "用户名")
+    private String username;
+
+    private String password;
+
+    private String xworkUserId;
+
+    @ApiModelProperty(value = "头像")
+    private String avatar;
+
+    @ApiModelProperty(value = "性别")
+    private String sex;
+
+    @ApiModelProperty(value = "昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "姓名")
+    private String realName;
+
+    @ApiModelProperty(value = "姓名")
+    private String mobile;
+
+    @ApiModelProperty(value = "email")
+    private String email;
+
+    @ApiModelProperty(value = "岗位ID")
+    private Long jobId;
+
+    @ApiModelProperty(value = "部门ID")
+    private Long deptId;
+
+    @ApiModelProperty(value = "分公司ID")
+    private Long subCompanyId;
+
+    @ApiModelProperty(value = "公司ID")
+    private Long companyId;
+
+    @ApiModelProperty(value = "直接上级ID")
+    private Long managerId;
+
+    @ApiModelProperty(value = "领导路由")
+    private String managers;
+
+    private String idNum;
+    private String address;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern="yyyy-MM-dd")
+    private LocalDate birthday;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern="yyyy-MM-dd")
+    private LocalDate entryDate;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern="yyyy-MM-dd")
+    private LocalDate permanentDate;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern="yyyy-MM-dd")
+    private LocalDate resignDate;
+    private String bankName;
+    private String bankCardNum;
+
+    @ApiModelProperty(value = "状态 0:试用期 1:正式期 2:临时 3:试用延期 5:离职流程")
+    private String status;
+    private Integer isShenzhenCensus;
+    private String census;
+    private String ethnic;
+    private String marriage;
+    private String education;
+    private Integer enabled;
+    private Long createBy;
+    /**创建日期*/
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+    private Long updateBy;
+    /**创建日期*/
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    @ApiModelProperty(value = "是否负责人:0 否,1分公司负责人,2部门负责人")
+    private Integer isLeader;
+
+    @ApiModelProperty(value = "是否是超管")
+    private Boolean isAdmin;
+
+    @ApiModelProperty(value = "数据权限")
+    private String dataScope;
+
+    @ApiModelProperty(value = "测试")
+    private Integer test;
+
+    @ApiModelProperty(value = "组织路由")
+    private String orgRouteCode;
+}

+ 44 - 0
storlead-framework/storlead-core/pom.xml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-framework</artifactId>
+        <version>1.0</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>storlead-core</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>transmittable-thread-local</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 26 - 0
storlead-framework/storlead-core/src/main/java/com/storlead/framework/annotate/AnnBase.java

@@ -0,0 +1,26 @@
+package com.storlead.framework.annotate;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface AnnBase {
+    /**
+     * KEY字段
+     * @return
+     */
+    String keyField() default "";
+    /**
+     * 赋值字段
+     * @return
+     */
+    String valueField() default "";
+    /**
+     * 是否多字典
+     * @return
+     */
+    boolean multiple() default false;
+
+}
+

+ 22 - 0
storlead-framework/storlead-core/src/main/java/com/storlead/framework/annotate/AnnotateEnumType.java

@@ -0,0 +1,22 @@
+package com.storlead.framework.annotate;
+
+public enum AnnotateEnumType {
+    //SubCompany
+    //Dept
+    //User
+    DICT("Dict"),
+    SUB_COMPANY("SubCompany"),
+    COMPANY("Company"),
+    DEPT("Dept"),
+    USER("User");
+
+    private String type;
+
+    AnnotateEnumType(String type) {
+        this.type = type;
+    }
+
+    public String getType() {
+        return type;
+    }
+}

+ 11 - 0
storlead-framework/storlead-core/src/main/java/com/storlead/framework/annotate/Dept.java

@@ -0,0 +1,11 @@
+package com.storlead.framework.annotate;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-21 16:38
+ */
+//public @interface @Dept extends AnnBase {
+//
+//}

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません