فهرست منبع

Add Tencent Ems

Mure 1 ماه پیش
والد
کامیت
c51bb4f43b

+ 8 - 8
.drone.yml

@@ -85,14 +85,14 @@ steps:
 #    settings:
 #      port: 22
 #      host:
-#        from_secret: daogucms_ssh_host
+#        from_secret: backendsys_ssh_host
 #      username:
-#        from_secret: daogucms_ssh_username
+#        from_secret: backendsys_ssh_username
 #      password:
-#        from_secret: daogucms_ssh_password
+#        from_secret: backendsys_ssh_password
 #      command_timeout: 5m
 #      source:
-#        - target/daogucms.jar
+#        - target/backendsys.jar
 #      target: /home/DaoguCms/
 #    when:
 #      branch: master
@@ -134,14 +134,14 @@ steps:
 #    settings:
 #      port: 22
 #      host:
-#        from_secret: daogucms_ssh_host
+#        from_secret: backendsys_ssh_host
 #      username:
-#        from_secret: daogucms_ssh_username
+#        from_secret: backendsys_ssh_username
 #      password:
-#        from_secret: daogucms_ssh_password
+#        from_secret: backendsys_ssh_password
 #      command_timeout: 5m
 #      source:
-#        - target/daogucms-dev.jar
+#        - target/backendsys-dev.jar
 #      target: /home/DaoguCmsDev/
 #    when:
 #      branch: develop

+ 18 - 0
db/sys_ems.sql

@@ -0,0 +1,18 @@
+/**
+Source Server Version: 8.0.31
+Source Database: daogucms
+Date: 2025/07/12 09:59:22
+*/
+
+DROP TABLE IF EXISTS `sys_ems`;
+CREATE TABLE `sys_ems`  (
+  PRIMARY KEY (`id`),
+  `id` BIGINT AUTO_INCREMENT COMMENT 'ID',
+  `origin` VARCHAR(20) COMMENT '来源',
+  `email` VARCHAR(50) NOT NULL COMMENT '邮箱地址',
+  `ems_code` VARCHAR(6) COMMENT '验证码',
+  `ip` VARCHAR(255) COMMENT 'IP',
+  `ua` VARCHAR(255) COMMENT 'User Agent',
+  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
+) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT = '系统邮件记录表';
+

+ 26 - 0
db/sys_ems_callback.sql

@@ -0,0 +1,26 @@
+/**
+Source Server Version: 8.0.31
+Source Database: daogucms
+Date: 2025/07/12 09:59:22
+*/
+
+DROP TABLE IF EXISTS `sys_ems_callback`;
+CREATE TABLE `sys_ems_callback`  (
+  PRIMARY KEY (`id`),
+  `id` BIGINT AUTO_INCREMENT COMMENT 'ID',
+  `event` VARCHAR(50) COMMENT '事件类型,如 bounce、delivered',
+  `email` VARCHAR(100) COMMENT '收件人地址',
+  `bulk_id` VARCHAR(200) COMMENT 'SendEmail 接口返回的 MessageId',
+  `timestamp` BIGINT COMMENT '事件产生的时间戳',
+  `reason` TEXT COMMENT '邮件递送失败的原因',
+  `bounce_type` VARCHAR(50) COMMENT '如果收件人邮件服务商拒信,拒信类型,取值:soft_bounce | hard_bounce,仅在event=\"bounce\"的时候生效',
+  `username` VARCHAR(50) COMMENT '腾讯云账号对应的 AppID',
+  `sender` VARCHAR(100) COMMENT '发信地址(不带发件人别名)',
+  `from_domain` VARCHAR(100) COMMENT '发件域名',
+  `template_id` BIGINT COMMENT '模板ID',
+  `subject` VARCHAR(255) COMMENT '邮件标题',
+  `message_id` VARCHAR(200) COMMENT 'smtp 协议头中的 Message-ID',
+  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '接收时间',
+  UNIQUE KEY (`message_id`)
+) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT = '邮件回调记录表';
+

+ 54 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/ems/controller/TencentEmsController.java

@@ -0,0 +1,54 @@
+package com.backendsys.modules.sdk.tencentcloud.ems.controller;
+
+import com.backendsys.modules.common.config.security.annotations.Anonymous;
+import com.backendsys.modules.common.config.security.utils.HttpRequestUtil;
+import com.backendsys.modules.common.utils.Result;
+import com.backendsys.modules.sdk.tencentcloud.ems.service.TencentEmsService;
+import com.backendsys.modules.sdk.tencentcloud.ems.service.TencentEmsCallbackService;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@Tag(name = "腾讯云-邮件回调")
+public class TencentEmsController {
+
+    @Autowired
+    private HttpRequestUtil httpRequestUtil;
+
+    @Autowired
+    private TencentEmsService tencentEmsService;
+    @Autowired
+    private TencentEmsCallbackService tencentEmsCallbackService;
+
+
+    @Anonymous
+    @Operation(summary = "邮件推送 (测试)")
+    @GetMapping("/api/ems/send")
+    public Result receiveCallback(String email) {
+        String templateParamSet = "{ \"code\": \"123456\", \"minute\": \"5\" }";
+        return Result.success().put("data", tencentEmsService.send("邮件测试", 140698L, templateParamSet, email));
+    }
+
+    /**
+     * [回调] 邮件通知事件
+     * https://cloud.tencent.com/document/product/1288/52368
+     */
+    @Anonymous
+    @Operation(summary = "邮件推送通知事件回调")
+    @PostMapping("/api/ems/callback")
+    //public Result receiveCallback(@RequestBody List<TencentEmsCallbackNew> emsCallbackList) {
+    public Result receiveCallback(@RequestBody Object body) {
+
+        System.out.println("body = " + body);
+
+        // tencentEmsCallbackService.receiveCallback(emsCallbackList);
+        return Result.success();
+    }
+
+}

+ 9 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/ems/dao/TencentEmsCallbackDao.java

@@ -0,0 +1,9 @@
+package com.backendsys.modules.sdk.tencentcloud.ems.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.backendsys.modules.sdk.tencentcloud.ems.entity.TencentEmsCallbackEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface TencentEmsCallbackDao extends BaseMapper<TencentEmsCallbackEntity> {
+}

+ 26 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/ems/entity/TencentEmsCallbackEntity.java

@@ -0,0 +1,26 @@
+package com.backendsys.modules.sdk.tencentcloud.ems.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("sys_ems_callback")
+public class TencentEmsCallbackEntity {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String event;
+    private String email;
+    private String bulk_id;
+    private Long timestamp;
+    private String reason;
+    private String bounce_type;
+    private String username;
+    private String sender;
+    private String from_domain;
+    private Long template_id;
+    private String subject;
+    private String message_id;
+    private String create_time;
+}

+ 19 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/ems/entity/TencentEmsCallbackNew.java

@@ -0,0 +1,19 @@
+package com.backendsys.modules.sdk.tencentcloud.ems.entity;
+
+import lombok.Data;
+
+@Data
+public class TencentEmsCallbackNew {
+    private String event;
+    private String email;
+    private String bulkId;
+    private Long timestamp;
+    private String reason;
+    private String bounceType;
+    private String username;
+    private String from;
+    private String fromDomain;
+    private Long templateId;
+    private String subject;
+    private String messageId;
+}

+ 9 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/ems/service/TencentEmsCallbackService.java

@@ -0,0 +1,9 @@
+package com.backendsys.modules.sdk.tencentcloud.ems.service;
+
+import com.backendsys.modules.sdk.tencentcloud.ems.entity.TencentEmsCallbackNew;
+
+import java.util.List;
+
+public interface TencentEmsCallbackService {
+    void receiveCallback(List<TencentEmsCallbackNew> tencentEmsCallbackNew);
+}

+ 11 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/ems/service/TencentEmsService.java

@@ -0,0 +1,11 @@
+package com.backendsys.modules.sdk.tencentcloud.ems.service;
+
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.ses.v20201002.models.SendEmailResponse;
+
+public interface TencentEmsService {
+
+    //邮件推送
+    SendEmailResponse send(String title, Long template_id, String templateParamSet, String email);
+}
+

+ 52 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/ems/service/impl/TencentEmsCallbackServiceImpl.java

@@ -0,0 +1,52 @@
+package com.backendsys.modules.sdk.tencentcloud.ems.service.impl;
+
+import com.backendsys.modules.sdk.tencentcloud.ems.dao.TencentEmsCallbackDao;
+import com.backendsys.modules.sdk.tencentcloud.ems.entity.TencentEmsCallbackEntity;
+import com.backendsys.modules.sdk.tencentcloud.ems.entity.TencentEmsCallbackNew;
+import com.backendsys.modules.sdk.tencentcloud.ems.service.TencentEmsCallbackService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class TencentEmsCallbackServiceImpl implements TencentEmsCallbackService {
+    @Autowired
+    private TencentEmsCallbackDao tencentEmsCallbackDao;
+
+    @Override
+    public void receiveCallback(List<TencentEmsCallbackNew> emsCallbackList) {
+        for (TencentEmsCallbackNew dto : emsCallbackList) {
+            String messageId = dto.getMessageId();
+            try {
+                // 幂等检查(根据 messageId)
+                if (tencentEmsCallbackDao.selectById(messageId) != null) {
+                    continue;
+                }
+                // 构建实体
+                TencentEmsCallbackEntity entity = new TencentEmsCallbackEntity();
+                entity.setEvent(dto.getEvent());
+                entity.setEmail(dto.getEmail());
+                entity.setBulk_id(dto.getBulkId());
+                entity.setTimestamp(dto.getTimestamp());
+                entity.setReason(dto.getReason());
+                entity.setBounce_type(dto.getBounceType());
+                entity.setUsername(dto.getUsername());
+                entity.setSender(dto.getFrom());
+                entity.setFrom_domain(dto.getFromDomain());
+                entity.setTemplate_id(dto.getTemplateId());
+                entity.setSubject(dto.getSubject());
+                entity.setMessage_id(messageId);
+                // 保存记录
+                tencentEmsCallbackDao.insert(entity);
+
+            } catch (DuplicateKeyException e) {
+                // 数据库唯一索引冲突兜底幂等
+                System.out.println("重复插入数据:" + messageId);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}

+ 68 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/ems/service/impl/TencentEmsServiceImpl.java

@@ -0,0 +1,68 @@
+package com.backendsys.modules.sdk.tencentcloud.ems.service.impl;
+
+
+import com.backendsys.exception.CustException;
+import com.backendsys.modules.sdk.tencentcloud.ems.service.TencentEmsService;
+import com.tencentcloudapi.common.Credential;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.common.profile.ClientProfile;
+import com.tencentcloudapi.common.profile.HttpProfile;
+import com.tencentcloudapi.ses.v20201002.SesClient;
+import com.tencentcloudapi.ses.v20201002.models.SendEmailRequest;
+import com.tencentcloudapi.ses.v20201002.models.SendEmailResponse;
+import com.tencentcloudapi.ses.v20201002.models.Template;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TencentEmsServiceImpl implements TencentEmsService {
+    @Value("${tencent.ems.secret-id}")
+    private String SECRET_ID;
+
+    @Value("${tencent.ems.secret-key}")
+    private String SECRET_KEY;
+
+    @Value("${tencent.ems.http-profile}")
+    private String HPF;
+
+    @Value("${tencent.ems.ses-client}")
+    private String SES_CLIENT;
+
+    @Value("${tencent.ems.from-email-address}")
+    private String FROM_EMAIL_ADDRESS;
+
+    @Override
+    public SendEmailResponse send(String title, Long template_id, String templateParamSet, String email) {
+
+        Credential cred = new Credential(SECRET_ID, SECRET_KEY);
+        HttpProfile httpProfile = new HttpProfile();
+        httpProfile.setEndpoint(HPF);
+        ClientProfile clientProfile = new ClientProfile();
+        clientProfile.setHttpProfile(httpProfile);
+        SesClient client = new SesClient(cred, SES_CLIENT, clientProfile);
+
+        SendEmailRequest req = new SendEmailRequest();
+        req.setFromEmailAddress(FROM_EMAIL_ADDRESS);
+
+        String[] destination = {email};
+        req.setDestination(destination);
+        req.setSubject(title);
+        Template template = new Template();
+        template.setTemplateID(template_id);
+
+        //设置参数模板
+        template.setTemplateData(templateParamSet);
+        req.setTemplate(template);
+
+        try {
+            SendEmailResponse resp = client.SendEmail(req);
+            return resp;
+        } catch (TencentCloudSDKException e) {
+            throw new CustException(e.getMessage());
+        }
+
+    }
+}
+
+
+

+ 6 - 0
src/main/resources/application-dev.yml

@@ -106,6 +106,12 @@ tencent:
   facefusion:
     secret-id: AKIDlNn7F9tRlCWsIe7d3IOM9mAIRhsIfxAV
     secret-key: c9Zt0sa5UzQf4HNOURAetFYfehqwpolc
+  ems:
+    secret-id: AKIDgOtBJEwT5Wu8GRTSDmALWaUQM6d0JYcT
+    secret-key: EG5g3DLNCzd5MRK2XhhaGm7F9pKdikgT
+    http-profile: ses.tencentcloudapi.com
+    ses-client: ap-hongkong
+    from-email-address: 稻谷 <dev.noreply@mail.dgdrama.com>
   sms:
     debug: false
     secret-id: AKIDITiApJZjt27AEOhzA92467Nbilw4RyRp

+ 6 - 0
src/main/resources/application-local.yml

@@ -108,6 +108,12 @@ tencent:
   facefusion:
     secret-id: AKIDlNn7F9tRlCWsIe7d3IOM9mAIRhsIfxAV
     secret-key: c9Zt0sa5UzQf4HNOURAetFYfehqwpolc
+  ems:
+    secret-id: AKIDgOtBJEwT5Wu8GRTSDmALWaUQM6d0JYcT
+    secret-key: EG5g3DLNCzd5MRK2XhhaGm7F9pKdikgT
+    http-profile: ses.tencentcloudapi.com
+    ses-client: ap-hongkong
+    from-email-address: 稻谷 <dev.noreply@mail.dgdrama.com>
   sms:
     debug: false
     secret-id: AKIDITiApJZjt27AEOhzA92467Nbilw4RyRp

+ 6 - 0
src/main/resources/application-prod.yml

@@ -107,6 +107,12 @@ tencent:
   facefusion:
     secret-id: AKIDlNn7F9tRlCWsIe7d3IOM9mAIRhsIfxAV
     secret-key: c9Zt0sa5UzQf4HNOURAetFYfehqwpolc
+  ems:
+    secret-id: AKIDgOtBJEwT5Wu8GRTSDmALWaUQM6d0JYcT
+    secret-key: EG5g3DLNCzd5MRK2XhhaGm7F9pKdikgT
+    http-profile: ses.tencentcloudapi.com
+    ses-client: ap-hongkong
+    from-email-address: 稻谷 <noreply@mail.dgdrama.com>
   sms:
     debug: false
     secret-id: AKIDITiApJZjt27AEOhzA92467Nbilw4RyRp