Ver código fonte

Merge remote-tracking branch 'origin/master'

183207892172 5 dias atrás
pai
commit
7f53864816
100 arquivos alterados com 4761 adições e 285 exclusões
  1. 8 0
      java/storlead-api/pom.xml
  2. 3 1
      java/storlead-api/src/main/java/com/storlead/api/StorleadTradeApplication.java
  3. 1 1
      java/storlead-api/src/main/java/com/storlead/system/CorpWeChatController.java
  4. 1 2
      java/storlead-api/src/main/java/com/storlead/system/controller/FileResourceController.java
  5. 0 141
      java/storlead-api/src/main/java/com/storlead/system/controller/SysAppApiController.java
  6. 12 1
      java/storlead-api/src/main/resources/application-dev.yml
  7. 11 1
      java/storlead-api/src/main/resources/application-prod.yml
  8. 13 1
      java/storlead-api/src/main/resources/application-test.yml
  9. 24 2
      java/storlead-api/src/main/resources/application.yml
  10. 68 56
      java/storlead-dependencies/pom.xml
  11. 2 3
      java/storlead-es/pom.xml
  12. 11 3
      java/storlead-es/storlead-es-api/pom.xml
  13. 0 17
      java/storlead-es/storlead-es-api/src/main/java/com/storlead/es/server/EsSearchCustomerService.java
  14. 14 3
      java/storlead-es/storlead-es-biz/pom.xml
  15. 16 25
      java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/config/ElasticSearchConfig.java
  16. 19 0
      java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/config/EsAutoConfiguration.java
  17. 27 0
      java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/config/EsProperties.java
  18. 1 1
      java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/internal/BaseSearchService.java
  19. 3 1
      java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/server/impl/BaseSearchServiceImpl.java
  20. 3 1
      java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/server/impl/EsSearchCustomerServiceImpl.java
  21. 23 0
      java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/service/impl/NoopEsSearchCustomerServiceImpl.java
  22. 2 0
      java/storlead-es/storlead-es-biz/src/main/resources/META-INF/spring.factories
  23. 1 6
      java/storlead-es/storlead-es-core/pom.xml
  24. 0 0
      java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/EsGenericVO.java
  25. 0 3
      java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/EsQuerySimilarityVO.java
  26. 0 0
      java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/EsQueryVO.java
  27. 0 0
      java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/FieldConfig.java
  28. 0 0
      java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/IndexFieldConfig.java
  29. 25 0
      java/storlead-es/storlead-es-spi/pom.xml
  30. 8 0
      java/storlead-es/storlead-es-spi/src/main/java/com/storlead/es/server/EsSearchCustomerService.java
  31. 18 0
      java/storlead-es/storlead-es-spi/src/main/java/com/storlead/es/service/EsSearchCustomerService.java
  32. 31 0
      java/storlead-framework/storlead-common/pom.xml
  33. 33 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/config/AutowiringSpringBeanJobFactory.java
  34. 50 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/config/QuartzConfig.java
  35. 5 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DSConstants.java
  36. 1 1
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/CloseUtil.java
  37. 12 2
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ConvertUtils.java
  38. 20 11
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtil.java
  39. 1 2
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/FileUtil.java
  40. 151 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/HttpUtil.java
  41. 48 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SpringCompareUtil.java
  42. 372 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/WordUtils.java
  43. 37 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/StaffVo.java
  44. 23 0
      java/storlead-framework/storlead-mybatis/pom.xml
  45. 113 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceAspect.java
  46. 9 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceConfiguration.java
  47. 40 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceProperties.java
  48. 7 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/query/QueryBaseEntity.java
  49. 42 0
      java/storlead-mail/pom.xml
  50. 41 0
      java/storlead-mail/storlead-mail-api/pom.xml
  51. 19 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/ClientSentEmailsController.java
  52. 170 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailFolderRuleApiController.java
  53. 94 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailServiceChecker.java
  54. 155 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MaiAttachmentApiController.java
  55. 133 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailBlacklistRecordApiController.java
  56. 123 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailTemplatesApiController.java
  57. 136 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailboxAutoReplySetController.java
  58. 442 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopMailApiController.java
  59. 457 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopSettingsApiController.java
  60. 135 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/UserEmailFolderApiController.java
  61. 46 0
      java/storlead-mail/storlead-mail-biz/pom.xml
  62. 157 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailReceiver.java
  63. 53 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailSender.java
  64. 129 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnection.java
  65. 27 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnectionFactory.java
  66. 21 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnection.java
  67. 26 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnectionFactory.java
  68. 126 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3Connection.java
  69. 26 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3ConnectionFactory.java
  70. 53 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnection.java
  71. 25 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnectionFactory.java
  72. 93 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailConnectionUtil.java
  73. 48 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailReceiverUtil.java
  74. 38 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailSenderUtil.java
  75. 43 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/config/MailProperties.java
  76. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/AttachmentMapper.java
  77. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/ClientSentEmailsMapper.java
  78. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailBlacklistRecordMapper.java
  79. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFileFolderMapper.java
  80. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFolderRuleMapper.java
  81. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFoldersMapper.java
  82. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailTemplatesMapper.java
  83. 107 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailsMapper.java
  84. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/FoldersMapper.java
  85. 23 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailAttachmentMapper.java
  86. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailDelayPullRecordMapper.java
  87. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailServerPortConfigMapper.java
  88. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailTempAttachmentMapper.java
  89. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailboxAutoReplySetMapper.java
  90. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/SmtpPopSettingsMapper.java
  91. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserEmailFolderMapper.java
  92. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserMailTrackRecordMapper.java
  93. 57 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/properties/MailFileProperties.java
  94. 14 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AbstractMailFunctionApiFactory.java
  95. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AttachmentServiceImpl.java
  96. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/ClientSentEmailsServiceImpl.java
  97. 38 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailBlacklistRecordServiceImpl.java
  98. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFileFolderServiceImpl.java
  99. 77 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFolderRuleServiceImpl.java
  100. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFoldersServiceImpl.java

+ 8 - 0
java/storlead-api/pom.xml

@@ -36,6 +36,10 @@
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-customer</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-sales</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
@@ -56,6 +60,10 @@
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-system-biz</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-system-api</artifactId>
+        </dependency>
 
         <!-- UserServiceImpl 等依赖 ThirdPartyUserSyncService,默认实现见 storlead-thirdparty-biz -->
         <dependency>

+ 3 - 1
java/storlead-api/src/main/java/com/storlead/api/StorleadTradeApplication.java

@@ -18,7 +18,9 @@ import java.net.InetAddress;
         "com.storlead.crm.customer.mapper",
         "com.storlead.user.mapper",
         "com.storlead.sms.mapper",
-        "com.storlead.system.mapper"
+        "com.storlead.system.mapper",
+        "com.storlead.sales.mapper",
+        "com.storlead.mail.mapper"
 })
 @Slf4j
 public class StorleadTradeApplication {

+ 1 - 1
java/storlead-api/src/main/java/com/storlead/system/controller/CorpWeChatController.java → java/storlead-api/src/main/java/com/storlead/system/CorpWeChatController.java

@@ -1,4 +1,4 @@
-package com.storlead.system.controller;
+package com.storlead.system;
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.SecureUtil;

+ 1 - 2
java/storlead-api/src/main/java/com/storlead/system/controller/FileResourceController.java

@@ -3,13 +3,13 @@ package com.storlead.system.controller;
 
 import com.alibaba.fastjson.JSONObject;
 import com.storlead.framework.common.result.Result;
+import com.storlead.framework.common.util.FileUtil;
 import com.storlead.framework.common.util.OssBootUtil;
 import com.storlead.framework.common.util.generate.CodeGenerate;
 import com.storlead.system.pojo.entity.FileResourceEntity;
 import com.storlead.system.properties.FileProperties;
 import com.storlead.system.service.FileResourceService;
 import com.storlead.system.service.IOSSFileService;
-import com.storlead.system.util.FileUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -19,7 +19,6 @@ import org.springframework.core.env.Environment;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
-import javax.annotation.Resource;
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
 import java.io.File;

+ 0 - 141
java/storlead-api/src/main/java/com/storlead/system/controller/SysAppApiController.java

@@ -1,141 +0,0 @@
-package com.storlead.system.controller;
-
-import cn.hutool.core.util.StrUtil;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.storlead.framework.common.result.Result;
-import com.storlead.system.pojo.dto.SysAppPageDTO;
-import com.storlead.system.pojo.entity.SysAppEntity;
-import com.storlead.system.service.SysAppService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import java.util.Date;
-import java.util.Objects;
-
-/**
- * 系统应用管理
- */
-@Api(tags = "系统: 应用管理")
-@RestController
-@RequestMapping("/sys/app")
-public class SysAppApiController {
-
-    @Resource
-    private SysAppService sysAppService;
-
-    @ApiOperation("应用分页")
-    @PostMapping("/pagelist")
-    public Result<IPage<SysAppEntity>> pagelist(@RequestBody SysAppPageDTO dto) {
-        IPage<SysAppEntity> page = new Page<>(dto.getPageIndex(), dto.getPageSize());
-        LambdaQueryWrapper<SysAppEntity> w = new LambdaQueryWrapper<>();
-        w.eq(SysAppEntity::getIsDelete, 0);
-        if (StrUtil.isNotBlank(dto.getAppCode())) {
-            w.like(SysAppEntity::getAppCode, dto.getAppCode().trim());
-        }
-        if (StrUtil.isNotBlank(dto.getAppName())) {
-            w.like(SysAppEntity::getAppName, dto.getAppName().trim());
-        }
-        if (dto.getEnabled() != null) {
-            w.eq(SysAppEntity::getEnabled, dto.getEnabled());
-        }
-        w.orderByAsc(SysAppEntity::getSort).orderByAsc(SysAppEntity::getId);
-        return Result.ok(sysAppService.page(page, w));
-    }
-
-    @ApiOperation("已启用应用列表(下拉等)")
-    @PostMapping("/listEnabled")
-    public Result<?> listEnabled() {
-        return Result.ok(sysAppService.listEnabled());
-    }
-
-    @ApiOperation("详情")
-    @PostMapping("/detail")
-    public Result<?> detail(@RequestParam Long id) {
-        SysAppEntity one = sysAppService.getOne(new LambdaQueryWrapper<SysAppEntity>()
-                .eq(SysAppEntity::getId, id)
-                .eq(SysAppEntity::getIsDelete, 0));
-        if (one == null) {
-            return Result.error("应用不存在或已删除");
-        }
-        return Result.ok(one);
-    }
-
-    @ApiOperation("保存或更新")
-    @PostMapping("/save")
-    public Result<?> save(@RequestBody SysAppEntity body) {
-        if (body == null || StrUtil.isBlank(body.getAppCode()) || StrUtil.isBlank(body.getAppName())) {
-            return Result.error("appCode、appName 不能为空");
-        }
-        body.setAppCode(body.getAppCode().trim());
-        body.setAppName(body.getAppName().trim());
-
-        Date now = new Date();
-        if (body.getId() == null) {
-            if (body.getEnabled() == null) {
-                body.setEnabled(1);
-            }
-            if (body.getIsDelete() == null) {
-                body.setIsDelete(0);
-            }
-            if (body.getSort() == null) {
-                body.setSort(0);
-            }
-            if (body.getOwnerBy() == null) {
-                body.setOwnerBy(0L);
-            }
-            body.setCreateTime(now);
-        }
-
-        LambdaQueryWrapper<SysAppEntity> dup = new LambdaQueryWrapper<SysAppEntity>()
-                .eq(SysAppEntity::getAppCode, body.getAppCode())
-                .eq(SysAppEntity::getIsDelete, 0);
-        if (body.getId() != null) {
-            dup.ne(SysAppEntity::getId, body.getId());
-        }
-        if (sysAppService.count(dup) > 0) {
-            return Result.error("应用编码已存在: " + body.getAppCode());
-        }
-
-        body.setUpdateTime(now);
-
-        sysAppService.saveOrUpdate(body);
-        return Result.ok();
-    }
-
-    @ApiOperation("启用/禁用")
-    @PostMapping("/enable")
-    public Result<?> enable(@RequestParam Long id, @RequestParam Boolean enabled) {
-        if (id == null || enabled == null) {
-            return Result.error("参数错误");
-        }
-        LambdaUpdateWrapper<SysAppEntity> uw = new LambdaUpdateWrapper<>();
-        uw.set(SysAppEntity::getEnabled, Boolean.TRUE.equals(enabled) ? 1 : 0);
-        uw.set(SysAppEntity::getUpdateTime, new Date());
-        uw.eq(SysAppEntity::getId, id).eq(SysAppEntity::getIsDelete, 0);
-        if (!sysAppService.update(uw)) {
-            return Result.error("更新失败,应用不存在或已删除");
-        }
-        return Result.ok();
-    }
-
-    @ApiOperation("逻辑删除(无关联菜单时)")
-    @PostMapping("/delete")
-    public Result<?> delete(@RequestParam Long id) {
-        if (Objects.isNull(id)) {
-            return Result.error("id 不能为空");
-        }
-        if (!sysAppService.trySoftDelete(id)) {
-            return Result.error("删除失败:仍存在关联菜单,或记录不存在");
-        }
-        return Result.ok();
-    }
-}

+ 12 - 1
java/storlead-api/src/main/resources/application-dev.yml

@@ -130,7 +130,7 @@ spring:
 #          password: rCgRgLjH99Xvg5BN
 #mybatis plus 设置
 mybatis-plus:
-  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  mapper-locations: classpath*:/mapper/*Mapper.xml,classpath*:/mapper/*/*Mapper.xml
   # 实体扫描,多个 package 用逗号或者分号分隔
   type-aliases-package:
   typeEnumsPackage:
@@ -180,3 +180,14 @@ logging:
 
 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

+ 11 - 1
java/storlead-api/src/main/resources/application-prod.yml

@@ -108,7 +108,7 @@ spring:
           password: 3raNoDvbo7jqbwtedQGQ
 #mybatis plus 设置
 mybatis-plus:
-  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  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
@@ -152,7 +152,16 @@ sp:
     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
 
@@ -222,3 +231,4 @@ file:
   dify:
     base-url: http://192.168.1.77/v1/
     dataset-api-key: dataset-qfhXMNcjsRSwN5CK6aoWx3hl
+

+ 13 - 1
java/storlead-api/src/main/resources/application-test.yml

@@ -93,10 +93,20 @@ spring:
           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
+  mapper-locations: classpath*:/mapper/*Mapper.xml,classpath*:/mapper/*/*Mapper.xml
   # 实体扫描,多个 package 用逗号或者分号分隔
   type-aliases-package: com.storlead.tems.modules.*.entity
   type-enums-package:
@@ -142,3 +152,5 @@ logging:
               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

+ 24 - 2
java/storlead-api/src/main/resources/application.yml

@@ -11,7 +11,7 @@ spring:
   #      secretKey: VzMzUUNCY0g2cnRVQkR6OU5kTnVUY2tkZWlodFdkc0dpdVRwTmk4dnRWc2lKYmllRnEyekVLV29NWEJIM2IzSm1wRllacWdndFZmZFY0UTk0RmhxQm4zR1R4
   #      expiryInHours: 2400
   application:
-    name: sp-smarttrade
+    name: storlead-saas
   main:
     allow-circular-references: true
     allow-bean-definition-overriding: true
@@ -39,7 +39,7 @@ spring:
             threadCount: 10
             threadPriority: 5
             threadsInheritContextClassLoaderOfInitializingThread: true
-            class: org.quartz.simpl.SimpleThreadPool
+            class: org.quartz.simpl.SimpleThreadPool`
   # 在上下文中没有Executor bean的情况下,Spring Boot会自动配置ThreadPoolTaskExecutor,并使用合理的默认值,这些默认值可以自动关联到异步任务执行(@EnableAsync)和Spring MVC异步请求处理。
   #如果您已经在上下文中定义了一个自定义执行器,那么常规任务执行(即@EnableAsync)将透明地使用它,但是Spring MVC支持将不会被配置,因为它需要一个AsyncTaskExecutor实现(名为applicationTaskExecutor)。根据您的目标安排,您可以将执行程序更改为ThreadPoolTaskExecutor,或者定义一个ThreadPoolTaskExecutor和一个包装自定义执行程序的AsyncConfigurer。
   task:
@@ -49,8 +49,30 @@ spring:
         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.service
+          - com.storlead.sales.mapper
+          - com.storlead.mail.service
+          - com.storlead.mail.mapper
+        datasource: sales

+ 68 - 56
java/storlead-dependencies/pom.xml

@@ -66,11 +66,7 @@
                 <version>${spring-web.version}</version>
             </dependency>
 
-            <dependency>
-                <groupId>com.baomidou</groupId>
-                <artifactId>mybatis-plus-generator</artifactId>
-                <version>${mybatis-plus.version}</version>
-            </dependency>
+
 
 
 <!--            &lt;!&ndash;swagger&ndash;&gt;-->
@@ -90,11 +86,11 @@
                 <version>${swagger.annotations.version}</version>
             </dependency>
 
-            <dependency>
-                <groupId>com.google.code.gson</groupId>
-                <artifactId>gson</artifactId>
-                <version>${google.code.gson.version}</version> <!-- 建议使用最新版本 -->
-            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>com.google.code.gson</groupId>-->
+<!--                <artifactId>gson</artifactId>-->
+<!--                <version>${google.code.gson.version}</version> &lt;!&ndash; 建议使用最新版本 &ndash;&gt;-->
+<!--            </dependency>-->
 
             <dependency>
                 <groupId>com.alibaba</groupId>
@@ -108,20 +104,18 @@
                 <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.client5</groupId>
-                <artifactId>httpclient5</artifactId>
-                <version>${apache.httpcore5.client5.version}</version>
-            </dependency>
-
-            <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5 -->
-            <dependency>
-                <groupId>org.apache.httpcomponents.core5</groupId>
-                <artifactId>httpcore5</artifactId>
-                <version>${apache.httpcore5.version}</version>
-            </dependency>
+<!--            &lt;!&ndash; https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5 &ndash;&gt;-->
+<!--            <dependency>-->
+<!--                <groupId>org.apache.httpcomponents.core5</groupId>-->
+<!--                <artifactId>httpcore5</artifactId>-->
+<!--                <version>${apache.httpcore5.version}</version>-->
+<!--            </dependency>-->
 
             <!--JWT-->
             <dependency>
@@ -136,12 +130,12 @@
                 <artifactId>jjwt</artifactId>
                 <version>${io.jsonwebtoken.version}</version>
             </dependency>
-            <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5-h2 -->
-            <dependency>
-                <groupId>org.apache.httpcomponents.core5</groupId>
-                <artifactId>httpcore5-h2</artifactId>
-                <version>${apache.httpcore5-h2.version}</version>
-            </dependency>
+<!--            &lt;!&ndash; https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5-h2 &ndash;&gt;-->
+<!--            <dependency>-->
+<!--                <groupId>org.apache.httpcomponents.core5</groupId>-->
+<!--                <artifactId>httpcore5-h2</artifactId>-->
+<!--                <version>${apache.httpcore5-h2.version}</version>-->
+<!--            </dependency>-->
 
             <dependency>
                 <groupId>org.apache.httpcomponents</groupId>
@@ -161,11 +155,11 @@
 <!--                <version>${squareup.okhttp.version}</version> &lt;!&ndash; 2023年最新稳定版 &ndash;&gt;-->
 <!--            </dependency>-->
 
-            <dependency>
-                <groupId>com.squareup.okhttp3</groupId>
-                <artifactId>okhttp-sse</artifactId>
-                <version>${okhttp.sse.version}</version>
-            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>com.squareup.okhttp3</groupId>-->
+<!--                <artifactId>okhttp-sse</artifactId>-->
+<!--                <version>${okhttp.sse.version}</version>-->
+<!--            </dependency>-->
 
             <dependency>
                 <groupId>org.projectlombok</groupId>
@@ -222,11 +216,26 @@
                 <version>${mysql.connector.version}</version>
             </dependency>
 
+            <!-- 动态数据源 -->
+            <!-- mybatis-plus -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
             <!-- 动态数据源 -->
             <dependency>
                 <groupId>com.baomidou</groupId>
                 <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
-                <version>${datasource.spring.boot.starter}</version>
+                <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 -->
@@ -236,17 +245,26 @@
                 <version>${druid.version}</version>
             </dependency>
 
-            <dependency>
-                <groupId>com.github.yulichang</groupId>
-                <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
-                <version>${mybatis-plus-join.version}</version>
-            </dependency>
 
-            <dependency>
-                <groupId>org.java-websocket</groupId>
-                <artifactId>Java-WebSocket</artifactId>
-                <version>${spring.web.socket.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>-->
+<!--                <artifactId>mybatis-plus-join-boot-starter</artifactId> &lt;!&ndash; MyBatis 联表查询 &ndash;&gt;-->
+<!--                <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>
@@ -261,11 +279,11 @@
                 </exclusions>
             </dependency>
 
-            <dependency>
-                <groupId>org.jsoup</groupId>
-                <artifactId>jsoup</artifactId>
-                <version>${jsoup.version}</version>
-            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>org.jsoup</groupId>-->
+<!--                <artifactId>jsoup</artifactId>-->
+<!--                <version>${jsoup.version}</version>-->
+<!--            </dependency>-->
 
             <dependency>
                 <groupId>com.google.guava</groupId>
@@ -273,12 +291,6 @@
                 <version>${guava.version}</version>
             </dependency>
 
-            <dependency>
-                <groupId>org.mockito</groupId>
-                <artifactId>mockito-inline</artifactId>
-                <version>${mockito-inline.version}</version> <!-- 支持 Mockito 的 final 类与 static 方法的 mock -->
-            </dependency>
-
             <dependency>
                 <groupId>org.springdoc</groupId>
                 <artifactId>springdoc-openapi-ui</artifactId>

+ 2 - 3
java/storlead-es/pom.xml

@@ -14,13 +14,12 @@
     <packaging>pom</packaging>
     <name>storlead-es</name>
     <version>1.0</version>
-    <description>es aggregate module</description>
+    <description>Elasticsearch 域聚合模块</description>
 
     <modules>
         <module>storlead-es-core</module>
+        <module>storlead-es-spi</module>
         <module>storlead-es-biz</module>
         <module>storlead-es-api</module>
     </modules>
-
 </project>
-

+ 11 - 3
java/storlead-es/storlead-es-api/pom.xml

@@ -14,16 +14,24 @@
     <artifactId>storlead-es-api</artifactId>
     <packaging>jar</packaging>
     <name>storlead-es-api</name>
+    <description>ES 域 Web:Controller 等。</description>
 
     <dependencies>
         <dependency>
             <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-common</artifactId>
+            <artifactId>storlead-es-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-es-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-es-biz</artifactId>
         </dependency>
-
         <dependency>
             <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-mybatis</artifactId>
+            <artifactId>storlead-web</artifactId>
         </dependency>
     </dependencies>
 </project>

+ 0 - 17
java/storlead-es/storlead-es-api/src/main/java/com/storlead/es/server/EsSearchCustomerService.java

@@ -1,17 +0,0 @@
-package com.storlead.es.server;
-
-import com.storlead.es.pojo.vo.EsGenericVO;
-import com.storlead.es.pojo.vo.EsQuerySimilarityVO;
-
-import java.util.List;
-
-/**
- * @program: sp-sales-platform
- * @description:
- * @author: chenkq
- * @create: 2025-04-16 09:56
- */
-public interface EsSearchCustomerService {
-
-    List<EsQuerySimilarityVO> listComparisonSimilarity(EsGenericVO dto);
-}

+ 14 - 3
java/storlead-es/storlead-es-biz/pom.xml

@@ -14,21 +14,32 @@
     <artifactId>storlead-es-biz</artifactId>
     <packaging>jar</packaging>
     <name>storlead-es-biz</name>
+    <description>ES 域实现:检索 Service、ES 客户端配置与可选装配。</description>
 
     <dependencies>
         <dependency>
             <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-es-api</artifactId>
+            <artifactId>storlead-es-spi</artifactId>
         </dependency>
-
         <dependency>
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-es-core</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mybatis</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
     </dependencies>
 </project>

+ 16 - 25
java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/config/ElasticSearchConfig.java

@@ -6,39 +6,30 @@ import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.elasticsearch.client.RestClient;
 import org.elasticsearch.client.RestHighLevelClient;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 /**
- * @program: sp-tems-gotr
- * @description:
- * @author: chenkq
- * @create: 2023-06-16 17:03
+ * Elasticsearch 客户端,仅在 {@code storlead.es.enabled=true} 时创建。
  */
 @Configuration
+@ConditionalOnProperty(prefix = "storlead.es", name = "enabled", havingValue = "true")
+@EnableConfigurationProperties(EsProperties.class)
 public class ElasticSearchConfig {
 
-    @Bean
-    public RestHighLevelClient restHighLevelClient() {
-
-        String hostname = "39.108.252.62";
-        int port = 9200;
-        // 定义 Elasticsearch 集群的用户名和密码
-        String username = "elastic";
-        String password = "storlead123456";
-        // 配置 RestClient
-        RestHighLevelClient client = new RestHighLevelClient(
-                RestClient.builder(
-                                new HttpHost(hostname, port, "http"))
+    @Bean(destroyMethod = "close")
+    public RestHighLevelClient restHighLevelClient(EsProperties properties) {
+        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+        if (properties.getUsername() != null && !properties.getUsername().isEmpty()) {
+            credentialsProvider.setCredentials(
+                    AuthScope.ANY,
+                    new UsernamePasswordCredentials(properties.getUsername(), properties.getPassword()));
+        }
+        return new RestHighLevelClient(
+                RestClient.builder(new HttpHost(properties.getHost(), properties.getPort(), properties.getScheme()))
                         .setHttpClientConfigCallback(httpClientBuilder ->
-                                httpClientBuilder.setDefaultCredentialsProvider(
-                                        new BasicCredentialsProvider() {{
-                                            setCredentials(AuthScope.ANY,
-                                                    new UsernamePasswordCredentials(username, password));
-                                        }}
-                                )
-                        )
-        );
-        return client;
+                                httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)));
     }
 }

+ 19 - 0
java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/config/EsAutoConfiguration.java

@@ -0,0 +1,19 @@
+package com.storlead.es.config;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * ES 域自动配置:始终注册 {@link com.storlead.es.service.EsSearchCustomerService}(Noop 或真实实现)。
+ */
+@Configuration
+@EnableConfigurationProperties(EsProperties.class)
+@Import(ElasticSearchConfig.class)
+@ComponentScan(basePackages = {
+        "com.storlead.es.service.impl",
+        "com.storlead.es.server.impl"
+})
+public class EsAutoConfiguration {
+}

+ 27 - 0
java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/config/EsProperties.java

@@ -0,0 +1,27 @@
+package com.storlead.es.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Elasticsearch 连接与开关配置。
+ */
+@Data
+@ConfigurationProperties(prefix = "storlead.es")
+public class EsProperties {
+
+    /**
+     * 是否启用 ES 检索(false 时使用 Noop 实现,不创建 ES 客户端)。
+     */
+    private boolean enabled = false;
+
+    private String host = "127.0.0.1";
+
+    private int port = 9200;
+
+    private String scheme = "http";
+
+    private String username = "elastic";
+
+    private String password = "";
+}

+ 1 - 1
java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/server/BaseSearchService.java → java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/internal/BaseSearchService.java

@@ -1,4 +1,4 @@
-package com.storlead.es.server;
+package com.storlead.es.internal;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.storlead.framework.common.dto.page.PageDTO;

+ 3 - 1
java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/server/impl/BaseSearchServiceImpl.java

@@ -1,7 +1,8 @@
 package com.storlead.es.server.impl;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.storlead.es.server.BaseSearchService;
+import com.storlead.es.internal.BaseSearchService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import com.storlead.framework.common.dto.page.PageDTO;
 import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.common.unit.Fuzziness;
@@ -37,6 +38,7 @@ import java.util.stream.Collectors;
  * @create: 2023-06-16 16:42
  */
 @Service
+@ConditionalOnProperty(prefix = "storlead.es", name = "enabled", havingValue = "true")
 public class BaseSearchServiceImpl<T> implements BaseSearchService<T> {
 
     private Logger logger = LoggerFactory.getLogger(getClass());

+ 3 - 1
java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/server/impl/EsSearchCustomerServiceImpl.java

@@ -5,7 +5,8 @@ import com.storlead.es.pojo.vo.EsGenericVO;
 import com.storlead.es.pojo.vo.EsQuerySimilarityVO;
 import com.storlead.es.pojo.vo.FieldConfig;
 import com.storlead.es.pojo.vo.IndexFieldConfig;
-import com.storlead.es.server.EsSearchCustomerService;
+import com.storlead.es.service.EsSearchCustomerService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.apache.commons.lang3.tuple.Pair;
 import org.elasticsearch.index.query.BoolQueryBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
@@ -28,6 +29,7 @@ import java.util.*;
 import java.util.stream.Collectors;
 
 @Service
+@ConditionalOnProperty(prefix = "storlead.es", name = "enabled", havingValue = "true")
 public class EsSearchCustomerServiceImpl implements EsSearchCustomerService {
 
     @Resource

+ 23 - 0
java/storlead-es/storlead-es-biz/src/main/java/com/storlead/es/service/impl/NoopEsSearchCustomerServiceImpl.java

@@ -0,0 +1,23 @@
+package com.storlead.es.service.impl;
+
+import com.storlead.es.pojo.vo.EsGenericVO;
+import com.storlead.es.pojo.vo.EsQuerySimilarityVO;
+import com.storlead.es.service.EsSearchCustomerService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * ES 未启用时的空实现,避免业务模块强依赖 Elasticsearch 运行时。
+ */
+@Service
+@ConditionalOnProperty(prefix = "storlead.es", name = "enabled", havingValue = "false", matchIfMissing = true)
+public class NoopEsSearchCustomerServiceImpl implements EsSearchCustomerService {
+
+    @Override
+    public List<EsQuerySimilarityVO> listComparisonSimilarity(EsGenericVO dto) {
+        return Collections.emptyList();
+    }
+}

+ 2 - 0
java/storlead-es/storlead-es-biz/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.storlead.es.config.EsAutoConfiguration

+ 1 - 6
java/storlead-es/storlead-es-core/pom.xml

@@ -14,17 +14,12 @@
     <artifactId>storlead-es-core</artifactId>
     <packaging>jar</packaging>
     <name>storlead-es-core</name>
+    <description>ES 域模型:dto、vo 等。</description>
 
     <dependencies>
         <dependency>
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-common</artifactId>
         </dependency>
-
-        <dependency>
-            <groupId>com.storlead.boot</groupId>
-            <artifactId>storlead-mybatis</artifactId>
-        </dependency>
-
     </dependencies>
 </project>

+ 0 - 0
java/storlead-es/storlead-es-api/src/main/java/com/storlead/es/pojo/vo/EsGenericVO.java → java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/EsGenericVO.java


+ 0 - 3
java/storlead-es/storlead-es-api/src/main/java/com/storlead/es/pojo/vo/EsQuerySimilarityVO.java → java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/EsQuerySimilarityVO.java

@@ -1,8 +1,6 @@
 package com.storlead.es.pojo.vo;
 
 import com.alibaba.fastjson.annotation.JSONField;
-import com.baomidou.mybatisplus.annotation.FieldFill;
-import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -62,6 +60,5 @@ public class EsQuerySimilarityVO {
     @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
     @JSONField(format ="yyyy-MM-dd HH:mm:ss")
-    @TableField(fill = FieldFill.INSERT)
     private Date createTime;
 }

+ 0 - 0
java/storlead-es/storlead-es-api/src/main/java/com/storlead/es/pojo/vo/EsQueryVO.java → java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/EsQueryVO.java


+ 0 - 0
java/storlead-es/storlead-es-api/src/main/java/com/storlead/es/pojo/vo/FieldConfig.java → java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/FieldConfig.java


+ 0 - 0
java/storlead-es/storlead-es-api/src/main/java/com/storlead/es/pojo/vo/IndexFieldConfig.java → java/storlead-es/storlead-es-core/src/main/java/com/storlead/es/pojo/vo/IndexFieldConfig.java


+ 25 - 0
java/storlead-es/storlead-es-spi/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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-es</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-es-spi</artifactId>
+    <packaging>jar</packaging>
+    <name>storlead-es-spi</name>
+    <description>ES 域对外契约:跨模块调用的 Service 接口。</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-es-core</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 8 - 0
java/storlead-es/storlead-es-spi/src/main/java/com/storlead/es/server/EsSearchCustomerService.java

@@ -0,0 +1,8 @@
+package com.storlead.es.server;
+
+/**
+ * @deprecated 请使用 {@link com.storlead.es.service.EsSearchCustomerService}
+ */
+@Deprecated
+public interface EsSearchCustomerService extends com.storlead.es.service.EsSearchCustomerService {
+}

+ 18 - 0
java/storlead-es/storlead-es-spi/src/main/java/com/storlead/es/service/EsSearchCustomerService.java

@@ -0,0 +1,18 @@
+package com.storlead.es.service;
+
+import com.storlead.es.pojo.vo.EsGenericVO;
+import com.storlead.es.pojo.vo.EsQuerySimilarityVO;
+
+import java.util.List;
+
+/**
+ * 客户 ES 相似度检索对外契约。
+ * <p>
+ * 业务模块编译期依赖 {@code storlead-es-spi};运行时引入 {@code storlead-es-biz} 提供实现。
+ * {@code storlead.es.enabled=false} 时使用空实现,不连接 Elasticsearch。
+ * </p>
+ */
+public interface EsSearchCustomerService {
+
+    List<EsQuerySimilarityVO> listComparisonSimilarity(EsGenericVO dto);
+}

+ 31 - 0
java/storlead-framework/storlead-common/pom.xml

@@ -66,6 +66,12 @@
             <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>
@@ -156,6 +162,24 @@
             <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>-->
@@ -172,6 +196,13 @@
             <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>

+ 33 - 0
java/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
java/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;
+    }
+}

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

@@ -16,6 +16,11 @@ public class DSConstants {
      */
     public static final String DATASOURCE_MASTER = "master";
 
+    /**
+     * SASA / CRM 等业务库(与 spring.datasource.dynamic.datasource.sales 的 key 一致)
+     */
+    public static final String DATASOURCE_SALES = "sales";
+
     /**
      * 数据源分组 oa库
      */

+ 1 - 1
java/storlead-system/storlead-system-biz/src/main/java/com/storlead/system/util/CloseUtil.java → java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/CloseUtil.java

@@ -1,4 +1,4 @@
-package com.storlead.system.util;
+package com.storlead.framework.common.util;
 
 import java.io.Closeable;
 

+ 12 - 2
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ConvertUtils.java

@@ -180,7 +180,8 @@ public class ConvertUtils {
 		Integer[] result = new Integer[len];
 		try {
 			for (int i = 0; i < len; i++) {
-				result[i] = new Integer(object[i].trim());
+				result[i] = Integer.valueOf(object[i].trim());
+				//new Integer(object[i].trim());
 			}
 			return result;
 		} catch (NumberFormatException e) {
@@ -231,7 +232,7 @@ public class ConvertUtils {
 	}
 
 	public static long stringToLong(String str) {
-		Long test = new Long(0);
+		Long test = Long.valueOf(0);
 		try {
 			test = Long.valueOf(str);
 		} catch (Exception e) {
@@ -334,6 +335,15 @@ public class ConvertUtils {
 
 	}
 
+	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;
+	}
+
 	/**
 	 * 判断元素是否在数组内
 	 *

+ 20 - 11
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtil.java

@@ -3,6 +3,7 @@ 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;
@@ -113,6 +114,13 @@ public class DateUtil {
         return c.getTime();
     }
 
+    public static Date getDateByOffset(LocalDateTime localDateTime, int calendarField, int offset) {
+        Date date = Timestamp.valueOf(localDateTime);
+        return getDateByOffset(date, calendarField, offset);
+    }
+
+
+
     /**
      * 描述:获取指定日期时间的字符串(可偏移).
      *
@@ -862,17 +870,18 @@ public class DateUtil {
         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);
+//        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);

+ 1 - 2
java/storlead-system/storlead-system-biz/src/main/java/com/storlead/system/util/FileUtil.java → java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/FileUtil.java

@@ -1,4 +1,4 @@
-package com.storlead.system.util;
+package com.storlead.framework.common.util;
 
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.IdUtil;
@@ -18,7 +18,6 @@ import java.security.MessageDigest;
 import java.text.DecimalFormat;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 
 /**
  * @program: sp-sales

+ 151 - 0
java/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);
+//        }
+    }
+}

+ 48 - 0
java/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;
+    };
+}

+ 372 - 0
java/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 "";
+        }
+    }
+}

+ 37 - 0
java/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;
+
+}

+ 23 - 0
java/storlead-framework/storlead-mybatis/pom.xml

@@ -38,6 +38,24 @@
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>
         </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>
+        <!-- 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>
 
         <!--mysql-->
         <dependency>
@@ -60,6 +78,11 @@
             <version>2.5.4</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 113 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceAspect.java

@@ -0,0 +1,113 @@
+package com.storlead.framework.mybatis.datasource;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Method;
+
+/**
+ * 子模块默认数据源:按配置包前缀 push 数据源;若目标已标注 {@link DS} 则跳过,由官方 {@code @DS} 切面处理。
+ */
+@Slf4j
+@Aspect
+@Component
+@Order(0)
+@RequiredArgsConstructor
+public class ModuleDefaultDataSourceAspect {
+
+    private final ModuleDefaultDataSourceProperties properties;
+
+    @Pointcut("execution(* *(..)) && (within(com.storlead..service..*) || within(com.storlead..mapper..*))")
+    public void moduleServiceOrMapperLayer() {
+    }
+
+    @Around("moduleServiceOrMapperLayer()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        if (!properties.isModuleDefaultEnabled()) {
+            return joinPoint.proceed();
+        }
+        if (hasDsAnnotation(joinPoint)) {
+            return joinPoint.proceed();
+        }
+        String datasource = resolveDatasource(joinPoint);
+        if (!StringUtils.hasText(datasource)) {
+            return joinPoint.proceed();
+        }
+        DynamicDataSourceContextHolder.push(datasource);
+        try {
+            if (log.isDebugEnabled()) {
+                log.debug("module default datasource [{}] -> {}", datasource, joinPoint.getSignature().toShortString());
+            }
+            return joinPoint.proceed();
+        } finally {
+            DynamicDataSourceContextHolder.poll();
+        }
+    }
+
+    private boolean hasDsAnnotation(ProceedingJoinPoint joinPoint) {
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        Class<?> declaringType = signature.getDeclaringType();
+        if (AnnotatedElementUtils.hasAnnotation(method, DS.class)) {
+            return true;
+        }
+        if (declaringType != null && AnnotatedElementUtils.hasAnnotation(declaringType, DS.class)) {
+            return true;
+        }
+        return AnnotatedElementUtils.hasAnnotation(joinPoint.getTarget().getClass(), DS.class);
+    }
+
+    /**
+     * 最长包前缀匹配
+     */
+    private String resolveDatasource(ProceedingJoinPoint joinPoint) {
+        String className = resolveBusinessClassName(joinPoint);
+        String matchedDs = null;
+        int matchedLen = -1;
+        for (ModuleDefaultDataSourceProperties.ModuleDefaultRule rule : properties.getModuleDefaults()) {
+            if (rule.getPackages() == null || !StringUtils.hasText(rule.getDatasource())) {
+                continue;
+            }
+            for (String pkg : rule.getPackages()) {
+                if (!StringUtils.hasText(pkg)) {
+                    continue;
+                }
+                if (matchesPackage(className, pkg.trim()) && pkg.length() > matchedLen) {
+                    matchedLen = pkg.length();
+                    matchedDs = rule.getDatasource();
+                }
+            }
+        }
+        return matchedDs;
+    }
+
+    private boolean matchesPackage(String className, String packagePrefix) {
+        return className.equals(packagePrefix)
+                || className.startsWith(packagePrefix + ".");
+    }
+
+    /**
+     * Mapper 为 JDK 代理时,用接口全限定名匹配包前缀。
+     */
+    private String resolveBusinessClassName(ProceedingJoinPoint joinPoint) {
+        if (joinPoint.getSignature() instanceof MethodSignature) {
+            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+            Class<?> declaringType = signature.getDeclaringType();
+            if (declaringType != null && declaringType.getName().startsWith("com.storlead.")) {
+                return declaringType.getName();
+            }
+        }
+        return joinPoint.getTarget().getClass().getName();
+    }
+}

+ 9 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceConfiguration.java

@@ -0,0 +1,9 @@
+package com.storlead.framework.mybatis.datasource;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties(ModuleDefaultDataSourceProperties.class)
+public class ModuleDefaultDataSourceConfiguration {
+}

+ 40 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceProperties.java

@@ -0,0 +1,40 @@
+package com.storlead.framework.mybatis.datasource;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 按业务包前缀绑定默认数据源;未命中或未配置时走 dynamic-datasource 的 primary。
+ * 与 {@link ModuleDefaultDataSourceAspect} 配合使用,方法/类上的 {@code @DS} 优先生效。
+ */
+@Data
+@ConfigurationProperties(prefix = "storlead.datasource")
+public class ModuleDefaultDataSourceProperties {
+
+    /**
+     * 是否启用模块默认数据源切面
+     */
+    private boolean moduleDefaultEnabled = true;
+
+    /**
+     * 模块默认数据源规则(最长包前缀匹配)
+     */
+    private List<ModuleDefaultRule> moduleDefaults = new ArrayList<>();
+
+    @Data
+    public static class ModuleDefaultRule {
+
+        /**
+         * 包前缀,如 com.storlead.sales.service
+         */
+        private List<String> packages = new ArrayList<>();
+
+        /**
+         * 数据源 key,与 spring.datasource.dynamic.datasource 下的名称一致
+         */
+        private String datasource;
+    }
+}

+ 7 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/query/QueryBaseEntity.java

@@ -48,4 +48,11 @@ public class QueryBaseEntity extends Page {
     @TableField(exist = false)
     @ApiModelProperty(value = "排序方式")
     private String sortMethod;
+
+    @TableField(exist = false)
+    @ApiModelProperty(value = "销售:归属分类(QueryRangeTypeEnum)(0全部客户、10:我的客户、20:我发起的、30:下属客户、40我协作的、50:下属协作的,70:重点)" +
+            "工资:" +
+            "目标")
+    private Integer queryRangeType;
+
 }

+ 42 - 0
java/storlead-mail/pom.xml

@@ -0,0 +1,42 @@
+<?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-saas-platform</artifactId>
+        <version>1.0</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-mail</artifactId>
+    <packaging>pom</packaging>
+    <name>storlead-mail</name>
+    <version>1.0</version>
+
+    <modules>
+        <module>storlead-mail-core</module>
+        <module>storlead-mail-spi</module>
+        <module>storlead-mail-biz</module>
+        <module>storlead-mail-api</module>
+    </modules>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.sun.mail</groupId>
+                <artifactId>javax.mail</artifactId>
+                <version>1.6.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>javax.mail</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 41 - 0
java/storlead-mail/storlead-mail-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</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-mail-api</artifactId>
+    <packaging>jar</packaging>
+    <name>storlead-mail-api</name>
+    <description>邮件域 Web:Controller 等。</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail-biz</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 19 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/ClientSentEmailsController.java

@@ -0,0 +1,19 @@
+package com.storlead.mail.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 邮件表 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-12-04
+ */
+@RestController
+@RequestMapping("/client-sent-emails-entity")
+public class ClientSentEmailsController {
+
+}

+ 170 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailFolderRuleApiController.java

@@ -0,0 +1,170 @@
+package com.storlead.mail.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.EmailFolderRuleEntity;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.enums.EmailFolderlRuleEnum;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.pojo.dto.FolderRuleDTO;
+import com.storlead.mail.service.EmailFolderRuleService;
+import com.storlead.mail.service.EmailsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+/**
+ * <p>
+ * 用户邮件文件夹出入规则 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+@RestController
+@Log4j2
+@RequestMapping("/email/folder/rule")
+@Api(tags = "邮件:邮件自定义入站规则")
+public class EmailFolderRuleApiController {
+
+    @Resource
+    private EmailFolderRuleService folderRuleService;
+
+    @Resource
+    private EmailsService emailsService;
+
+    @PostMapping(value = "/get_folder_rule")
+    @ApiOperation(value = "获取规则" )
+    public Result<?> getFolderRule() {
+        List<EmailFolderlRuleEnum>  ls = Arrays.asList(EmailFolderlRuleEnum.values());
+        List<Map>  enumls = new ArrayList<>();
+        for (EmailFolderlRuleEnum e : ls) {
+            Map<String,String> stringMap = new HashMap<>();
+            stringMap.put("code",e.getCode());
+            stringMap.put("desc",e.getDesc());
+            enumls.add(stringMap);
+        }
+        return Result.ok(enumls);
+    }
+
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取文件夹入站规则配置(分页)" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailFolderRuleEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailFolderRuleEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(EmailFolderRuleEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(EmailFolderRuleEntity::getIsDelete,Integer.valueOf(0));
+        IPage<EmailFolderRuleEntity> pageList = folderRuleService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取文件夹入站规则" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> list() {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        List<EmailFolderRuleEntity> list = folderRuleService.getUserFolderRules(currentUserId);
+        return Result.result(list);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存用户规则配置" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(EmailFolderRuleEntity entity) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        LambdaQueryWrapper<EmailFolderRuleEntity> w = new LambdaQueryWrapper<>();
+        w.eq(EmailFolderRuleEntity::getFolderRuleCode,entity.getFolderRuleCode());
+        w.eq(EmailFolderRuleEntity::getCompareContent,entity.getCompareContent());
+        w.eq(EmailFolderRuleEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        w.eq(EmailFolderRuleEntity::getOwnerBy,currentUserId);
+        if (Objects.isNull(entity.getId())) {
+            Integer exit = folderRuleService.count(w);
+            if (exit > 0) {
+                return Result.error("已存在同样的规则,不能新增同样的入站规则!");
+            }
+        } else {
+
+            EmailFolderRuleEntity oldFolder = folderRuleService.getOne(w);
+            if (Objects.nonNull(oldFolder)) {
+                if (!oldFolder.getId().equals(entity.getId())) {
+                    return Result.error("已存在同样的入站规则,无法修改!");
+                }
+            }
+        }
+
+        folderRuleService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/saveBatch")
+    @ApiOperation(value = "保存用户规则配置" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(@RequestBody FolderRuleDTO dto) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (CollectionUtils.isEmpty(dto.getFolderRules())) {
+            return Result.error("参数错误");
+        }
+        LambdaQueryWrapper<EmailFolderRuleEntity> deleteW = new LambdaQueryWrapper<>();
+        deleteW.eq(EmailFolderRuleEntity::getOwnerBy,currentUserId);
+        folderRuleService.remove(deleteW);
+        folderRuleService.saveOrUpdateBatch(dto.getFolderRules());
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        folderRuleService.lgDelete(Arrays.asList(ids));
+
+        LambdaUpdateWrapper<EmailsEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.in(EmailsEntity::getCustomFolderId,Arrays.asList(ids));
+        updateWrapper.set(EmailsEntity::getCustomFolderId,null);
+        emailsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<EmailFolderRuleEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(EmailFolderRuleEntity::getEnabled,enable);
+        updateWrapper.eq(EmailFolderRuleEntity::getId,id);
+        folderRuleService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 94 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailServiceChecker.java

@@ -0,0 +1,94 @@
+package com.storlead.mail.controller;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-08 17:46
+ */
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import java.util.Hashtable;
+
+public class EmailServiceChecker {
+    public static void main(String[] args) {
+//        String email = "chenkaiqiang@lcjs43.wecom.work";
+        String email = "fan@storlead.com";
+        String emailService = getEmailService(email);
+        System.out.println("邮箱服务提供商: " + emailService);
+    }
+
+    public static String getEmailService(String email) {
+        if (email == null || !email.contains("@")) {
+            return "";
+        }
+
+        String domain = email.substring(email.indexOf("@") + 1);
+
+        // 使用域名匹配常见邮箱服务提供商
+        switch (domain) {
+            case "gmail.com":
+                return "Google Gmail";
+            case "yahoo.com":
+            case "yahoo.co.jp":
+                return "yahoo.com";
+            case "outlook.com":
+                return "outlook.com";
+            case "hotmail.com":
+                return "hotmail.com";
+            case "qq.com":
+                return "qq.com";
+            case "exmail.qq.com":
+                return "exmail.qq.com";
+            case "163.com":
+                return "163.com";
+            case "storlead.com":
+                return "exmail.qq.com";
+            case "renice-tech.com":
+                return "exmail.qq.com";
+            // 添加其他常见的邮箱服务提供商...
+            default:
+                return checkMXRecords(domain);
+        }
+    }
+
+    public static String checkMXRecords(String domain) {
+        try {
+            Hashtable<String, String> env = new Hashtable<>();
+            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
+            DirContext ctx = new InitialDirContext(env);
+            Attributes attrs = ctx.getAttributes(domain, new String[]{"MX"});
+            Attribute attr = attrs.get("MX");
+
+            if (attr == null) {
+                return "";
+            }
+
+            for (int i = 0; i < attr.size(); i++) {
+                String mxRecord = (String) attr.get(i);
+                if (mxRecord.contains("google.com")) {
+                    return "gmail.com";
+                } else if (mxRecord.contains("outlook.com")) {
+                    return "outlook.com";
+                } else if (mxRecord.contains("hotmail.com")) {
+                    return "hotmail.com";
+                }  else if (mxRecord.contains("qq.com") && (mxRecord.contains("mx1") || mxRecord.contains("mx2"))) {
+                    return "qq.com";
+                } else if (mxRecord.contains("qq.com") && mxRecord.contains("mxbiz")) {
+                    return "exmail.qq.com";
+                } else if (mxRecord.contains("yahoo.com")) {
+                    return "yahoo.com";
+                }
+            }
+        } catch (NamingException e) {
+            e.printStackTrace();
+            return "";
+        }
+        return "";
+    }
+}

+ 155 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MaiAttachmentApiController.java

@@ -0,0 +1,155 @@
+package com.storlead.mail.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.storlead.framework.common.util.DateUtils;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.MailTempAttachmentEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.properties.MailFileProperties;
+import com.storlead.mail.service.MailTempAttachmentService;
+import com.storlead.mail.service.SmtpPopSettingsService;
+import com.storlead.mail.util.FileHelper;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.util.*;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-10 09:14
+ */
+@RestController
+@RequestMapping("/mail/file")
+@Api(tags = "020.邮件管理相关接口")
+@Log4j2
+public class MaiAttachmentApiController {
+
+    @Resource
+    private MailFileProperties mailFileProperties;
+    @Resource
+    private SmtpPopSettingsService smtpPopSettingsService;
+    @Resource
+    private MailTempAttachmentService tempAttachmentService;
+
+    @PostMapping(value = "/cache_mail_file")
+    @ApiOperation(value = "缓存需要上传的附件" )
+    public Result<?> cacheMailFile(@RequestParam("files") List<MultipartFile> files) {
+        Result result = new Result();
+
+        try {
+            List<JSONObject> res = new ArrayList<>();
+
+            for (MultipartFile file : files) {
+                Long currentUserId = LoginUserUtil.getCurrentUserId();
+                SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+                String recipient = DateUtils.date2Str(new Date(), DateUtils.yyyyMMdd);
+                String downloadDir = mailFileProperties.getPath().getPath() + File.separator + smtpPop.getEmailAddress() + File.separator + recipient + File.separator;
+                JSONObject jsonObject = new JSONObject();
+                Long fileId = tempAttachmentService.saveTempAttachment(file, downloadDir, null);
+                if (Objects.isNull(fileId)) {
+                    return Result.error("上传错误");
+                }
+                jsonObject.put("id", fileId);
+                jsonObject.put("fileShowName", file.getOriginalFilename());
+                jsonObject.put("fileName", FilenameUtils.getBaseName(file.getOriginalFilename()));
+                jsonObject.put("fileSize", file.getSize());
+                jsonObject.put("fileSizeName", FileHelper.formatFileSize(file.getSize()));
+                jsonObject.put("fileExt", FilenameUtils.getExtension(file.getOriginalFilename()));
+                res.add(jsonObject);
+            }
+            return Result.result(res);
+
+        } catch (Exception e) {
+            log.error("--upload -- error =", e);
+            return Result.error("上传错误");
+        }
+    }
+
+    @PostMapping(value = "/cache_image_file")
+    @ApiOperation(value = "缓存图片文件" )
+    public Map cacheImageFile(@RequestParam("upload") MultipartFile file) {
+        Map jsonObject = new HashMap();
+        try {
+            Long currentUserId = LoginUserUtil.getCurrentUserId();
+            String downloadDir = mailFileProperties.getPath().getJpeg();
+            MailTempAttachmentEntity tempAttachment = tempAttachmentService.saveTempImageAttachment(file, downloadDir);
+            if (Objects.isNull(tempAttachment) || Objects.isNull(tempAttachment.getId())) {
+                return jsonObject;
+            }
+            String fileName =  tempAttachment.getFileName()+"."+tempAttachment.getFileExt();
+            jsonObject.put("id", tempAttachment.getId());
+            jsonObject.put("fileName", fileName);
+            jsonObject.put("uploaded", 1);
+            jsonObject.put("url", "/sales/mail/file/images/"+fileName);
+            return jsonObject;
+        } catch (Exception e) {
+            log.error("--upload -- error =", e);
+            return jsonObject;
+        }
+    }
+
+    @PostMapping(value = "/download_mail_file")
+    @ApiOperation(value = "下载临时附件" )
+    public void downLoadMailFile(Long fileId, HttpServletResponse response) {
+        try {
+            MailTempAttachmentEntity resource = tempAttachmentService.getById(fileId);
+            String downloadDir = mailFileProperties.getPath().getPath() +resource.getFilePath();
+            InputStream inputStream = new BufferedInputStream(new FileInputStream(downloadDir));
+            response.reset();
+            response.setContentType("application/octet-stream;charset=utf-8");
+            /*
+             * 跨域配置
+             * */
+            response.addHeader("Access-Control-Allow-Origin", "*");
+            response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
+            response.addHeader("Access-Control-Allow-Headers", "Content-Type");
+
+            String filename = resource.getFileName()+"."+resource.getFileExt();
+            filename = filename.replace(" ","");
+            response.addHeader("content-disposition",
+                    "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")
+                            + ";filename*=utf-8''" + URLEncoder.encode(filename, "UTF-8"));
+
+            ServletOutputStream outputStream = response.getOutputStream();
+            byte[] b = new byte[1024];
+            int len;
+//从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
+            while ((len = inputStream.read(b)) > 0) {
+                outputStream.write(b, 0, len);
+            }
+            inputStream.close();
+            outputStream.close();
+
+        } catch (Exception e) {
+            log.error("--upload -- error =", e);
+        }
+    }
+
+    @GetMapping("/images/{imageName}")
+    public ResponseEntity<FileSystemResource> getImage(@PathVariable String imageName) {
+
+        String downloadDir = mailFileProperties.getPath().getJpeg();
+        FileSystemResource resource = new FileSystemResource(downloadDir + imageName);
+        return ResponseEntity.ok()
+                .contentType(MediaType.IMAGE_JPEG)  // 根据图片类型调整
+                .body(resource);
+    }
+}

+ 133 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailBlacklistRecordApiController.java

@@ -0,0 +1,133 @@
+package com.storlead.mail.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.EmailBlacklistRecordEntity;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.enums.EmailBoxEnum;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.service.EmailBlacklistRecordService;
+import com.storlead.mail.service.EmailsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-10-31 10:51
+ */
+@RestController
+@RequestMapping("/email/blacklist")
+@Api(tags = "邮件系统:黑名单")
+@Log4j2
+public class MailBlacklistRecordApiController {
+
+    @Resource
+    private EmailBlacklistRecordService blacklistRecordService;
+
+    @Resource
+    private EmailsService emailsService;
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailBlacklistRecordEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailBlacklistRecordEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(EmailBlacklistRecordEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(EmailBlacklistRecordEntity::getIsDelete,Integer.valueOf(0));
+        IPage<EmailBlacklistRecordEntity> pageList = blacklistRecordService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> list(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailBlacklistRecordEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailBlacklistRecordEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(EmailBlacklistRecordEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(EmailBlacklistRecordEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.eq(EmailBlacklistRecordEntity::getEnabled,dto.getEnabled());
+        IPage<EmailBlacklistRecordEntity> pageList = blacklistRecordService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(EmailBlacklistRecordEntity entity) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (StrUtil.isBlank(entity.getEmailAddress())) {
+            return Result.error("邮箱不能为空");
+        }
+        LambdaQueryWrapper<EmailBlacklistRecordEntity> wrapper =new LambdaQueryWrapper<>();
+        wrapper.eq(EmailBlacklistRecordEntity::getEmailAddress,entity.getEmailAddress());
+        wrapper.eq(EmailBlacklistRecordEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        wrapper.eq(EmailBlacklistRecordEntity::getOwnerBy,currentUserId);
+        wrapper.last(" limit 1");
+        EmailBlacklistRecordEntity record = blacklistRecordService.getOne(wrapper);
+        if (record != null){
+            return Result.error("该邮箱已在黑名单中已存在");
+        }
+        Boolean b = blacklistRecordService.saveOrUpdate(entity);
+        if (b) {
+            /** 将黑名单邮箱的邮件移到垃圾箱中 **/
+            LambdaUpdateWrapper<EmailsEntity> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(EmailsEntity::getOwnerBy,currentUserId);
+            updateWrapper.eq(EmailsEntity::getFolder, EmailBoxEnum.INBOX);
+            updateWrapper.eq(EmailsEntity::getFrom,entity.getEmailAddress());
+            updateWrapper.set(EmailsEntity::getIsTrash,1);
+            emailsService.update(updateWrapper);
+        }
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        blacklistRecordService.lgDelete(Arrays.asList(ids));
+        return Result.ok();
+    }
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<EmailBlacklistRecordEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(EmailBlacklistRecordEntity::getEnabled,enable);
+        updateWrapper.eq(EmailBlacklistRecordEntity::getId,id);
+        blacklistRecordService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 123 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailTemplatesApiController.java

@@ -0,0 +1,123 @@
+package com.storlead.mail.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.EmailTemplatesEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.service.EmailTemplatesService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-29 16:02
+ */
+@RestController
+@RequestMapping("/email/templates")
+@Api(tags = "邮件系统:邮件模板")
+@Log4j2
+public class MailTemplatesApiController {
+
+    @Resource
+    private EmailTemplatesService templatesService;
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailTemplatesEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailTemplatesEntity> queryWrapper = new LambdaQueryWrapper();
+        // queryWrapper.select(EmailTemplatesEntity::getId,EmailTemplatesEntity::getIsDelete,EmailTemplatesEntity::getTemplateTitle,EmailTemplatesEntity::getTemplateType,EmailTemplatesEntity::getCreateTime,EmailTemplatesEntity::getUpdateTime);
+        queryWrapper.eq(EmailTemplatesEntity::getOwnerBy,currentUserId);
+        if (Objects.nonNull(dto.getTemplateType())) {
+            queryWrapper.eq(EmailTemplatesEntity::getTemplateType,dto.getTemplateType());
+        }
+        queryWrapper.eq(EmailTemplatesEntity::getIsDelete,Integer.valueOf(0));
+        IPage<EmailTemplatesEntity> pageList = templatesService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/getById")
+    @ApiOperation(value = "获取邮件模板详情" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> getById(Long id) {
+        EmailTemplatesEntity templatesEntity = templatesService.getById(id);
+        return Result.result(templatesEntity);
+    }
+
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> list(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailTemplatesEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailTemplatesEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.select(EmailTemplatesEntity::getId,EmailTemplatesEntity::getIsDelete,EmailTemplatesEntity::getTemplateTitle,EmailTemplatesEntity::getTemplateType,EmailTemplatesEntity::getCreateTime,EmailTemplatesEntity::getUpdateTime);
+        queryWrapper.eq(EmailTemplatesEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(EmailTemplatesEntity::getIsDelete,Integer.valueOf(0));
+        if (Objects.nonNull(dto.getTemplateType())) {
+            queryWrapper.eq(EmailTemplatesEntity::getTemplateType,dto.getTemplateType());
+        }
+        queryWrapper.eq(EmailTemplatesEntity::getEnabled,dto.getEnabled());
+        IPage<EmailTemplatesEntity> pageList = templatesService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(EmailTemplatesEntity entity) {
+        templatesService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        templatesService.lgDelete(Arrays.asList(ids));
+        return Result.ok();
+    }
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<EmailTemplatesEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(EmailTemplatesEntity::getEnabled,enable);
+        updateWrapper.eq(EmailTemplatesEntity::getId,id);
+        templatesService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 136 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailboxAutoReplySetController.java

@@ -0,0 +1,136 @@
+package com.storlead.mail.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.MailboxAutoReplySetEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.service.MailboxAutoReplySetService;
+import com.storlead.mail.service.SmtpPopSettingsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 邮箱自动回复内容 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-02-24
+ */
+@RestController
+@RequestMapping("/mail/mailbox/reply")
+@Api(tags = "020.邮件管理相关接口")
+@Log4j2
+public class MailboxAutoReplySetController {
+
+    @Resource
+    private MailboxAutoReplySetService replySetService;
+
+    @Resource
+    private SmtpPopSettingsService popSettingsService;
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取邮件自动回复配置" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<MailboxAutoReplySetEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<MailboxAutoReplySetEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(MailboxAutoReplySetEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(MailboxAutoReplySetEntity::getIsDelete,Integer.valueOf(0));
+        IPage<MailboxAutoReplySetEntity> pageList = replySetService.page(page,queryWrapper);
+        if (!CollectionUtils.isEmpty(pageList.getRecords())) {
+            List<Long> popIds = pageList.getRecords().stream().map(MailboxAutoReplySetEntity::getSmtpPopId).collect(Collectors.toList());
+            List<SmtpPopSettingsEntity> smtpPopSettings = popSettingsService.list(new LambdaQueryWrapper<SmtpPopSettingsEntity>().in(SmtpPopSettingsEntity::getId, popIds));
+            if (!CollectionUtils.isEmpty(smtpPopSettings)) {
+                Map<Long,String> smtpPopMap =  smtpPopSettings.stream().collect(Collectors.toMap(SmtpPopSettingsEntity::getId,SmtpPopSettingsEntity::getEmailAddress, (existing, replacement) -> existing));
+                pageList.getRecords().forEach(e -> {
+                    e.setMailAddress(smtpPopMap.get(e.getSmtpPopId()));
+                });
+            }
+        }
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> list(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<MailboxAutoReplySetEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<MailboxAutoReplySetEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(MailboxAutoReplySetEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(MailboxAutoReplySetEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.eq(MailboxAutoReplySetEntity::getEnabled,dto.getEnabled());
+        IPage<MailboxAutoReplySetEntity> pageList = replySetService.page(page,queryWrapper);
+        if (!CollectionUtils.isEmpty(pageList.getRecords())) {
+           List<Long> popIds = pageList.getRecords().stream().map(MailboxAutoReplySetEntity::getSmtpPopId).collect(Collectors.toList());
+           List<SmtpPopSettingsEntity> smtpPopSettings = popSettingsService.list(new LambdaQueryWrapper<SmtpPopSettingsEntity>().in(SmtpPopSettingsEntity::getId, popIds));
+           if (!CollectionUtils.isEmpty(smtpPopSettings)) {
+              Map<Long,String> smtpPopMap =  smtpPopSettings.stream().collect(Collectors.toMap(SmtpPopSettingsEntity::getId,SmtpPopSettingsEntity::getEmailAddress, (existing, replacement) -> existing));
+               pageList.getRecords().forEach(e -> {
+                   e.setMailAddress(smtpPopMap.get(e.getSmtpPopId()));
+               });
+           }
+        }
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> save(MailboxAutoReplySetEntity entity) {
+        replySetService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        replySetService.lgDelete(Arrays.asList(ids));
+        return Result.ok();
+    }
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<MailboxAutoReplySetEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(MailboxAutoReplySetEntity::getEnabled,enable);
+        updateWrapper.eq(MailboxAutoReplySetEntity::getId,id);
+        replySetService.update(updateWrapper);
+        return Result.ok();
+    }
+
+}

+ 442 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopMailApiController.java

@@ -0,0 +1,442 @@
+package com.storlead.mail.controller;//package com.storlead.sales.mail;
+//
+//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+//import com.baomidou.mybatisplus.core.metadata.IPage;
+//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+//import com.storlead.framework.common.util.DateUtils;
+//import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
+//import com.storlead.framework.util.LoginUserUtil;
+//import com.storlead.framework.common.result.Result;
+//import com.storlead.mail.connection.MailConnection;
+//import com.storlead.mail.connection.client.MailConnectionUtil;
+//import com.storlead.mail.connection.config.MailProperties;
+//import com.storlead.mail.entity.EmailsEntity;
+//import com.storlead.mail.entity.MailAttachmentEntity;
+//import com.storlead.mail.entity.SmtpPopSettingsEntity;
+//import com.storlead.mail.pojo.MailDTO;
+//import com.storlead.mail.pojo.SendMailDTO;
+//import com.storlead.mail.properties.MailFileProperties;
+//import com.storlead.mail.service.EmailsService;
+//import com.storlead.mail.service.MailAttachmentService;
+//import com.storlead.mail.service.SmtpPopSettingsService;
+//import com.sun.mail.imap.IMAPFolder;
+//import io.swagger.annotations.Api;
+//import io.swagger.annotations.ApiOperation;
+//import io.swagger.annotations.ApiResponse;
+//import io.swagger.annotations.ApiResponses;
+//import lombok.extern.log4j.Log4j2;
+////import org.springframework.mail.javamail.JavaMailSender;
+//import org.springframework.mail.javamail.MimeMessageHelper;
+//import org.springframework.util.CollectionUtils;
+//import org.springframework.web.bind.annotation.*;
+//import org.springframework.web.multipart.MultipartFile;
+//
+//import javax.annotation.Resource;
+//import javax.mail.*;
+//import javax.mail.internet.InternetAddress;
+//import javax.mail.internet.MimeMessage;
+//import javax.mail.internet.MimeUtility;
+//import javax.mail.search.ComparisonTerm;
+//import javax.mail.search.ReceivedDateTerm;
+//import javax.mail.search.SearchTerm;
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.text.SimpleDateFormat;
+//import java.util.*;
+//import java.util.stream.Collectors;
+//
+///**
+// * @program: sp-sales-platform
+// * @description:
+// * @author: chenkq
+// * @create: 2024-05-29 16:02
+// */
+//@RestController
+//@RequestMapping("/mail")
+//@Api(tags = "020.邮件管理相关接口")
+//public class SmtpPopMailApiController {
+//
+////    @Resource
+////    private JavaMailSender mailSender;
+//
+//    @Resource
+//    private EmailsService emailsService;
+//
+//    @Resource
+//    private MailFileProperties mailFileProperties;
+//
+//    @Resource
+//    private MailAttachmentService mailAttachmentService;
+//
+//    @Resource
+//    private SmtpPopSettingsService smtpPopSettingsService;
+//
+//
+//    @PostMapping(value = "/pageList")
+//    @ApiOperation(value = "获取邮件" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> pageList(com.storlead.common.object.Page dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+//
+//        if (Objects.isNull(currentUserId) && Objects.nonNull(smtpPop)) {
+//            return Result.result(null);
+//        }
+//        Page<EmailsEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+//        LambdaQueryWrapper<EmailsEntity> queryWrapper = new LambdaQueryWrapper();
+//        queryWrapper.eq(EmailsEntity::getOwnerBy,currentUserId);
+//        queryWrapper.eq(EmailsEntity::getFrom,smtpPop.getEmailAddress());
+//        IPage<EmailsEntity> pageList = emailsService.page(page,queryWrapper);
+//        return Result.result(pageList);
+//    }
+//
+//    @PostMapping(value = "/sendMail")
+//    @ApiOperation(value = "发送邮件" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> sendMail(SendMailDTO dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+//        AccessKeyEncryptor accessKeyEncryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+//        String pass = accessKeyEncryptor.decrypt(smtpPop.getEmailPassword());
+//        smtpPop.setEmailPassword(pass);
+//        if (Objects.isNull(currentUserId) && Objects.nonNull(smtpPop)) {
+//            return Result.result(null);
+//        }
+//        try {
+//           Session session = getSendEmailsSession(smtpPop);
+//            try {
+//
+//                InternetAddress [] froms = new InternetAddress[dto.getRecipientls().size()];
+//                for (int i = 0; i < dto.getRecipientls().size(); i++) {
+//                    froms[i] = new InternetAddress(dto.getRecipientls().get(i));
+//                }
+//                if (CollectionUtils.isEmpty(dto.getFiles())) {
+//                    Message message = new MimeMessage(session);
+//                    message.setFrom(new InternetAddress(smtpPop.getEmailAddress()));
+//                    message.setRecipients(Message.RecipientType.TO, froms);
+//                    message.setSubject(dto.getSubject());
+//                    message.setText(dto.getContent());
+//                    Transport.send(message);
+//                } else {
+////                    MimeMessage message = mailSender.createMimeMessage();
+////                    MimeMessageHelper helper = new MimeMessageHelper(message, true);
+////                    helper.setFrom(new InternetAddress(smtpPop.getEmailAddress()));
+////                    helper.setTo(froms);
+////                    helper.setSubject(dto.getSubject());
+////                    helper.setText(dto.getContent());
+////                    List<MultipartFile> files = dto.getFiles();
+////                    for (MultipartFile multipartFile : files) {
+////                        File file = multipartFile.getResource().getFile();
+////                        helper.addAttachment(file.getName(), file);
+////                    }
+//                }
+//            }catch (Exception e) {
+//                log.error("getSendEmailsSession -error",e);
+//            }
+//            return Result.ok();
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//            log.error("send sendMail -error",e);
+//        }
+//        return Result.ok();
+//    }
+//
+//
+//    @PostMapping(value = "/receiveMail")
+//    @ApiOperation(value = "接收邮件" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> receiveMail(MailDTO dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+//        if (Objects.isNull(currentUserId) && Objects.nonNull(smtpPop)) {
+//            return Result.error("请先设置邮箱账号!");
+//        }
+//
+//        AccessKeyEncryptor accessKeyEncryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+//        String pass = accessKeyEncryptor.decrypt(smtpPop.getEmailPassword());
+//        smtpPop.setEmailPassword(pass);
+//        receiveEmails(smtpPop, "INBOX");
+////        receiveEmails(smtpPop, "Sent Items");
+////        receiveEmails(smtpPop, "Drafts");
+////        receiveEmails(smtpPop, "Trash");
+//        return Result.ok();
+//    }
+//
+//
+//    @PostMapping(value = "/receiveMail1")
+//    @ApiOperation(value = "接收邮件" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> receiveMail1(MailDTO dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+//        if (Objects.isNull(currentUserId) && Objects.nonNull(smtpPop)) {
+//            return Result.error("请先设置邮箱账号!");
+//        }
+//        return Result.ok();
+//    }
+//
+//    private void receiveEmails(SmtpPopSettingsEntity smtpPop, String folderName) {
+//        MailProperties properties = new MailProperties(smtpPop);
+//        MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+//        if (Objects.isNull(mailConnection)) {
+//            return;
+//        }
+//        List<EmailsEntity> entities = new ArrayList<>();
+//
+//        QueryWrapper queryWrapper = new QueryWrapper<>();
+//        queryWrapper.eq("smtp_pop_id",smtpPop.getId());
+//        queryWrapper.eq("folder",folderName);
+//        queryWrapper.select("max(recipient_date) as recipientDate");
+//        Map<String, Date> qv = emailsService.getMap(queryWrapper);
+//
+//        QueryWrapper queryMsgIdWp = new QueryWrapper<>();
+//        queryMsgIdWp.select("msg_uid");
+//        queryMsgIdWp.eq("folder",folderName);
+//        queryMsgIdWp.eq("smtp_pop_id",smtpPop.getId());
+//        queryMsgIdWp.last("order by msg_uid desc limit 100");
+//
+//        List<String> dis = emailsService.listObjs(queryMsgIdWp);
+//        List<Long> msgUids = new ArrayList<>();
+//        if (!CollectionUtils.isEmpty(dis)) {
+//            msgUids = dis.stream().map(Long::valueOf).collect(Collectors.toList());
+//        }
+//        try {
+//            Store store = mailConnection.getStore();
+//            Folder inbox = store.getFolder(folderName);
+//            inbox.open(Folder.READ_WRITE);
+//            Date beginDate = null;
+//            if (!CollectionUtils.isEmpty(qv) && Objects.nonNull(qv.get("recipientDate"))) {
+//                beginDate = qv.get("recipientDate");
+//            }
+//            if (beginDate == null) {
+//                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+//                String dateStr = "2024-03-28"; // 替换为你需要的日期
+//                beginDate = dateFormat.parse(dateStr);
+//            }
+//
+//            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+//            Date date = dateFormat.parse("2024-05-28");
+//
+//            SearchTerm searchTerm = new ReceivedDateTerm(ComparisonTerm.GT, date);
+//            Message[] messages = inbox.search(searchTerm);
+//            for (Message message : messages) {
+//                Long mesId = getMessageId(message,inbox);
+//                if (!CollectionUtils.isEmpty(msgUids) && msgUids.contains(mesId)) {
+//                    continue;
+//                }
+//                msgUids.add(mesId);
+//                EmailsEntity entity = convertMessageToEmailVo(message,smtpPop);
+//                entity.setSmtpPopId(smtpPop.getId());
+//                entity.setMsgUid(mesId);
+//                entity.setFolder(folderName);
+//                entities.add(entity);
+//                emailsService.save(entity);
+//
+//                String downloadDir = mailFileProperties.getPath().getPath()+File.separator+smtpPop.getEmailAddress()+File.separator;
+//                downloadAttachmentFromMessage(message,downloadDir,entity.getId());
+//            }
+//            inbox.close(false);
+//            mailConnection.close();
+//        }catch (Exception e) {
+//            log.error("receiveEmails - error =",e);
+//            mailConnection.close();
+//        }
+//
+//    }
+//
+//    private Session getSendEmailsSession(SmtpPopSettingsEntity smtpPop) {
+//
+//        Properties properties = new Properties();
+//        properties.put("mail.smtp.host", smtpPop.getSmtpServer());
+//        properties.put("mail.smtp.port", smtpPop.getSmtpPort());
+//        properties.put("mail.smtp.auth", "true");
+//        properties.put("mail.user", smtpPop.getEmailAddress());
+//        properties.put("mail.smtp.starttls.enable",smtpPop.getUseTls());
+//        properties.put("mail.smtp.ssl.enable", smtpPop.getUseSsl());
+//
+//        Session session = Session.getInstance(properties, new Authenticator() {
+//            protected PasswordAuthentication getPasswordAuthentication() {
+//                return new PasswordAuthentication(smtpPop.getEmailAddress(), smtpPop.getEmailPassword());
+//            }
+//        });
+//        return session;
+//    }
+//
+//        private static Long getMessageId(Message message, Folder folder) throws MessagingException {
+//        if (folder instanceof IMAPFolder) {
+//            IMAPFolder imapFolder = (IMAPFolder) folder;
+//            return  imapFolder.getUID(message);
+//        } else {
+//            return null;
+//        }
+//    }
+//
+//
+//    private EmailsEntity convertMessageToEmailVo(Message message,SmtpPopSettingsEntity smtpPop) throws MessagingException, IOException {
+//
+//        EmailsEntity entity = new EmailsEntity();
+//        String subject =  MimeUtility.decodeText(message.getSubject());
+//        entity.setSubject(subject);
+//
+//        Map<String,String>  fromMap = decodeAndPrintAddresses(message.getFrom());
+//        entity.setFrom(mapKeyToString(fromMap));
+//        entity.setFromName(mapValueToString(fromMap));
+//
+//        Map<String,String>  toMap = decodeAndPrintAddresses(message.getRecipients(Message.RecipientType.TO));
+//        entity.setRecipient(mapKeyToString(toMap));
+//        entity.setRecipientName(mapValueToString(toMap));
+//
+//        if (Objects.nonNull(message.getSentDate())) {
+//            entity.setSentDate(message.getSentDate());
+//        }
+//
+//        if (Objects.nonNull(message.getReceivedDate())) {
+//            entity.setRecipientDate(message.getReceivedDate());
+//        }
+//        boolean isSeen = message.isSet(Flags.Flag.SEEN);
+//        entity.setIsRead(isSeen);
+//
+////      Map<String,String>  ccMap = decodeAndPrintAddresses(message.getRecipients(Message.RecipientType.CC));
+////      Map<String,String>  bccMap = decodeAndPrintAddresses(message.getRecipients(Message.RecipientType.BCC));
+//        Object content = message.getContent();
+//        if (content instanceof String) {
+//            entity.setContent(message.getContent().toString());
+//        } else if (content instanceof Multipart) {
+//            Multipart multipart = (Multipart) content;
+//            String str = parseMultipart(multipart);
+//            entity.setContent(str);
+//        }
+//        return entity;
+//    }
+//
+//    private String parseMultipart(Multipart multipart) {
+//        try {
+//            StringBuilder stringBuilder = new StringBuilder();
+//            for (int i = 0; i < multipart.getCount(); i++) {
+//                BodyPart bodyPart = multipart.getBodyPart(i);
+//                if (bodyPart.isMimeType("text/plain")) {
+//                    stringBuilder.append(bodyPart.getContent());
+//                } else if (bodyPart.isMimeType("text/html")) {
+//                    stringBuilder.append(bodyPart.getContent());
+//                } else if (bodyPart.isMimeType("multipart/alternative")) {
+//                    parseMultipart((Multipart) bodyPart.getContent());
+//                } else if (bodyPart.isMimeType("multipart/*")) {
+//                    parseMultipart((Multipart) bodyPart.getContent());
+//                }
+//            }
+//            return stringBuilder.toString();
+//        }catch (Exception e) {
+//            return "";
+//        }
+//    }
+//
+//    private void downloadAttachmentFromMessage(Message message, String downloadDir, long emailId) {
+//        try {
+//            Object content = message.getContent();
+//            if (content instanceof Multipart) {
+//                Multipart multipart = (Multipart) content;
+//                for (int i = 0; i < multipart.getCount(); i++) {
+//                    BodyPart bodyPart = multipart.getBodyPart(i);
+//                    if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())) {
+//                        saveAttachment(bodyPart, downloadDir, emailId);
+//                    }
+//                }
+//            }
+//        }catch (Exception e) {
+//            log.error("downloadAttachmentFromMessage -- error = ",e);
+//        }
+//    }
+//
+//    private void saveAttachment(BodyPart bodyPart,  String downloadDir, long emailId) throws MessagingException, IOException {
+//        String fileName = bodyPart.getFileName();
+//        fileName = MimeUtility.decodeText(fileName);
+//        String path = DateUtils.date2Str(new Date(), DateUtils.date_yyyy_mm);
+//        // 如果不存在这创建路径
+//        downloadDir = downloadDir + File.separator + path;
+//        if (!new File(downloadDir).exists()) {
+//            new File(downloadDir).mkdirs();
+//        }
+//        File file = new File(downloadDir +  File.separator +fileName);
+//        try (FileOutputStream output = new FileOutputStream(file)) {
+//            bodyPart.getInputStream().transferTo(output);
+//        }
+//        System.out.println("Saved attachment: " + file.getAbsolutePath());
+//        saveAttachmentToDatabase(file, emailId);
+//    }
+//
+//    private void saveAttachmentToDatabase(File file, long emailId) {
+//
+//        String fileName = file.getName();
+//        MailAttachmentEntity attachment = new MailAttachmentEntity();
+//        attachment.setEmailId(emailId);
+//        attachment.setFileName(fileName);
+//        attachment.setFilePath(file.getAbsolutePath());
+//        attachment.setFilePath(file.getAbsolutePath());
+//
+//        String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1);
+//        attachment.setFileExt(fileExtension);
+//        attachment.setFileSize(file.length());
+//        mailAttachmentService.save(attachment);
+//    }
+//
+//    private static String formatDate(Date date) {
+//        if (date == null) return "N/A";
+//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+//        return sdf.format(date);
+//    }
+//
+//    private static String decodeAddresses(Address [] addresses) throws MessagingException {
+//        try {
+//            if (addresses == null) return "[]";
+//            String[] decodedAddresses = new String[addresses.length];
+//            for (int i = 0; i < addresses.length; i++) {
+//                decodedAddresses[i] = MimeUtility.decodeText(addresses[i].toString());
+//            }
+//            return Arrays.toString(decodedAddresses);
+//        }catch (Exception e) {
+//            return "";
+//        }
+//    }
+//
+//    private static Map<String,String> decodeAndPrintAddresses(Address[] addresses) throws MessagingException, IOException {
+//
+//        Map<String,String> map = new HashMap<>();
+//        if (addresses == null) return new HashMap<>();
+//        StringBuilder decodedAddresses = new StringBuilder();
+//        for (Address address : addresses) {
+//            InternetAddress internetAddress = (InternetAddress) address;
+//            String personal = internetAddress.getPersonal();
+//            String email = internetAddress.getAddress();
+//            if (personal != null) {
+//                personal = MimeUtility.decodeText(personal);
+//            }
+//            map.put(email,(personal != null ? personal : "N/A"));
+////            decodedAddresses.append("[Name: ").append(personal != null ? personal : "N/A").append(", Email: ").append(email).append("] ");
+//        }
+//        return map;
+//    }
+//
+//    private static String mapKeyToString(Map<String,String> map) {
+//        if (map == null) return "";
+//        return map.entrySet().stream()
+//                .map(entry -> entry.getKey())
+//                .collect(Collectors.joining(","));
+//    }
+//
+//    private static String mapValueToString(Map<String,String> map) {
+//        if (map == null) return "";
+//        return map.entrySet().stream()
+//                .map(entry -> entry.getValue())
+//                .collect(Collectors.joining(","));
+//    }
+//}

+ 457 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopSettingsApiController.java

@@ -0,0 +1,457 @@
+package com.storlead.mail.controller;
+
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.connection.MailConnection;
+import com.storlead.mail.connection.client.MailConnectionUtil;
+import com.storlead.mail.connection.config.MailProperties;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.pojo.entity.MailboxAutoReplySetEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.enums.EmailBoxEnum;
+import com.storlead.mail.pojo.vo.MailAcountVO;
+import com.storlead.mail.service.EmailsService;
+import com.storlead.mail.service.SmtpPopSettingsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.mail.*;
+import java.util.*;
+
+/**
+ * <p>
+ * 邮箱smtp_setting 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+@Log4j2
+@RestController
+@RequestMapping("/smtp/pop/setting")
+@Api(tags = "020.邮件管理相关接口")
+public class SmtpPopSettingsApiController {
+
+    @Resource
+    private SmtpPopSettingsService smtpPopSettingsService;
+
+    @Resource
+    private EmailsService emailsService;
+
+
+    @PostMapping(value = "/pageList")
+    @ApiOperation(value = "获取我的邮箱配置信息" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(com.storlead.framework.mybatis.page.Page dto) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (Objects.isNull(currentUserId)) {
+            return Result.result(null);
+        }
+        AccessKeyEncryptor encryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+        Page<SmtpPopSettingsEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<SmtpPopSettingsEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(SmtpPopSettingsEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+        IPage<SmtpPopSettingsEntity> pageList =  smtpPopSettingsService.page(page,queryWrapper);
+        if (!CollectionUtils.isEmpty(pageList.getRecords())) {
+            pageList.getRecords().forEach(e -> {
+                e.setEmailPassword(encryptor.decrypt(e.getEmailPassword()));
+            });
+        }
+        return Result.result(pageList);
+    }
+
+//    @PostMapping(value = "/listAutoReplySettings")
+//    @ApiOperation(value = "获取我的邮箱配置信息" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> listAutoReplySettings(com.storlead.common.object.Page dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        if (Objects.isNull(currentUserId)) {
+//            return Result.result(null);
+//        }
+//        AccessKeyEncryptor encryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+//        Page<SmtpPopSettingsEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+//        LambdaQueryWrapper<SmtpPopSettingsEntity> queryWrapper = new LambdaQueryWrapper();
+//        queryWrapper.eq(SmtpPopSettingsEntity::getIsDelete,Integer.valueOf(0));
+//        queryWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+//        IPage<SmtpPopSettingsEntity> pageList =  smtpPopSettingsService.page(page,queryWrapper);
+//        if (!CollectionUtils.isEmpty(pageList.getRecords())) {
+//            pageList.getRecords().forEach(e -> {
+//                e.setEmailPassword(encryptor.decrypt(e.getEmailPassword()));
+//            });
+//        }
+//        return Result.result(pageList);
+//    }
+    @PostMapping(value = "/setReplyContent")
+    @ApiOperation(value = "设置自动回复内容,并且开始" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> setReplyContent(Long id,String replyContent,String replyTitle,Integer autoReplyStatus) {
+        if (Objects.isNull(id) || Objects.isNull(autoReplyStatus)) {
+            return Result.error("参数错误");
+        }
+        if (Integer.valueOf(1).equals(autoReplyStatus)) {
+            if(StrUtil.isBlank(replyContent) || StrUtil.isBlank(replyTitle)) {
+                return Result.error("参数错误");
+            }
+        }
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.set(SmtpPopSettingsEntity::getReplyContent,replyContent);
+        updateWrapper.set(SmtpPopSettingsEntity::getReplyTitle,replyTitle);
+        updateWrapper.set(SmtpPopSettingsEntity::getReplyOnTime,new Date());
+        updateWrapper.set(SmtpPopSettingsEntity::getIsAutoReply,autoReplyStatus);
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        smtpPopSettingsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/setAutoReplyStatus")
+    @ApiOperation(value = "设置自动回复是否开启:1:开启,0:关闭" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> setAutoReplyStatus(Long id,Integer autoReplyStatus) {
+        if (Objects.isNull(id) || Objects.isNull(autoReplyStatus)) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.set(SmtpPopSettingsEntity::getIsAutoReply,autoReplyStatus);
+        updateWrapper.set(SmtpPopSettingsEntity::getReplyOnTime,new Date());
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        smtpPopSettingsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+//    @PostMapping(value = "/list")
+//    @ApiOperation(value = "获取我的邮箱配置信息" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> list(com.storlead.common.object.Page dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        if (Objects.isNull(currentUserId)) {
+//            return Result.result(null);
+//        }
+//        Page<SmtpPopSettingsEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+//        LambdaQueryWrapper<SmtpPopSettingsEntity> queryWrapper = new LambdaQueryWrapper();
+//        queryWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+//        IPage<SmtpPopSettingsEntity> pageList =  smtpPopSettingsService.page(page,queryWrapper);
+//        return Result.result(pageList);
+//    }
+
+    @PostMapping(value = "/delete")
+    @ApiOperation(value = "删除" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> delete(Long id) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (Objects.isNull(currentUserId)) {
+            return Result.result(null);
+        }
+        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getById(id);
+        if (Objects.isNull(smtpPop)) {
+            return Result.error("参数错误");
+        }
+
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(SmtpPopSettingsEntity::getIsDelete,Integer.valueOf(1));
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        smtpPopSettingsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存或修改" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = Result.class)
+    })
+    public Result<?> save(@RequestBody SmtpPopSettingsEntity entity) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (Objects.isNull(currentUserId)) {
+            return Result.result(null);
+        }
+        /**
+         * 保存邮箱先校验
+         */
+        if ("POP3".equals(entity.getProtocolType())) {
+            if (!testPop3(entity)) {
+                MailProperties properties = new MailProperties(entity);
+                MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+                if (Objects.isNull(mailConnection)) {
+                    return Result.error("邮箱校验失败,请输入正确的账号密码");
+                }
+                mailConnection.close();
+            }
+        }else if("IMAP".equals(entity.getProtocolType())) {
+            MailProperties properties = new MailProperties(entity);
+            MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+            if (!testImap(entity)) {
+                return Result.error("邮箱校验失败,请输入正确的账号密码");
+            }
+            mailConnection.close();
+        } else {
+            return Result.error("邮箱校验失败,请输入正确的账号密码");
+        }
+        if (!testSmtp(entity)) {
+            return Result.error("邮箱校验失败,请输入正确的账号密码");
+        }
+        /**
+         * 修改和增加根据邮箱进行查重
+         */
+        AccessKeyEncryptor encryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+        entity.setEmailPassword(encryptor.encrypt(entity.getEmailPassword()));
+        if (!Integer.valueOf(1).equals(entity.getUseDefault())) {
+            LambdaQueryWrapper<SmtpPopSettingsEntity> queryWrapper = new LambdaQueryWrapper();
+            queryWrapper.eq(SmtpPopSettingsEntity::getUseDefault, Integer.valueOf(1));
+            queryWrapper.eq(SmtpPopSettingsEntity::getOwnerBy, currentUserId);
+            queryWrapper.eq(SmtpPopSettingsEntity::getIsDelete, Integer.valueOf(0));
+            queryWrapper.last("limit 1");
+            SmtpPopSettingsEntity c = smtpPopSettingsService.getOne(queryWrapper);
+            if (Objects.isNull(c)) {
+                return Result.error("请设置默认邮箱");
+            }
+            if (Objects.nonNull(c) && c.getId().equals(entity.getId())) {
+                return Result.error("请设置默认邮箱");
+            }
+        }
+
+        if (Objects.nonNull(entity.getId())) {
+            // 修改
+            SmtpPopSettingsEntity old = smtpPopSettingsService.getById(entity.getId());
+            if (Objects.nonNull(old)) {
+                if(!old.getEmailAddress().equals(entity.getEmailAddress())){
+                    return Result.error("已绑定的邮箱无法修改,可新增邮箱配置!");
+                }
+            }
+        }
+        Boolean b = smtpPopSettingsService.saveOrUpdate(entity);
+        if (Integer.valueOf(1).equals(entity.getUseDefault()) && b) {
+            // 如果设置了默认邮箱,则将所有默认邮箱改为非默认
+            LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+            updateWrapper.set(SmtpPopSettingsEntity::getUseDefault,Integer.valueOf(0));
+            updateWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+            updateWrapper.ne(SmtpPopSettingsEntity::getId,entity.getId());
+            smtpPopSettingsService.update(updateWrapper);
+        }
+
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/defaultUse")
+    @ApiOperation(value = "设置默认邮箱" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = Result.class)
+    })
+    public Result<?> defaultUse(Long id) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (Objects.isNull(currentUserId) || Objects.isNull(id)) {
+            return Result.result(null);
+        }
+
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.set(SmtpPopSettingsEntity::getUseDefault,Integer.valueOf(0));
+        updateWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+        smtpPopSettingsService.update(updateWrapper);
+
+        updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        updateWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+        updateWrapper.set(SmtpPopSettingsEntity::getUseDefault,Integer.valueOf(1));
+        smtpPopSettingsService.update(updateWrapper);
+        /**
+         * 需要当前默认邮箱的邮件和未读数
+         */
+        return Result.ok();
+    }
+
+
+    @PostMapping(value = "/isEnabledDelete")
+    @ApiOperation(value = "是否开启删除服务器邮件;0 不开启,1:开启" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> setReplyContent(Long id,Integer isEnabled) {
+        if (Objects.isNull(id) || Objects.isNull(isEnabled)) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.set(SmtpPopSettingsEntity::getIsDeleteMail,isEnabled);
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        smtpPopSettingsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+
+    @PostMapping(value = "/personal_mail_account")
+    @ApiOperation(value = "获取个人邮箱账户" )
+    public Result personalMailAccount() {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        List<SmtpPopSettingsEntity> emails =  smtpPopSettingsService.getEmailAccount(currentUserId);
+        QueryWrapper<EmailsEntity> queryWrapper = new QueryWrapper<EmailsEntity>();
+        queryWrapper.select("smtp_pop_id", "COUNT(*) AS email_count");
+        queryWrapper.eq("owner_by", currentUserId);
+        queryWrapper.eq("folder", EmailBoxEnum.INBOX.code);
+        queryWrapper.eq("is_trash", Integer.valueOf(0));
+        queryWrapper.eq("is_read", Integer.valueOf(0));
+        queryWrapper.eq("is_delete", Integer.valueOf(0));
+        queryWrapper.isNotNull("smtp_pop_id");
+        queryWrapper.ne("`from`", "system@storlead.com");
+        queryWrapper.groupBy("smtp_pop_id");
+        List<Map<String,Object>> readMapls = emailsService.listMaps(queryWrapper);
+        Map<Long, Integer> readMap = new HashMap<>();
+        if (!CollectionUtils.isEmpty(readMapls)) {
+            for (Map<String, Object> map : readMapls) {
+                Long key = Long.valueOf(map.get("smtp_pop_id").toString());
+                Integer val = Integer.valueOf(map.get("email_count").toString());
+                readMap.put(key, val);
+            }
+        }
+
+        List<MailAcountVO> mails = new ArrayList<>();
+        if (CollectionUtil.isNotEmpty(emails)) {
+            emails.forEach(e -> {
+                MailAcountVO mailvo = new MailAcountVO();
+                mailvo.setMailAccount(e.getEmailAddress());
+                mailvo.setId(e.getId());
+                mailvo.setUseDefault(e.getUseDefault());
+                Integer readNum = readMap.get(e.getId());
+                if (Objects.nonNull(readNum)) {
+                    mailvo.setUnReadCount(readNum);
+                } else {
+                    mailvo.setUnReadCount(0);
+                }
+                mails.add(mailvo);
+            });
+        }
+        return Result.result(mails);
+    }
+
+    @PostMapping(value = "/test_connect")
+    @ApiOperation(value = "测试" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> receiveMail(@RequestBody SmtpPopSettingsEntity mailSet) {
+
+        if (!testSmtp(mailSet)) {
+            return Result.error("邮箱校验失败,请输入正确的账号密码");
+        }
+        if ("POP3".equals(mailSet.getProtocolType())) {
+            if (!testPop3(mailSet)) {
+                return Result.error("邮箱校验失败,请输入正确的账号密码");
+            }
+        }else if("IMAP".equals(mailSet.getProtocolType())) {
+            if (!testImap(mailSet)) {
+                return Result.error("邮箱校验失败,请输入正确的账号密码");
+            }
+        } else {
+            return Result.error("邮箱校验失败,请输入正确的账号密码");
+        }
+        return Result.ok();
+    }
+
+
+
+    public Boolean testImap(SmtpPopSettingsEntity mailSet){
+        try {
+            MailProperties properties = new MailProperties(mailSet);
+            MailConnection mailConnection = MailConnectionUtil.testReceiveEmailsConnection(properties);
+            if (Objects.isNull(mailConnection)) {
+                return false;
+            }
+            log.error("mailConnection -- testImap == "+mailConnection);
+//            String host = mailSet.getReceiveServer();
+//            String port = mailSet.getReceivePort();
+//            String username = mailSet.getEmailAddress();
+//            String password = mailSet.getEmailPassword();;
+//
+//            Properties properties = new Properties();
+//            properties.put("mail.imap.host", host);
+//            properties.put("mail.imap.port", port);
+//            properties.put("mail.imap.ssl.enable",Integer.valueOf(1).equals(mailSet.getReceiveSsl()) ? "true" : "false");
+//            Session session = Session.getDefaultInstance(properties);
+//            Store store = session.getStore("imap");
+//            store.connect(username, password);
+//            store.close();
+//            session = null;
+            mailConnection.close();
+
+            return true;
+        } catch (Exception e) {
+            log.error("testImap - error",e);
+            return false;
+        }
+    }
+
+    public Boolean testPop3(SmtpPopSettingsEntity mailSet){
+        try {
+            MailProperties properties = new MailProperties(mailSet);
+            MailConnection mailConnection = MailConnectionUtil.testReceiveEmailsConnection(properties);
+            if (Objects.isNull(mailConnection)) {
+                return false;
+            }
+            log.error("mailConnection -- testPop3 == "+mailConnection);
+            mailConnection.close();
+
+            return true;
+        } catch (Exception e) {
+            log.error("testPop3 - error",e);
+            return false;
+        }
+    }
+
+    public Boolean testSmtp(SmtpPopSettingsEntity smtpPop){
+        try {
+            Properties properties = new Properties();
+            properties.put("mail.smtp.host", smtpPop.getSmtpServer());
+            properties.put("mail.smtp.port", smtpPop.getSmtpPort());
+            properties.put("mail.smtp.auth", "true");
+            properties.put("mail.user", smtpPop.getEmailAddress());
+            properties.put("mail.smtp.starttls.enable",Integer.valueOf(1).equals(smtpPop.getSmtpTls()) ? "true" : "false");
+            properties.put("mail.smtp.ssl.enable", Integer.valueOf(1).equals(smtpPop.getSmtpSsl()) ? "true" : "false");
+
+            String username = smtpPop.getEmailAddress();
+            String password = smtpPop.getEmailPassword();
+
+            Session session = Session.getInstance(properties, new Authenticator() {
+                protected PasswordAuthentication getPasswordAuthentication() {
+                    return new PasswordAuthentication(username, password);
+                }
+            });
+            Transport transport = session.getTransport("smtp");
+            transport.connect();
+            transport.close();
+            return true;
+        } catch (Exception e) {
+            log.error("testSmtp - error",e);
+            return false;
+        }
+    }
+
+}

+ 135 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/UserEmailFolderApiController.java

@@ -0,0 +1,135 @@
+package com.storlead.mail.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.pojo.entity.UserEmailFolderEntity;
+import com.storlead.mail.service.EmailsService;
+import com.storlead.mail.service.UserEmailFolderService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * <p>
+ * 用户文件夹 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+@RestController
+@RequestMapping("/user/email/folder")
+@Api(tags = "邮件:邮件自定义文件夹")
+public class UserEmailFolderApiController {
+    @Resource
+    private UserEmailFolderService emailFolderService;
+
+    @Resource
+    private EmailsService emailsService;
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取用户文件夹(分页)" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<UserEmailFolderEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<UserEmailFolderEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(UserEmailFolderEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(UserEmailFolderEntity::getIsDelete,Integer.valueOf(0));
+        IPage<UserEmailFolderEntity> pageList = emailFolderService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取用户文件夹" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> list() {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        LambdaQueryWrapper<UserEmailFolderEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(UserEmailFolderEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(UserEmailFolderEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.eq(UserEmailFolderEntity::getEnabled,Integer.valueOf(1));
+        List<UserEmailFolderEntity> ls = emailFolderService.list(queryWrapper);
+        return Result.result(ls);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "绑定文件夹入站规则" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(UserEmailFolderEntity entity) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        LambdaQueryWrapper<UserEmailFolderEntity> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserEmailFolderEntity::getFolderName,entity.getFolderName());
+        queryWrapper.eq(UserEmailFolderEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        queryWrapper.eq(UserEmailFolderEntity::getOwnerBy,currentUserId);
+
+        if (Objects.isNull(entity.getId())) {
+            Integer exit = emailFolderService.count(queryWrapper);
+            if (exit > 0) {
+                return Result.error("文件夹'"+ entity.getFolderName() +"'已存在!");
+            }
+        } else {
+            UserEmailFolderEntity oldFolder = emailFolderService.getOne(queryWrapper);
+            if (Objects.nonNull(oldFolder)) {
+                if (!oldFolder.getId().equals(entity.getId())) {
+                    return Result.error("文件夹'"+ entity.getFolderName() +"'已存在,无法修改!");
+                }
+            }
+        }
+        emailFolderService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        /**
+         * 需要修改邮件文件夹标记为''
+         */
+        emailFolderService.lgDelete(Arrays.asList(ids));
+
+        LambdaUpdateWrapper<EmailsEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.in(EmailsEntity::getCustomFolderId,Arrays.asList(ids));
+        updateWrapper.set(EmailsEntity::getCustomFolderId,null);
+        emailsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<UserEmailFolderEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(UserEmailFolderEntity::getEnabled,enable);
+        updateWrapper.eq(UserEmailFolderEntity::getId,id);
+        emailFolderService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 46 - 0
java/storlead-mail/storlead-mail-biz/pom.xml

@@ -0,0 +1,46 @@
+<?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</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-mail-biz</artifactId>
+    <packaging>jar</packaging>
+    <name>storlead-mail-biz</name>
+    <description>邮件域实现:Mapper、Service 实现、协议连接与工具。</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mybatis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>javax.mail</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+            <version>1.21</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 157 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailReceiver.java

@@ -0,0 +1,157 @@
+package com.storlead.mail;
+
+import com.sun.mail.imap.DefaultFolder;
+import com.sun.mail.imap.IMAPFolder;
+import com.sun.mail.imap.IMAPStore;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.*;
+import javax.mail.search.*;
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-24 14:42
+ */
+@Log4j2
+public class QQEmailReceiver {
+
+
+    private static final String UID_FILE_PATH = "uids.txt"; // 存储UID的文件路径
+
+    public static void main(String[] args) {
+        String host = "imap.exmail.qq.com"; // QQ邮箱IMAP服务器地址
+        String port = "993"; // QQ邮箱IMAP服务器端口
+        String userName = "chenkaiqiang@lcjs43.wecom.work"; // 你的QQ邮箱地址
+        String password = "-Chenkq1992"; // 你的QQ邮箱授权码
+        //
+        // pnkxmvlldlrmidif
+        Properties properties = new Properties();
+        properties.put("mail.imap.host", host);
+        properties.put("mail.imap.port", port);
+        properties.put("mail.imap.starttls.enable", "true");
+        properties.put("mail.imap.ssl.trust", "*");
+        properties.put("mail.imap.ssl.enable", "true");
+
+        Authenticator auth = new Authenticator() {
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(userName, password);
+            }
+        };
+
+        Session session = Session.getInstance(properties,auth);
+        try {
+            IMAPStore store =  (IMAPStore) session.getStore("imap");
+            store.connect();
+//            Folder inbox = store.getFolder("Sent Items");
+            IMAPFolder inbox = (IMAPFolder) store.getFolder("INBOX");
+//            Folder inbox = store.getFolder("INBOX");
+            inbox.open(Folder.READ_WRITE);
+            long lastUID = -1;
+            // 加载已处理的UID
+//            if (lastUID == -1) {
+//                // 首次获取所有邮件
+//                 dateStr = "2001-05-28";
+//            } else {
+//                 dateStr = "2024-05-28"; // 替换为你需要的日期
+//            }
+//            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+//            Date date = dateFormat.parse(dateStr);
+//            SearchTerm searchTerm = new ReceivedDateTerm(ComparisonTerm.GT, date);
+            //<tencent_49C3E81C77FD0CAB1F16CA6F@qq.com>
+//            SearchTerm searchTerm = new HeaderTerm("Message-ID","<tencent_49C3E81C77FD0CAB1F16CA6F@qq.com>");
+
+            SearchTerm searchTerm = new HeaderTerm("Message-ID","<tencent_573CB96038E4DCF91E167FA6@qq.com>");
+            SearchTerm searchTerm1 = new MessageIDTerm("<tencent_573CB96038E4DCF91E167FA6@qq.com>");
+            long start = System.currentTimeMillis();
+
+            Message [] megs = inbox.getMessages();
+            Message [] messages0 = inbox.search(searchTerm);
+            Message [] messages1 = inbox.search(searchTerm,megs);
+            Message [] messages2 = inbox.search(searchTerm1,megs);
+            System.out.println("----耗时"+(System.currentTimeMillis() - start));
+            long maxUID = lastUID;
+            for (Message message : megs) {
+                long uid = inbox.getUID(message);
+                String msguid = message.getHeader("Message-ID")[0];
+                if (message.match(searchTerm1)) {
+                    log.error("找到了 :"+msguid);
+                }
+                log.error("Message-UID = :"+uid);
+                log.error("Message-subject = :"+message.getSubject());
+                log.error("Message-ID = :"+msguid);
+            }
+            inbox.close(false);
+            store.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static Set<Long> loadProcessedUIDs() {
+        Set<Long> uids = new HashSet<>();
+        try (BufferedReader reader = new BufferedReader(new FileReader(UID_FILE_PATH))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                uids.add(Long.parseLong(line));
+            }
+        } catch (IOException e) {
+            // 文件不存在或读取错误时返回空集合
+        }
+        return uids;
+    }
+
+    // 保存已处理的UID
+    private static void saveProcessedUIDs(Set<Long> uids) {
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(UID_FILE_PATH))) {
+            for (Long uid : uids) {
+                writer.write(uid.toString());
+                writer.newLine();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static void saveAttachment(BodyPart bodyPart) throws Exception {
+        String fileName = bodyPart.getFileName();
+        int size = bodyPart.getSize();
+        InputStream is = bodyPart.getInputStream();
+//        File file = new File("attachments/" + fileName); // 存储附件的文件夹路径
+//        try (FileOutputStream fos = new FileOutputStream(file)) {
+//            byte[] buffer = new byte[4096];
+//            int bytesRead;
+//            while ((bytesRead = is.read(buffer)) != -1) {
+//                fos.write(buffer, 0, bytesRead);
+//            }
+//        }
+        System.out.println("Saved attachment: " + fileName);
+    }
+
+
+    private static String parseMultipart(Multipart multipart) throws Exception {
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 0; i < multipart.getCount(); i++) {
+            BodyPart bodyPart = multipart.getBodyPart(i);
+            if (bodyPart.isMimeType("text/plain")) {
+                stringBuilder.append(bodyPart.getContent());
+            } else if (bodyPart.isMimeType("text/html")) {
+                stringBuilder.append(bodyPart.getContent());
+            } else if (bodyPart.isMimeType("multipart/alternative")) {
+                parseMultipart((Multipart) bodyPart.getContent());
+            } else if (bodyPart.isMimeType("multipart/*")) {
+                parseMultipart((Multipart) bodyPart.getContent());
+            }else if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())) {
+                saveAttachment(bodyPart);
+            }
+        }
+        return stringBuilder.toString();
+    }
+}

+ 53 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailSender.java

@@ -0,0 +1,53 @@
+package com.storlead.mail;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-24 17:08
+ */
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import java.util.Properties;
+
+public class QQEmailSender {
+
+
+    public static void main(String[] args) {
+        String host = "smtp.exmail.qq.com"; // QQ邮箱SMTP服务器地址
+        String port = "465"; // QQ邮箱SMTP服务器端口
+        String userName = "chenkaiqiang@lcjs43.wecom.work"; // 你的QQ邮箱地址
+//        String password = "-Chenkq1992";  // 你的QQ邮箱授权码
+        String password = "-Chenkq1992";  // 你的QQ邮箱授权码
+
+        Properties properties = new Properties();
+        properties.put("mail.smtp.host", host);
+        properties.put("mail.smtp.port", port);
+        properties.put("mail.smtp.auth", "true");
+        properties.put("mail.user", userName);
+        properties.put("mail.smtp.starttls.enable", "true");
+        properties.put("mail.smtp.ssl.enable", "true");
+
+        Authenticator auth = new Authenticator() {
+            public PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(userName, password);
+            }
+        };
+
+        Session session = Session.getInstance(properties, auth);
+
+        try {
+            Message message = new MimeMessage(session);
+            message.setFrom(new InternetAddress(userName));
+            message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("1217276730@qq.com"));
+            message.setSubject("Test Subject");
+            message.setText("Hello, this is a test email!");
+            Transport.send(message);
+            System.out.println("Email sent successfully!");
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        }
+    }
+}
+

+ 129 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnection.java

@@ -0,0 +1,129 @@
+package com.storlead.mail.connection;
+
+import com.sun.mail.imap.IMAPFolder;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.*;
+import java.util.Objects;
+import java.util.Properties;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 14:55
+ */
+@Log4j2
+public class IMAPConnection implements MailConnection {
+    private  Store store;
+    private  Session session;
+    private  String host;
+    private  String port;
+    private  String user;
+    private  String password;
+    private  Boolean tls;
+    private  Boolean ssl;
+    private  IMAPFolder folder;
+
+    public IMAPConnection(String host, String port, String user, String password,Boolean tls,Boolean ssl) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+        this.tls = tls;
+        this.ssl = ssl;
+    }
+
+    public IMAPConnection(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+        this.tls = true;
+        this.ssl = true;
+    }
+
+    @Override
+    public void connect() throws MessagingException {
+        Properties properties = new Properties();
+        properties.put("mail.store.protocol", "imap");
+        properties.put("mail.imap.host", host);
+        properties.put("mail.imap.port", port);
+        properties.put("mail.imap.starttls.enable",tls);
+        properties.put("mail.imap.ssl.enable", ssl);
+
+        Authenticator auth = new Authenticator() {
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(user, password);
+            }
+        };
+        this.session = Session.getInstance(properties,auth);
+        this.store = session.getStore("imap");
+        this.store.connect();
+    }
+
+    @Override
+    public void close() {
+        try {
+            if (this.store != null && this.store.isConnected()) {
+                this.store.close();
+            }
+            if (this.folder != null) {
+                this.folder.close();
+            }
+            if (this.session != null) {
+                this.session = null;
+            }
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public Store getStore() {
+        return this.store;
+    }
+
+    @Override
+    public IMAPFolder getFolder(String folderName) {
+        try {
+            if (Objects.isNull(this.store)) {
+                return null;
+            }
+            this.folder = (IMAPFolder) this.store.getFolder(folderName);
+            return  this.folder;
+        }catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    public Message getMessageByUid(String uid,String folderName) {
+        try {
+            if (Objects.isNull(this.folder)) {
+                this.folder = getFolder(folderName);
+            }
+            if (!this.folder.isOpen()) {
+                this.folder.open(Folder.READ_ONLY);
+            }
+            Message message = this.folder.getMessageByUID(Long.parseLong(uid));
+            return message;
+        } catch (MessagingException e) {
+            log.error("getMessageByUid -- error --",e);
+            return null;
+        }
+    }
+
+    @Override
+    public String getMessageUid(Message message) {
+        try {
+            Long uid = this.folder.getUID(message);
+            if (Objects.isNull(uid)) {
+                return null;
+            }
+            return String.valueOf(uid);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 27 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnectionFactory.java

@@ -0,0 +1,27 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 14:57
+ */
+public class IMAPConnectionFactory extends MailConnectionFactory {
+    private final String host;
+    private final String port;
+    private final String user;
+    private final String password;
+
+    public IMAPConnectionFactory(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+    }
+
+    @Override
+    public MailConnection createConnection() {
+        return new IMAPConnection(host, port, user, password);
+    }
+}
+

+ 21 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnection.java

@@ -0,0 +1,21 @@
+package com.storlead.mail.connection;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Store;
+
+public interface MailConnection {
+
+    void connect() throws MessagingException;
+
+    void close();
+
+    Store getStore();
+
+    Folder getFolder(String folderName);
+
+    Message getMessageByUid(String uid,String folderName);
+
+    String getMessageUid(Message message);
+}

+ 26 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnectionFactory.java

@@ -0,0 +1,26 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 14:56
+ */
+public abstract class MailConnectionFactory {
+    public abstract MailConnection createConnection();
+
+    public static MailConnectionFactory getFactory(String protocol, String host, String port, String user, String password) {
+        switch (protocol.toLowerCase()) {
+            case "imap":
+                return new IMAPConnectionFactory(host, port, user, password);
+            case "pop3":
+                return new POP3ConnectionFactory(host, port, user, password);
+            default:
+                throw new IllegalArgumentException("Unsupported protocol: " + protocol);
+        }
+    }
+
+    public static SMTPConnectionFactory getSMTPFactory(String host, String port, String user, String password) {
+        return new SMTPConnectionFactory(host, port, user, password);
+    }
+}

+ 126 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3Connection.java

@@ -0,0 +1,126 @@
+package com.storlead.mail.connection;
+
+import com.sun.mail.pop3.POP3Folder;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.*;
+import javax.mail.search.MessageIDTerm;
+import javax.mail.search.SearchTerm;
+import java.util.Objects;
+import java.util.Properties;
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 15:14
+ */
+@Log4j2
+public class POP3Connection implements MailConnection {
+    private  Store store;
+    private  Session session;
+    private  String host;
+    private  String port;
+    private  String user;
+    private  String password;
+    private  Boolean tls;
+    private  Boolean ssl;
+    private  POP3Folder folder;
+
+    public POP3Connection(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+        this.tls = true;
+        this.ssl = true;
+    }
+
+    public POP3Connection(String host, String port, String user, String password,Boolean tls,Boolean ssl) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+        this.tls = tls;
+        this.ssl = ssl;
+    }
+    @Override
+    public void connect() throws MessagingException {
+        Properties properties = new Properties();
+        properties.put("mail.store.protocol", "pop3");
+        properties.put("mail.pop3.host", host);
+        properties.put("mail.pop3.port", port);
+        properties.put("mail.pop3.starttls.enable", "true");
+        properties.put("mail.pop3.ssl.enable", "true");
+
+        Authenticator auth = new Authenticator() {
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(user, password);
+            }
+        };
+        this.session = Session.getInstance(properties,auth);
+        this.store = session.getStore("pop3");
+        this.store.connect();
+    }
+
+    @Override
+    public void close() {
+        try {
+            if (this.store != null && this.store.isConnected()) {
+                this.store.close();
+            }
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public Store getStore() {
+        return this.store;
+    }
+
+    @Override
+    public POP3Folder getFolder(String folderName) {
+        try {
+            if (Objects.isNull(this.store)) {
+                log.error("store is null --- address ---"+this.user);
+                return null;
+            }
+            this.folder = (POP3Folder)this.store.getFolder(folderName);
+            return this.folder;
+        }catch (Exception e) {
+            log.error("store is null --- Exception ---"+this.user,e);
+            return null;
+        }
+    }
+
+    @Override
+    public Message getMessageByUid(String messageId,String folderName) {
+        try {
+            if (Objects.isNull(this.folder)) {
+                this.folder = getFolder(folderName);
+            }
+            SearchTerm searchTerm = new MessageIDTerm(messageId);
+            if (!this.folder.isOpen()) {
+                this.folder.open(Folder.READ_ONLY);
+            }
+            Message [] messages = folder.search(searchTerm);
+            if (messages.length > 0) {
+                return messages[0];
+            }
+            return null;
+        } catch (MessagingException e) {
+            log.error("getMessageByUid -- error",e);
+            return null;
+        }
+    }
+
+    @Override
+    public String getMessageUid(Message message) {
+        try {
+           return this.folder.getUID(message);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
+

+ 26 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3ConnectionFactory.java

@@ -0,0 +1,26 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 15:22
+ */
+public class POP3ConnectionFactory extends MailConnectionFactory {
+    private final String host;
+    private final String port;
+    private final String user;
+    private final String password;
+
+    public POP3ConnectionFactory(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+    }
+
+    @Override
+    public MailConnection createConnection() {
+        return new POP3Connection(host, port, user, password);
+    }
+}

+ 53 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnection.java

@@ -0,0 +1,53 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 15:23
+ */
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import java.util.Properties;
+
+public class SMTPConnection {
+    private Session session;
+    private final String host;
+    private final String port;
+    private final String user;
+    private final String password;
+
+    public SMTPConnection(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+    }
+
+    public void connect() {
+        Properties properties = new Properties();
+        properties.put("mail.smtp.auth", "true");
+        properties.put("mail.smtp.starttls.enable", "true");
+        properties.put("mail.smtp.host", host);
+        properties.put("mail.smtp.port", port);
+        properties.put("mail.smtp.ssl.enable", "true");
+
+        session = Session.getInstance(properties, new Authenticator() {
+            @Override
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(user, password);
+            }
+        });
+    }
+
+    public void sendEmail(String toAddress, String subject, String message) throws MessagingException {
+        Message mimeMessage = new MimeMessage(session);
+        mimeMessage.setFrom(new InternetAddress(user));
+        mimeMessage.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toAddress));
+        mimeMessage.setSubject(subject);
+        mimeMessage.setText(message);
+        Transport.send(mimeMessage);
+    }
+}
+

+ 25 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnectionFactory.java

@@ -0,0 +1,25 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 15:23
+ */
+public class SMTPConnectionFactory {
+    private final String host;
+    private final String port;
+    private final String user;
+    private final String password;
+
+    public SMTPConnectionFactory(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+    }
+
+    public SMTPConnection createConnection() {
+        return new SMTPConnection(host, port, user, password);
+    }
+}

+ 93 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailConnectionUtil.java

@@ -0,0 +1,93 @@
+package com.storlead.mail.connection.client;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 16:26
+ */
+import com.storlead.mail.connection.MailConnection;
+import com.storlead.mail.connection.MailConnectionFactory;
+import com.storlead.mail.connection.*;
+import com.storlead.mail.connection.config.MailProperties;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Log4j2
+public class MailConnectionUtil {
+
+    public static ConcurrentHashMap<String, MailConnection> chatMailConnectionMap = new ConcurrentHashMap<>();
+    // Method to receive emails using IMAP
+
+    public static MailConnection receiveEmailsConnection(MailProperties properties) {
+        try {
+//            if (Objects.nonNull(chatMailConnectionMap.get(properties.getEmailAddress()+"_"+properties.get))) {
+//                return chatMailConnectionMap.get(properties.getEmailAddress());
+//            }
+            MailConnectionFactory factory = MailConnectionFactory.getFactory(properties.getProtocol(),properties.getHost(),properties.getPort(),properties.getEmailAddress(),properties.getEmailPassword());
+            MailConnection imapConnection  = factory.createConnection();
+            log.error("mailConnection -- testImap == "+properties.getProtocol()+"------"+imapConnection);
+            imapConnection.connect();
+//            chatMailConnectionMap.put(properties.getEmailAddress(),imapConnection);
+            return imapConnection;
+        } catch (MessagingException e) {
+//            e.printStackTrace();
+            log.error("connection error mail --"+properties.getEmailAddress(),e);
+//            chatMailConnectionMap.remove(properties.getEmailAddress());
+            return null;
+        }
+    }
+
+    public static MailConnection testReceiveEmailsConnection(MailProperties properties) {
+        try {
+            MailConnectionFactory factory = MailConnectionFactory.getFactory(properties.getProtocol(),properties.getHost(),properties.getPort(),properties.getEmailAddress(),properties.getEmailPassword());
+            MailConnection imapConnection  = factory.createConnection();
+            log.error("mailConnection -- testImap == "+properties.getProtocol()+"------"+imapConnection);
+            imapConnection.connect();
+            return imapConnection;
+        } catch (MessagingException e) {
+//            e.printStackTrace();
+            log.error("connection error mail --"+properties.getEmailAddress(),e);
+            return null;
+        }
+    }
+
+    public static MailConnection deleteEmailsConnection(MailProperties properties) {
+        try {
+            MailConnectionFactory factory = MailConnectionFactory.getFactory(properties.getProtocol(),properties.getHost(),properties.getPort(),properties.getEmailAddress(),properties.getEmailPassword());
+            MailConnection imapConnection  = factory.createConnection();
+            imapConnection.connect();
+            return imapConnection;
+        } catch (MessagingException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+//    public static void main(String[] args) {
+//        String imapHost = "imap.exmail.qq.com";
+//        String pop3Host = "pop.exmail.qq.com";
+//        String smtpHost = "smtp.exmail.qq.com";
+//        String port = "993";
+//        String smtpPort = "465";
+//        String user = "your_email@example.com"; // Replace with your email
+//        String password = "your_password"; // Replace with your password
+//
+//        try {
+//            System.out.println("Receiving emails using IMAP:");
+//            receiveEmailsIMAP(imapHost, port, user, password);
+//
+//            System.out.println("Receiving emails using POP3:");
+//            receiveEmailsPOP3(pop3Host, port, user, password);
+//
+//            System.out.println("Sending email using SMTP:");
+//            sendEmailSMTP(smtpHost, smtpPort, user, password,
+//                    "recipient@example.com", "Test Subject", "Test Message");
+//
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//        }
+//    }
+}
+

+ 48 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailReceiverUtil.java

@@ -0,0 +1,48 @@
+package com.storlead.mail.connection.client;
+
+import com.storlead.mail.connection.MailConnection;
+import com.storlead.mail.connection.MailConnectionFactory;
+
+import javax.mail.*;
+import java.util.Arrays;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 16:31
+ */
+
+public class MailReceiverUtil {
+    public static void receiveInboxEmails(String protocol, String host, String port, String user, String password) {
+        receiveEmails(protocol, host, port, user, password, "INBOX");
+    }
+
+    public static void receiveSentEmails(String protocol, String host, String port, String user, String password) {
+        receiveEmails(protocol, host, port, user, password, "Sent");
+    }
+
+    private static void receiveEmails(String protocol, String host, String port, String user, String password, String folderName) {
+        MailConnectionFactory connectionFactory = MailConnectionFactory.getFactory(protocol, host, port, user, password);
+        MailConnection mailConnection = connectionFactory.createConnection();
+        try {
+            mailConnection.connect();
+            Store store = mailConnection.getStore();
+            Folder folder = store.getFolder(folderName);
+            folder.open(Folder.READ_WRITE);
+            Message[] messages = folder.getMessages();
+            for (Message message : messages) {
+                System.out.println(protocol.toUpperCase() + " - Subject: " + message.getSubject());
+                System.out.println(protocol.toUpperCase() + " - From: " + Arrays.toString(message.getFrom()));
+                System.out.println("------------------------------------------------");
+            }
+
+            folder.close(false);
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        } finally {
+            mailConnection.close();
+        }
+    }
+}
+

+ 38 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailSenderUtil.java

@@ -0,0 +1,38 @@
+package com.storlead.mail.connection.client;
+
+import com.storlead.mail.connection.MailConnectionFactory;
+import com.storlead.mail.connection.SMTPConnection;
+import com.storlead.mail.connection.SMTPConnectionFactory;
+
+import javax.mail.MessagingException;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 16:34
+ */
+public class MailSenderUtil {
+    public static void sendEmail(String host, String port, String user, String password,
+                                 String toAddress, String subject, String messageContent) {
+        SMTPConnectionFactory smtpFactory = MailConnectionFactory.getSMTPFactory(host, port, user, password);
+        SMTPConnection smtpConnection = smtpFactory.createConnection();
+        try {
+            smtpConnection.connect();
+            smtpConnection.sendEmail(toAddress, subject, messageContent);
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void main(String[] args) {
+        String smtpHost = "smtp.exmail.qq.com";
+        String smtpPort = "465";
+        String user = "your_email@example.com"; // Replace with your email
+        String password = "your_password"; // Replace with your password
+
+        System.out.println("Sending email using SMTP:");
+        sendEmail(smtpHost, smtpPort, user, password,
+                "recipient@example.com", "Test Subject", "Test Message");
+    }
+}

+ 43 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/config/MailProperties.java

@@ -0,0 +1,43 @@
+package com.storlead.mail.connection.config;
+
+import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import lombok.Data;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 16:59
+ */
+@Data
+public class MailProperties {
+
+    private String protocol;
+
+    private String host;
+
+    private String port;
+
+    private String emailAddress;
+
+    private String emailPassword;
+
+    private Boolean tls;
+
+    private Boolean ssl;
+
+
+    public MailProperties(SmtpPopSettingsEntity smtpPop) {
+        AccessKeyEncryptor accessKeyEncryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+        String pass = accessKeyEncryptor.decrypt(smtpPop.getEmailPassword());
+        this.host = smtpPop.getReceiveServer() ;
+        this.port = smtpPop.getReceivePort();
+        this.emailAddress = smtpPop.getEmailAddress();
+        this.emailPassword = pass;
+        this.tls = Integer.valueOf(1).equals(smtpPop.getSmtpTls()) ? Boolean.TRUE : Boolean.FALSE;
+        this.ssl = Integer.valueOf(1).equals(smtpPop.getSmtpSsl()) ? Boolean.TRUE : Boolean.FALSE;
+        this.protocol = smtpPop.getProtocolType();
+    }
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/AttachmentMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.AttachmentEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 附件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface AttachmentMapper extends MyBaseMapper<AttachmentEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/ClientSentEmailsMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.ClientSentEmailsEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-12-04
+ */
+public interface ClientSentEmailsMapper extends MyBaseMapper<ClientSentEmailsEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailBlacklistRecordMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailBlacklistRecordEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮箱黑名单 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-10-31
+ */
+public interface EmailBlacklistRecordMapper extends MyBaseMapper<EmailBlacklistRecordEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFileFolderMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailFileFolderEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 文件夹 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-04
+ */
+public interface EmailFileFolderMapper extends MyBaseMapper<EmailFileFolderEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFolderRuleMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailFolderRuleEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 用户邮件文件夹出入规则 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+public interface EmailFolderRuleMapper extends MyBaseMapper<EmailFolderRuleEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFoldersMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailFoldersEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮件文件夹关系表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface EmailFoldersMapper extends MyBaseMapper<EmailFoldersEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailTemplatesMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailTemplatesEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮件模板 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-10-28
+ */
+public interface EmailTemplatesMapper extends MyBaseMapper<EmailTemplatesEntity> {
+
+}

+ 107 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailsMapper.java

@@ -0,0 +1,107 @@
+package com.storlead.mail.mapper;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import com.storlead.mail.pojo.MailDTO;
+import com.storlead.mail.pojo.MailListDTO;
+import com.storlead.mail.pojo.vo.*;
+import com.storlead.mail.pojo.vo.*;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * 邮件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface EmailsMapper extends MyBaseMapper<EmailsEntity> {
+
+    void updateCleanBindCustomerMail();
+
+    void updateBindCustomerMail();
+
+    void updateBindLiaisonMail();
+
+    /**
+     * 绑定客户邮件关系
+     * @param mailId
+     * @param mailAddress
+     * @param userId
+     */
+    Set<Long> getMailCustomerIdByMailIdAndUserId(@Param("mailId") Long mailId, @Param("mailAddress") String mailAddress, @Param("userId") Long userId);
+
+    /**
+     * 绑定联系人邮箱和客户关系
+     * @param mailId
+     * @param mailAddress
+     * @param userId
+     */
+    List<MailLiaisonVO> getLiaisonMailCustomerIdByMailIdAndUserId(@Param("mailId") Long mailId, @Param("mailAddress") String mailAddress, @Param("userId") Long userId);
+
+    void bindMailCustomerIdByMailId(@Param("mailId") Long mailId,@Param("customerIds") String customerId,@Param("liaisonIds") String liaisonIds);
+
+    EmailsEntity getEmailsByMessageId(@Param("messageId") String messageId,@Param("folder") String folder,@Param("smtpPopId") Long smtpPopId);
+
+    List<String> getEmailMessageIdsByMessageIds(@Param("messageIds") List<String> messageIds,@Param("folder") String folder,@Param("smtpPopId") Long smtpPopId);
+
+    String getEmailMessageIdsByMessageId(@Param("messageId") String messageId,@Param("folder") String folder,@Param("smtpPopId") Long smtpPopId);
+
+    Long getEmailsIdByMessageId(@Param("messageId") String messageId,@Param("smtpPopId") Long smtpPopId);
+
+    IPage<EmailsEntity> pageList(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+
+    IPage<EmailsEntity> pageListCus(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+    IPage<EmailsEntity> pageListAllCus(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+    IPage<EmailsEntity> pageListNew(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+    IPage<EmailsEntity> pageListNewAllCus(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+    /**
+     * 汇总邮件数量
+     * @param stmpPopIdk
+     * @param ownerBy
+     * @return
+     */
+    NewMailCountTipVO countMailSummaryNum(Long smtpPopId, Long ownerBy);
+
+    /**
+     * 汇总未跟进数
+     * @param stmpPopId
+     * @param ownerBy
+     * @return
+     */
+    NewMailCountTipVO countCustomerAndClueNumber(Long smtpPopId, Long ownerBy);
+
+    /**
+     * 汇总客户邮件数
+     * @param stmpPopId
+     * @param ownerBy
+     * @return
+     */
+    NewMailCountTipVO countCustomerFollowUpMailNum(Long smtpPopId, Long ownerBy);
+
+    LocalDateTime selectCustomerLastFollowUpTime(Long customerId);
+
+    List<EmailsEntity> selectDelayDeleteMail();
+
+    List<UserClickTitleVO> selectUserClickTitle(@Param("ownerBy") Long ownerBy, @Param("title") String title);
+
+    IPage<CustomerEmailVo> selectCustomerEmailPage(Page<CustomerEmailVo> page, @Param("query") MailListDTO dto);
+
+    List<Long> selectCusIdByMailId(@Param("mailIdls") List<Long> mailIdls);
+
+    List<CustomerMailVO> selectCusInfoByCusId(@Param("customerIdls") List<Long> customerIdls);
+
+    Integer countSmtpMailCount(@Param("userId") Long userId);
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/FoldersMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.FoldersEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 文件夹名称 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface FoldersMapper extends MyBaseMapper<FoldersEntity> {
+
+}

+ 23 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailAttachmentMapper.java

@@ -0,0 +1,23 @@
+package com.storlead.mail.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.mail.pojo.entity.MailAttachmentEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * <p>
+ * 邮件附件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-06-14
+ */
+public interface MailAttachmentMapper extends MyBaseMapper<MailAttachmentEntity> {
+
+    IPage<MailAttachmentEntity> pageList(Page<MailAttachmentEntity> page, @Param(Constants.WRAPPER) Wrapper<MailAttachmentEntity> wrapper);
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailDelayPullRecordMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.MailDelayPullRecordEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮件等待处理记录 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-08-24
+ */
+public interface MailDelayPullRecordMapper extends MyBaseMapper<MailDelayPullRecordEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailServerPortConfigMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.MailServerPortConfigEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮箱端口配置 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-08-08
+ */
+public interface MailServerPortConfigMapper extends MyBaseMapper<MailServerPortConfigEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailTempAttachmentMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.MailTempAttachmentEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 临时邮件附件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-08-09
+ */
+public interface MailTempAttachmentMapper extends MyBaseMapper<MailTempAttachmentEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailboxAutoReplySetMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.MailboxAutoReplySetEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮箱自动回复内容 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-02-24
+ */
+public interface MailboxAutoReplySetMapper extends MyBaseMapper<MailboxAutoReplySetEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/SmtpPopSettingsMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮箱smtp_setting Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface SmtpPopSettingsMapper extends MyBaseMapper<SmtpPopSettingsEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserEmailFolderMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.UserEmailFolderEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 用户文件夹 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+public interface UserEmailFolderMapper extends MyBaseMapper<UserEmailFolderEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserMailTrackRecordMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.UserMailTrackRecordEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 当前收取邮件的最新邮件记录 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-19
+ */
+public interface UserMailTrackRecordMapper extends MyBaseMapper<UserMailTrackRecordEntity> {
+
+}

+ 57 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/properties/MailFileProperties.java

@@ -0,0 +1,57 @@
+package com.storlead.mail.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-06-14 18:28
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "file")
+public class MailFileProperties {
+
+    /** 文件大小限制 */
+    private Long maxSize;
+
+    /** 头像大小限制 */
+    private Long avatarMaxSize;
+
+    /** 0:本地,1:OSS */
+    private Integer mode = 0;
+
+    /** 环境 */
+    private String active = "";
+
+    private ElPath mac;
+
+    private String filePath;
+
+    private ElPath linux;
+
+    private ElPath windows;
+
+    public ElPath getPath(){
+        String os = System.getProperty("os.name");
+        if(os.toLowerCase().startsWith("win")) {
+            return windows;
+        } else if(os.toLowerCase().startsWith("mac")){
+            return mac;
+        }
+        return linux;
+    }
+
+    @Data
+    public static class ElPath{
+
+        private String path;
+
+        private String avatar;
+
+        private String jpeg;
+    }
+}

+ 14 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AbstractMailFunctionApiFactory.java

@@ -0,0 +1,14 @@
+package com.storlead.mail.service.impl;
+
+/**
+ * @program: sp-sales-platform
+ * @description: 抽象邮件工厂类
+ * @author: chenkq
+ * @create: 2024-05-29 10:16
+ */
+public abstract class  AbstractMailFunctionApiFactory {
+
+    // 收邮件
+
+    // 发送邮件
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AttachmentServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.AttachmentService;
+import com.storlead.mail.pojo.entity.AttachmentEntity;
+import com.storlead.mail.mapper.AttachmentMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 附件表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+@Service
+public class AttachmentServiceImpl extends MyBaseServiceImpl<AttachmentMapper, AttachmentEntity> implements AttachmentService {
+
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/ClientSentEmailsServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.ClientSentEmailsService;
+import com.storlead.mail.pojo.entity.ClientSentEmailsEntity;
+import com.storlead.mail.mapper.ClientSentEmailsMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 邮件表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-12-04
+ */
+@Service
+public class ClientSentEmailsServiceImpl extends MyBaseServiceImpl<ClientSentEmailsMapper, ClientSentEmailsEntity> implements ClientSentEmailsService {
+
+}

+ 38 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailBlacklistRecordServiceImpl.java

@@ -0,0 +1,38 @@
+package com.storlead.mail.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.mail.service.EmailBlacklistRecordService;
+import com.storlead.mail.pojo.entity.EmailBlacklistRecordEntity;
+import com.storlead.mail.mapper.EmailBlacklistRecordMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 邮箱黑名单 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-10-31
+ */
+@Service
+public class EmailBlacklistRecordServiceImpl extends MyBaseServiceImpl<EmailBlacklistRecordMapper, EmailBlacklistRecordEntity> implements EmailBlacklistRecordService {
+
+    @Override
+    public Set<String> getBlackEmaillist(Long userId) {
+        LambdaQueryWrapper<EmailBlacklistRecordEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(EmailBlacklistRecordEntity::getOwnerBy,userId);
+        queryWrapper.eq(EmailBlacklistRecordEntity::getIsDelete,Integer.valueOf(0));
+        List<EmailBlacklistRecordEntity> blacklist = list(queryWrapper);
+        if(CollectionUtils.isEmpty(blacklist)) {
+            return Collections.emptySet();
+        }
+        return blacklist.stream().map(EmailBlacklistRecordEntity::getEmailAddress).collect(Collectors.toSet());
+    }
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFileFolderServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.EmailFileFolderService;
+import com.storlead.mail.pojo.entity.EmailFileFolderEntity;
+import com.storlead.mail.mapper.EmailFileFolderMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 文件夹 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-04
+ */
+@Service
+public class EmailFileFolderServiceImpl extends MyBaseServiceImpl<EmailFileFolderMapper, EmailFileFolderEntity> implements EmailFileFolderService {
+
+}

+ 77 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFolderRuleServiceImpl.java

@@ -0,0 +1,77 @@
+package com.storlead.mail.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.mail.service.EmailFolderRuleService;
+import com.storlead.mail.pojo.entity.EmailFolderRuleEntity;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.enums.EmailFolderlRuleEnum;
+import com.storlead.mail.mapper.EmailFolderRuleMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <p>
+ * 用户邮件文件夹出入规则 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+@Service
+public class EmailFolderRuleServiceImpl extends MyBaseServiceImpl<EmailFolderRuleMapper, EmailFolderRuleEntity> implements EmailFolderRuleService {
+
+    @Override
+    public List<EmailFolderRuleEntity> getUserFolderRules(Long userId) {
+        LambdaQueryWrapper<EmailFolderRuleEntity> queryWrapper = new LambdaQueryWrapper();
+        if (Objects.nonNull(userId)) {
+            queryWrapper.eq(EmailFolderRuleEntity::getOwnerBy,userId);
+        }
+        queryWrapper.eq(EmailFolderRuleEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.orderByAsc(EmailFolderRuleEntity::getSort);
+        List<EmailFolderRuleEntity> list = this.list(queryWrapper);
+        return list;
+    }
+
+    @Override
+    public void carveUpMailCustomFolder(EmailsEntity emails,List<EmailFolderRuleEntity> rolderRules) {
+        if (CollectionUtils.isEmpty(rolderRules)) {
+            return;
+        }
+        for (EmailFolderRuleEntity rule : rolderRules) {
+            if (EmailFolderlRuleEnum.EMAIL_FROM_EQ.code.equals(rule.getFolderRuleCode())) {
+                // 发件箱等于
+                if (emails.getFrom().equals(rule.getCompareContent())) {
+                    emails.setCustomFolderId(rule.getFolderId());
+                    break;
+                }
+            } else if (EmailFolderlRuleEnum.EMAIL_SUBJECT_CONTAINS.code.equals(rule.getFolderRuleCode())) {
+                // 邮件主题包含
+                if (emails.getSubject().contains(rule.getCompareContent())) {
+                    emails.setCustomFolderId(rule.getFolderId());
+                    break;
+                }
+            } else if (EmailFolderlRuleEnum.EMAIL_FROM_SUFFIX_EQ.code.equals(rule.getFolderRuleCode())) {
+                //发件箱后缀等于
+                String domainRegex = "@(.+)$";
+                // 创建 Pattern 对象
+                Pattern pattern = Pattern.compile(domainRegex);
+                // 创建 Matcher 对象
+                Matcher matcher = pattern.matcher(emails.getFrom());
+                // 查找并提取 @ 后面的部分
+                if (matcher.find()) {
+                    String domain = matcher.group(1);  // 获取 @ 后面的部分
+                    if (rule.getCompareContent().equals(domain)) {
+                        emails.setCustomFolderId(rule.getFolderId());
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFoldersServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.EmailFoldersService;
+import com.storlead.mail.pojo.entity.EmailFoldersEntity;
+import com.storlead.mail.mapper.EmailFoldersMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 邮件文件夹关系表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+@Service
+public class EmailFoldersServiceImpl extends MyBaseServiceImpl<EmailFoldersMapper, EmailFoldersEntity> implements EmailFoldersService {
+
+}

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