Przeglądaj źródła

1.增加html邮件
2.增加发送邮件工具类

lengfaqiang 2 lat temu
rodzic
commit
2184a07359
15 zmienionych plików z 1300 dodań i 93 usunięć
  1. 51 0
      slope-common/slope-common-core/src/main/java/com/sckw/core/model/enums/AlarmLevelEnum.java
  2. 1 1
      slope-common/slope-common-core/src/main/java/com/sckw/core/model/enums/AlarmTitleEnum.java
  3. 13 0
      slope-common/slope-common-sms/pom.xml
  4. 15 22
      slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/consumer/MqttCallbackHandler.java
  5. 2 5
      slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/consumer/MqttDeviceCallbackHandler.java
  6. 50 0
      slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/controller/CommonController.java
  7. 4 0
      slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/model/param/SmsMessageParam.java
  8. 34 3
      slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/service/CommonService.java
  9. 1 2
      slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/service/api/DetectionApiService.java
  10. 79 59
      slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/service/task/DeviceAlarmTaskService.java
  11. 274 0
      slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/utils/MailUtil.java
  12. 1 1
      slope-modules/slope-detection/src/main/resources/bootstrap.yml
  13. 127 0
      slope-modules/slope-detection/src/main/resources/template/Email.html
  14. 648 0
      slope-modules/slope-detection/src/main/resources/template/EmailTemplate.html
  15. BIN
      slope-modules/slope-detection/src/main/resources/template/logo.png

+ 51 - 0
slope-common/slope-common-core/src/main/java/com/sckw/core/model/enums/AlarmLevelEnum.java

@@ -0,0 +1,51 @@
+package com.sckw.core.model.enums;
+
+import lombok.Getter;
+
+/**
+ * @author lfdc
+ * @desc 告警等级
+ * @date 2023/6/15
+ */
+@Getter
+public enum AlarmLevelEnum {
+
+    //离线时长超1天
+    ALARM_Level_ONE(1, "1", "一级", "一"),
+    //数值超阈值
+    ALARM_Level_TWO(2, "2", "二级", "二"),
+    ALARM_LEVEL_THREE(3, "2", "三级", "三"),
+    ;
+
+    private final int code;
+
+    private final String status;
+
+    private final String name;
+    private final String destination;
+
+    AlarmLevelEnum(int code, String status, String name, String destination) {
+        this.code = code;
+        this.status = status;
+        this.name = name;
+        this.destination = destination;
+    }
+
+    public static String getName(int code) {
+        for (AlarmLevelEnum entryTypeEnum : values()) {
+            if (entryTypeEnum.getCode() == code) {
+                return entryTypeEnum.name;
+            }
+        }
+        return null;
+    }
+
+    public static String getChineseName(int code) {
+        for (AlarmLevelEnum entryTypeEnum : values()) {
+            if (entryTypeEnum.getCode() == code) {
+                return entryTypeEnum.destination;
+            }
+        }
+        return null;
+    }
+}

+ 1 - 1
slope-common/slope-common-core/src/main/java/com/sckw/core/model/enums/AlarmTitleEnum.java

@@ -11,7 +11,7 @@ import lombok.Getter;
 public enum AlarmTitleEnum {
 
     //离线时长超1天
-    ALARM_TITLE_ONE(1, "1", "离线时长超1天"),
+    ALARM_TITLE_ONE(1, "1", "离线时长超24小时"),
     //数值超阈值
     ALARM_TITLE_TWO(2, "2", "数值超阈值");
 

+ 13 - 0
slope-common/slope-common-sms/pom.xml

@@ -30,6 +30,19 @@
             <artifactId>spring-cloud-alicloud-sms</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-mail</artifactId>
+        </dependency>
+
+        <!-- 读取html -->
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+            <version>1.8.3</version>
+        </dependency>
+
+
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-context</artifactId>

+ 15 - 22
slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/consumer/MqttCallbackHandler.java

@@ -6,10 +6,7 @@ import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.sckw.core.model.constant.Global;
 import com.sckw.core.model.constant.NumberConstant;
-import com.sckw.core.model.enums.AlarmTitleEnum;
-import com.sckw.core.model.enums.AlarmTypeEnum;
-import com.sckw.core.model.enums.DictEnum;
-import com.sckw.core.model.enums.DictItemEnum;
+import com.sckw.core.model.enums.*;
 import com.sckw.core.utils.CollectionUtils;
 import com.sckw.core.utils.DateUtils;
 import com.sckw.core.utils.IdWorker;
@@ -42,6 +39,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
@@ -100,13 +98,6 @@ public class MqttCallbackHandler extends AbstractHandler {
 
         DevicesAlarm devicesAlarm = JSONObject.parseObject(payload, DevicesAlarm.class);
         Map<String, SystemDict> dictByDictCode = commonService.getDictByDictCode(DictEnum.MODEL_PART);
-//        JSONObject objects = JSONObject.parseObject(payload);
-//        String deviceTs = objects.getString("ts");
-//        String deviceGuid = objects.getString("guid");
-//        String deviceTslverId = objects.getString("tslver_id");
-//        String deviceMsgId = objects.getString("msg_id");
-//        String deviceRawTs = objects.getString("raw_ts");
-//        JSONArray checkArr = objects.getJSONArray("check_arr");
         String key = PasswordUtils.md5(topic + "alarm" + payload);
         String alarmKey = String.format(RedisConstant.MQ_CONSUMER_ALARM_KEY, key);
         boolean flag = redisLockUtil.tryLock(alarmKey, 5);
@@ -186,7 +177,7 @@ public class MqttCallbackHandler extends AbstractHandler {
                                     time = com.sckw.excel.utils.DateUtil.format(localDateTime);
                                 }
                                 templateParam.put("time", time);
-                                templateParam.put("level", level.toString());
+                                templateParam.put("level", AlarmLevelEnum.getChineseName(level.intValue()));
                                 templateParam.put("name", device.getName());
 //                            templateParam.put("value", itemValue);
                                 templateParam.put("value", offset);
@@ -208,13 +199,13 @@ public class MqttCallbackHandler extends AbstractHandler {
                                 emailAddress = emailList.stream().map(KwsAlarmInfo::getValueDesc).distinct().collect(Collectors.toList());
                             }
                             if (!CollectionUtils.isEmpty(emailAddress)) {
-                                Map<String, Object> templateParam = new HashMap<>();
+                                Map<String, String> templateParam = new HashMap<>();
                                 if (StringUtils.isBlank(time)) {
                                     LocalDateTime localDateTime = Instant.ofEpochSecond(ts).atZone(ZoneId.systemDefault()).toLocalDateTime();
                                     time = com.sckw.excel.utils.DateUtil.format(localDateTime);
                                 }
                                 templateParam.put("company", smsTemplateConfig.getDataAlarm().getSignName());
-                                templateParam.put("level", level.toString());
+                                templateParam.put("level", AlarmLevelEnum.getName(level.intValue()));
                                 templateParam.put("name", device.getName());
                                 templateParam.put("time", time);
                                 templateParam.put("project", vo == null ? null : vo.getProjectName());
@@ -222,8 +213,10 @@ public class MqttCallbackHandler extends AbstractHandler {
                                 templateParam.put("value", offset);
                                 templateParam.put("element", dictByDictCode.get(itemName).getLabel());
                                 templateParam.put("type", AlarmTitleEnum.ALARM_TITLE_TWO.getName() + "告警");
-                                commonService.pushEmailMessage(emailAddress
-                                        , templateParam);
+//                                commonService.pushEmailMessage(emailAddress
+//                                        , templateParam,"设备告警通知邮件");
+                                commonService.pushHtmlEmailMessage(emailAddress
+                                        , templateParam, "设备告警通知邮件");
                             }
                         }
                         //集成要素阈值告警
@@ -261,7 +254,7 @@ public class MqttCallbackHandler extends AbstractHandler {
      * @param ts     ts
      * @param time   时间
      */
-    private void deviceIntegrationsAlarm(KwsDevice device, Long ts, String time) {
+    private void deviceIntegrationsAlarm(KwsDevice device, Long ts, String time) throws IOException {
         Map<String, SystemDict> dictByDictCode = commonService.getDictByDictCode(DictEnum.INTEGRATION_UNIT);
 //        List<KwsDeviceIntegration> deviceIntegrations = deviceIntegrationMapper.selectList(
 //                new LambdaQueryWrapper<KwsDeviceIntegration>()
@@ -333,7 +326,7 @@ public class MqttCallbackHandler extends AbstractHandler {
                                 time = com.sckw.excel.utils.DateUtil.format(localDateTimes);
                             }
                             templateParam.put("time", time);
-                            templateParam.put("level", level.toString());
+                            templateParam.put("level", AlarmLevelEnum.getChineseName(level.intValue()));
                             templateParam.put("name", device.getName());
 //                            templateParam.put("value", itemValueAndUnit);
                             unit = dictByDictCode.get(deviceIntegration.getUnit()) == null ? deviceIntegration.getUnit() : dictByDictCode.get(deviceIntegration.getUnit()).getDescription();
@@ -360,13 +353,13 @@ public class MqttCallbackHandler extends AbstractHandler {
                             emailAddress = emailList.stream().map(KwsAlarmInfo::getValueDesc).distinct().collect(Collectors.toList());
                         }
                         if (!CollectionUtils.isEmpty(emailAddress)) {
-                            Map<String, Object> templateParam = new HashMap<>();
+                            Map<String, String> templateParam = new HashMap<>();
                             if (StringUtils.isBlank(time)) {
                                 LocalDateTime localDateTime = Instant.ofEpochSecond(ts).atZone(ZoneId.systemDefault()).toLocalDateTime();
                                 time = com.sckw.excel.utils.DateUtil.format(localDateTime);
                             }
                             templateParam.put("company", smsTemplateConfig.getDataAlarm().getSignName());
-                            templateParam.put("level", level.toString());
+                            templateParam.put("level",AlarmLevelEnum.getName(level.intValue()));
                             templateParam.put("name", device.getName());
                             templateParam.put("time", time);
                             templateParam.put("project", vo == null ? null : vo.getProjectName());
@@ -379,8 +372,8 @@ public class MqttCallbackHandler extends AbstractHandler {
                             templateParam.put("value", String.valueOf(bd) + unit);
                             templateParam.put("type", AlarmTitleEnum.ALARM_TITLE_TWO.getName());
                             templateParam.put("element", deviceIntegration.getIntegrationName());
-                            commonService.pushEmailMessage(emailAddress
-                                    , templateParam);
+                            commonService.pushHtmlEmailMessage(emailAddress
+                                    , templateParam, "设备告警通知邮件");
                         }
                     }
                 }

+ 2 - 5
slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/consumer/MqttDeviceCallbackHandler.java

@@ -6,10 +6,7 @@ import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.sckw.core.model.constant.NumberConstant;
-import com.sckw.core.model.enums.AlarmTitleEnum;
-import com.sckw.core.model.enums.AlarmTypeEnum;
-import com.sckw.core.model.enums.DictEnum;
-import com.sckw.core.model.enums.DictItemEnum;
+import com.sckw.core.model.enums.*;
 import com.sckw.core.utils.CollectionUtils;
 import com.sckw.core.utils.IdWorker;
 import com.sckw.core.utils.StringUtils;
@@ -161,7 +158,7 @@ public class MqttDeviceCallbackHandler extends AbstractHandler {
                         String time = DateUtil.format(localDateTime, "yyyy-MM-dd HH:mm:ss");
                         Map<String, String> templateParam = new HashMap<>();
                         templateParam.put("time",time);
-                        templateParam.put("level",level.toString());
+                        templateParam.put("level", AlarmLevelEnum.getName(level.intValue()));
                         templateParam.put("name",device.getName());
                         templateParam.put("value",itemValue);
 //                        commonService.pushSmsMessage(pushToPhone,

+ 50 - 0
slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/controller/CommonController.java

@@ -1,16 +1,28 @@
 package com.sckw.slope.detection.controller;
 
+import com.alibaba.fastjson2.JSONObject;
+import com.sckw.core.model.enums.AlarmTitleEnum;
 import com.sckw.core.web.response.HttpResult;
 import com.sckw.slope.detection.common.config.SmsTemplateConfig;
+import com.sckw.slope.detection.model.param.SmsMessageParam;
 import com.sckw.slope.detection.model.vo.QueryDictTypePageReqVo;
 import com.sckw.slope.detection.service.CommonService;
+import com.sckw.slope.detection.service.api.DetectionApiService;
+import com.sckw.slope.detection.utils.MailUtil;
 import jakarta.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 /**
  * @author lfdc
  * @description 公共字典查询
@@ -91,4 +103,42 @@ public class CommonController {
 ////                , templateParam);
 //        return HttpResult.ok();
 //    }
+
+    @Value("${sms.url}")
+    private String smsUrl;
+
+    @Autowired
+    DetectionApiService detectionApiService;
+
+    @RequestMapping(value = "/pushEmailMessageDemo", method = RequestMethod.GET)
+    public HttpResult pushEmailMessageDemo() throws IOException {
+        Map<String, String> templateParam = new HashMap<>();
+        templateParam.put("company", smsTemplateConfig.getDataAlarm().getSignName());
+        templateParam.put("level", "一级");
+        templateParam.put("name", "设备名称");
+        templateParam.put("time", "2023-12-05 15:00:00");
+        templateParam.put("project", "第一个项目");
+//                            templateParam.put("value", itemValue);
+        templateParam.put("value", "29.5645E");
+        templateParam.put("element", "element");
+        templateParam.put("type", AlarmTitleEnum.ALARM_TITLE_TWO.getName() + "告警");
+        String path = CommonController.class.getResource("/template/Email.html").getPath();
+        String htmlContent = MailUtil.readResourceFile(path);
+        // 创建一个Map来存储要替换的值
+        // 替换HTML中的占位符
+        String replacedHtml = MailUtil.replacePlaceholders(htmlContent, templateParam);
+        List<String> emailAddress = new ArrayList<>();
+        emailAddress.add("1573035851@qq.com");
+        SmsMessageParam param = new SmsMessageParam();
+        param.setCode("");
+        param.setType(4);
+        param.setPushTo(emailAddress);
+        SmsMessageParam.Content content = new SmsMessageParam.Content();
+        content.setSubject("设备告警通知邮件");
+        content.setText(replacedHtml);
+        content.setComplexFlag(true);
+        param.setContent(content);
+        JSONObject jsonObject = detectionApiService.pushEmailMessage(smsUrl, JSONObject.toJSONString(param));
+        return HttpResult.ok();
+    }
 }

+ 4 - 0
slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/model/param/SmsMessageParam.java

@@ -45,5 +45,9 @@ public class SmsMessageParam implements Serializable {
          */
         private String subject;
         private String text;
+        /**
+         * 当文本邮件  可以不传递  html时 赋值为 true
+         */
+        private boolean complexFlag;
     }
 }

+ 34 - 3
slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/service/CommonService.java

@@ -11,6 +11,7 @@ import com.sckw.core.model.enums.MessageLogEnum;
 import com.sckw.core.model.page.PageRes;
 import com.sckw.core.utils.IdWorker;
 import com.sckw.core.web.response.PhpResult;
+import com.sckw.excel.utils.DateUtil;
 import com.sckw.slope.detection.common.config.SmsTemplateConfig;
 import com.sckw.slope.detection.dao.mysql.KwsDeviceReferenceMapper;
 import com.sckw.slope.detection.dao.mysql.KwsDictMapper;
@@ -26,6 +27,7 @@ import com.sckw.slope.detection.model.param.SmsMessageParam;
 import com.sckw.slope.detection.model.vo.DeviceIntegrationVo;
 import com.sckw.slope.detection.model.vo.QueryDictTypePageReqVo;
 import com.sckw.slope.detection.service.api.DetectionApiService;
+import com.sckw.slope.detection.utils.MailUtil;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +35,7 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cloud.context.config.annotation.RefreshScope;
 import org.springframework.stereotype.Service;
 
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.text.DecimalFormat;
 import java.text.ParseException;
@@ -434,7 +437,7 @@ public class CommonService {
      * @param templateParam
      */
 //    @Async
-    public void pushEmailMessage(List<String> emailAddress, Map<String, Object> templateParam) {
+    public void pushEmailMessage(List<String> emailAddress, Map<String, Object> templateParam,String title) {
         SmsMessageParam param = new SmsMessageParam();
         param.setCode("");
         param.setType(4);
@@ -463,12 +466,40 @@ public class CommonService {
                         "    所属项目:${project};\n" +
                         "    设备告警内容:${element};\n" +
                         "    触发告警值:${value};\n" +
-                        "    请在收到邮件后尽快处理,或查阅操作手册文档,确认告警通知相关信息,以避免造成相关损失。";
+                        "    请在收到邮件后尽快处理,或查阅操作手册文档,确认告警通知相关信息,以避免造成相关损失。\n" +
+                        "                                                                 ${createTime}";
+        templateParam.put("createTime", DateUtil.getDateString(new Date(),"yyyy-MM-dd HH:mm:ss"));
         String text = com.sckw.core.utils.StringUtils.replaceTextVar(templateStr, templateParam);
-        content.setSubject("设备告警通知邮件");
+        content.setSubject(title);
         content.setText(text);
         param.setContent(content);
         JSONObject jsonObject = detectionApiService.pushEmailMessage(smsUrl, JSONObject.toJSONString(param));
         log.info("邮件发送 请求参数:{} 返回结果:{}", JSONObject.toJSONString(param), jsonObject.toString());
     }
+
+
+    /**
+     * 推送邮箱
+     *
+     * @param emailAddress
+     * @param templateParam
+     */
+    public void pushHtmlEmailMessage(List<String> emailAddress, Map<String, String> templateParam,String title) throws IOException {
+        SmsMessageParam param = new SmsMessageParam();
+        param.setCode("");
+        param.setType(4);
+        param.setPushTo(emailAddress);
+        SmsMessageParam.Content content = new SmsMessageParam.Content();
+        String path = CommonService.class.getResource("/template/Email.html").getPath();
+        String htmlContent = MailUtil.readResourceFile(path);
+        templateParam.put("createTime", DateUtil.getDateString(new Date(),"yyyy-MM-dd HH:mm:ss"));
+        String replacedHtml = MailUtil.replacePlaceholders(htmlContent, templateParam);
+        content.setSubject(title);
+        content.setText(replacedHtml);
+        content.setComplexFlag(true);
+        param.setContent(content);
+        JSONObject jsonObject = detectionApiService.pushEmailMessage(smsUrl, JSONObject.toJSONString(param));
+        log.info("邮件发送 请求参数:{} 返回结果:{}", JSONObject.toJSONString(param), jsonObject.toString());
+    }
+
 }

+ 1 - 2
slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/service/api/DetectionApiService.java

@@ -211,8 +211,7 @@ public class DetectionApiService {
                 .build();
         try {
             Response response = client.newCall(request).execute();
-//            System.out.println(JSONObject.toJSONString(response));
-            System.out.println("请求响应状态码:" + response.code());
+            System.out.println(JSONObject.toJSONString(response));
             if (response.isSuccessful()) {
                 String responseBody = response.body().string();
                 log.info("响应信息:{}", responseBody);

+ 79 - 59
slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/service/task/DeviceAlarmTaskService.java

@@ -2,6 +2,7 @@ package com.sckw.slope.detection.service.task;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.sckw.core.model.constant.NumberConstant;
+import com.sckw.core.model.enums.AlarmLevelEnum;
 import com.sckw.core.model.enums.AlarmTitleEnum;
 import com.sckw.core.model.enums.AlarmTypeEnum;
 import com.sckw.core.utils.IdWorker;
@@ -13,12 +14,11 @@ import com.sckw.slope.detection.dao.mysql.KwsAlarmMapper;
 import com.sckw.slope.detection.dao.mysql.KwsDeviceMapper;
 import com.sckw.slope.detection.dao.mysql.KwsProjectDeviceMapper;
 import com.sckw.slope.detection.dao.tdengine.DevicesMapper;
-import com.sckw.slope.detection.model.dos.mysql.KwsAlarm;
-import com.sckw.slope.detection.model.dos.mysql.KwsAlarmDetail;
-import com.sckw.slope.detection.model.dos.mysql.KwsDevice;
-import com.sckw.slope.detection.model.dos.mysql.KwsProjectDevice;
+import com.sckw.slope.detection.model.dos.mysql.*;
 import com.sckw.slope.detection.model.dos.tdengine.Devices;
+import com.sckw.slope.detection.model.vo.ProjectVo;
 import com.sckw.slope.detection.service.CommonService;
+import com.sckw.slope.detection.service.repository.KwsAlarmInfoRepository;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,8 +29,12 @@ import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
+import java.io.IOException;
+import java.time.Instant;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @author lfdc
@@ -72,7 +76,7 @@ public class DeviceAlarmTaskService {
     @TraceLog(description = "设备告警数据")
     @Scheduled(cron = "0 0 */4 * * ? ")
 //    @Scheduled(cron = "* * * * * *")
-    public void deviceAlarm() {
+    public void deviceAlarm() throws IOException {
 //        if ("false".equals(deviceEnable)) {
 //            return;
 //        }
@@ -97,24 +101,8 @@ public class DeviceAlarmTaskService {
 //                        // todo 暂未设备告警电话,设备告警不推送短信
                         device.setOnline(1);
                         deviceMapper.updateById(device);
-                        //设备离线
-                        String servicePhones = device.getServicePhones();
-                        List<String> pushToPhone = new ArrayList<>();
-                        if (StringUtils.isNotBlank(servicePhones)) {
-                            pushToPhone = com.sckw.core.utils.StringUtils.splitStrToList(servicePhones, String.class);
-                        }
-                        if (!CollectionUtils.isEmpty(pushToPhone) && pushToPhone.size() > 0) {
-                            Map<String, String> templateParam = new HashMap<>();
-                            String dateString = DateUtil.getDateString(new Date(), "yyyy-MM-dd HH:mm:ss");
-                            templateParam.put("time", dateString);
-                            templateParam.put("level", "1");
-                            templateParam.put("name", device.getName());
-                            templateParam.put("value", "");
-                            commonService.pushSmsMessage(pushToPhone
-                                    , smsTemplateConfig.getDataAlarm().getSmsCode()
-                                    , smsTemplateConfig.getDataAlarm().getSignName()
-                                    , templateParam);
-                        }
+                        pushDeviceAlarmSmsMessage(device);
+                        pushDeviceAlarmEmailMessage(device.getMountainId(), 1, device, new Date().getTime());
                     }
                     if (devices == null) {
 //                        continue;
@@ -122,24 +110,8 @@ public class DeviceAlarmTaskService {
 //                        // todo 暂未设备告警电话,设备告警不推送短信
                         device.setOnline(1);
                         deviceMapper.updateById(device);
-                        //设备离线
-                        String servicePhones = device.getServicePhones();
-                        List<String> pushToPhone = new ArrayList<>();
-                        if (StringUtils.isNotBlank(servicePhones)) {
-                            pushToPhone = com.sckw.core.utils.StringUtils.splitStrToList(servicePhones, String.class);
-                        }
-                        if (!CollectionUtils.isEmpty(pushToPhone) && pushToPhone.size() > 0) {
-                            Map<String, String> templateParam = new HashMap<>();
-                            String dateString = DateUtil.getDateString(new Date(), "yyyy-MM-dd HH:mm:ss");
-                            templateParam.put("time", dateString);
-                            templateParam.put("level", "1");
-                            templateParam.put("name", device.getName());
-                            templateParam.put("value", "");
-                            commonService.pushSmsMessage(pushToPhone
-                                    , smsTemplateConfig.getDataAlarm().getSmsCode()
-                                    , smsTemplateConfig.getDataAlarm().getSignName()
-                                    , templateParam);
-                        }
+                        pushDeviceAlarmSmsMessage(device);
+                        pushDeviceAlarmEmailMessage(device.getMountainId(), 1, device, new Date().getTime());
                     }
                     //判断是否满足设备超时离线报警
                     try {
@@ -152,6 +124,69 @@ public class DeviceAlarmTaskService {
         }
     }
 
+    @Autowired
+    KwsAlarmInfoRepository alarmInfoRepository;
+
+    /**
+     * 设备告警邮件
+     *
+     * @param mountainId
+     * @param level
+     * @param device
+     * @param ts
+     */
+    private void pushDeviceAlarmEmailMessage(String mountainId, Integer level, KwsDevice device, Long ts) throws IOException {
+        /**邮件推送*/
+        List<KwsAlarmInfo> emailList = alarmInfoRepository.list(new LambdaQueryWrapper<KwsAlarmInfo>()
+                .eq(KwsAlarmInfo::getMountainId, mountainId)
+                .eq(KwsAlarmInfo::getLevel, level.intValue())
+                .eq(KwsAlarmInfo::getType, NumberConstant.TWO)
+                .eq(KwsAlarmInfo::getDelFlag, NumberConstant.ZERO)
+        );
+        List<String> emailAddress = new ArrayList<>();
+        if (!com.sckw.core.utils.CollectionUtils.isEmpty(emailList)) {
+            emailAddress = emailList.stream().map(KwsAlarmInfo::getValueDesc).distinct().collect(Collectors.toList());
+        }
+        if (!com.sckw.core.utils.CollectionUtils.isEmpty(emailAddress)) {
+            Map<String, String> templateParam = new HashMap<>();
+            LocalDateTime localDateTime = Instant.ofEpochSecond(ts).atZone(ZoneId.systemDefault()).toLocalDateTime();
+            templateParam.put("company", smsTemplateConfig.getDataAlarm().getSignName());
+            templateParam.put("level", AlarmLevelEnum.getName(level));
+            templateParam.put("name", device.getName());
+            templateParam.put("time", cn.hutool.core.date.DateUtil.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"));
+            ProjectVo vo = projectDeviceMapper.selectProjectByDeviceId(device.getId());
+            templateParam.put("project", vo == null ? null : vo.getProjectName());
+            templateParam.put("value",  cn.hutool.core.date.DateUtil.format(localDateTime, "yyyy-MM-dd HH:mm:ss"));
+            templateParam.put("type", "设备离线告警");
+            templateParam.put("element", AlarmTitleEnum.ALARM_TITLE_ONE.getName());
+//            commonService.pushEmailMessage(emailAddress
+//                    , templateParam, "设备告警通知邮件");
+            commonService.pushHtmlEmailMessage(emailAddress
+                    , templateParam, "设备告警通知邮件");
+        }
+    }
+
+    private void pushDeviceAlarmSmsMessage(KwsDevice device) {
+        //设备离线
+        String servicePhones = device.getServicePhones();
+        List<String> pushToPhone = new ArrayList<>();
+        if (StringUtils.isNotBlank(servicePhones)) {
+            pushToPhone = com.sckw.core.utils.StringUtils.splitStrToList(servicePhones, String.class);
+        }
+        if (!CollectionUtils.isEmpty(pushToPhone) && pushToPhone.size() > 0) {
+            Map<String, String> templateParam = new HashMap<>();
+            String dateString = DateUtil.getDateString(new Date(), "yyyy-MM-dd HH:mm:ss");
+            templateParam.put("time", dateString);
+            templateParam.put("level", "一");
+            templateParam.put("name", device.getName());
+            templateParam.put("value", "");
+            commonService.pushSmsMessage(pushToPhone
+                    , smsTemplateConfig.getDataAlarm().getSmsCode()
+                    , smsTemplateConfig.getDataAlarm().getSignName()
+                    , templateParam);
+        }
+    }
+
     /**
      * 去除告警历史数据
      */
@@ -171,7 +206,7 @@ public class DeviceAlarmTaskService {
         }
     }
 
-    private void checkDeviceAlarm(String deviceCode, KwsDevice device, Devices devices) {
+    private void checkDeviceAlarm(String deviceCode, KwsDevice device, Devices devices) throws IOException {
         Date deviceTime = devices.getTs();
         Date date = new Date();
         long diff = Math.abs(date.getTime() - deviceTime.getTime());
@@ -182,23 +217,8 @@ public class DeviceAlarmTaskService {
             //设备离线
             device.setOnline(1);
             deviceMapper.updateById(device);
-            String servicePhones = device.getServicePhones();
-            List<String> pushToPhone = new ArrayList<>();
-            if (StringUtils.isNotBlank(servicePhones)) {
-                pushToPhone = com.sckw.core.utils.StringUtils.splitStrToList(servicePhones, String.class);
-            }
-            if (!CollectionUtils.isEmpty(pushToPhone) && pushToPhone.size() > 0) {
-                Map<String, String> templateParam = new HashMap<>();
-                String dateString = DateUtil.getDateString(deviceTime, "yyyy-MM-dd HH:mm:ss");
-                templateParam.put("time", dateString);
-                templateParam.put("level", "1");
-                templateParam.put("name", device.getName());
-                templateParam.put("value", "");
-                commonService.pushSmsMessage(pushToPhone
-                        , smsTemplateConfig.getDataAlarm().getSmsCode()
-                        , smsTemplateConfig.getDataAlarm().getSignName()
-                        , templateParam);
-            }
+            pushDeviceAlarmSmsMessage(device);
+            pushDeviceAlarmEmailMessage(device.getMountainId(), 1, device, deviceTime.getTime());
         } else {
             device.setOnline(0);
             deviceMapper.updateById(device);

+ 274 - 0
slope-modules/slope-detection/src/main/java/com/sckw/slope/detection/utils/MailUtil.java

@@ -0,0 +1,274 @@
+package com.sckw.slope.detection.utils;
+
+/**
+ * @author lfdc
+ * @description
+ * @date 2023-12-08 08:12:53
+ */
+
+import jakarta.mail.Message;
+import jakarta.mail.Session;
+import jakarta.mail.Transport;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.ComponentScan;
+
+import java.io.*;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * 发送邮件工具类
+ */
+@Slf4j
+@ComponentScan
+public class MailUtil {
+
+    public static String buildContent(HashMap<String,String> parameterMap) throws IOException {
+        String reminderDate = parameterMap.get("reminderDate");
+        //加载邮件html模板
+        File fileName = new File("Email.html");
+        BufferedReader fileReader = new BufferedReader(new FileReader(fileName));
+
+        StringBuffer buffer = new StringBuffer();
+        String line = "";
+        try {
+            while ((line = fileReader.readLine()) != null) {
+                buffer.append(line);
+            }
+        } catch (Exception e) {
+            log.error("读取文件失败,fileName:{}", fileName, e);
+        } finally {
+            fileReader.close();
+        }
+
+        Date date = new Date();
+        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String array[] = {"梅奥心磁","100",sdf.format(date),"serverId"};
+
+        //替换参数
+        String htmlText = MessageFormat.format(buffer.toString(), array);
+        return htmlText;
+    }
+
+    /**
+     * 邮件发送
+     * @param mailHost 邮件服务地址
+     * @param fromMail 发件人
+     * @param fromName 发件人名
+     * @param fromMailPwd 发件人密码
+     * @param toMails 收件人,多个用英文逗号分隔
+     * @param mailTitle 邮件标题
+     * @param mailContent 邮件内容
+     * @throws Exception
+     */
+    public static void sendMail(String mailHost, String fromMail, String fromName, String fromMailPwd,
+                                String toMails, String mailTitle, String mailContent) throws Exception {
+        String[] toMailArr = null;
+        if (toMails != null && !toMails.equals("")) {
+            toMailArr = toMails.split(",");
+        } else {
+            throw new Exception("邮件发送人不能为空");
+        }
+
+        // 邮件属性信息
+        Properties props = new Properties();
+        props.put("mail.host", mailHost);
+        props.put("mail.transport.protocol", "smtp");
+        props.put("mail.smtp.auth", "true");
+
+        Session session = Session.getInstance(props); // 根据属性新建一个邮件会话
+        //session.setDebug(true); // 是否打印调试信息
+        toMailArr = toMails.split(",");
+        for (String to : toMailArr) {
+            MimeMessage message = new MimeMessage(session); // 由邮件会话新建一个消息对象
+            message.setFrom(new InternetAddress(fromMail));// 设置发件人的地址
+            message.setRecipient(Message.RecipientType.TO, new InternetAddress(to, fromName));// 设置收件人,并设置其接收类型为TO
+            message.setSubject(mailTitle);// 设置标题
+            message.setContent(mailContent, "text/html;charset=UTF-8"); // 设置邮件内容类型为html
+            message.setSentDate(new Date());// 设置发信时间
+            message.saveChanges();// 存储邮件信息
+
+            // 发送邮件
+            Transport transport = session.getTransport();
+            transport.connect(fromMail, fromMailPwd);
+            transport.sendMessage(message, message.getAllRecipients());
+            transport.close();
+        }
+    }
+
+    /**
+     * 邮件发送(群发)
+     * @param mailHost 邮件服务地址
+     * @param fromMail 发件人
+     * @param fromName 发件人名
+     * @param fromMailPwd 发件人密码
+     * @param toMails 收件人,多个用英文逗号分隔
+     * @param mailTitle 邮件标题
+     * @param mailContent 邮件内容
+     * @throws Exception
+     */
+    public static void sendGroupMail(String mailHost, String fromMail, String fromName, String fromMailPwd,
+                                     String toMails, String mailTitle, String mailContent) throws Exception {
+        String[] toMailArr = null;
+        if (toMails != null && !toMails.equals("")) {
+            toMailArr = toMails.split(",");
+        } else {
+            throw new Exception("邮件发送人不能为空");
+        }
+
+        // 邮件属性信息
+        Properties props = new Properties();
+        props.put("mail.host", mailHost);
+        props.put("mail.transport.protocol", "smtp");
+        props.put("mail.smtp.auth", "true");
+
+
+        Session session = Session.getInstance(props); // 根据属性新建一个邮件会话
+        //session.setDebug(true); // 是否打印调试信息
+        MimeMessage message = new MimeMessage(session); // 由邮件会话新建一个消息对象
+        message.setFrom(new InternetAddress(fromMail)); // 设置发件人的地址
+        InternetAddress[] sendTo = new InternetAddress[toMailArr.length];
+        for (int i = 0; i < toMailArr.length; i++) {
+            sendTo[i] = new InternetAddress(toMailArr[i], fromName);
+        }
+        message.setRecipients(Message.RecipientType.TO, sendTo); // 设置收件人,并设置其接收类型为TO
+        message.setSubject(mailTitle); // 设置标题
+        message.setContent(mailContent, "text/html;charset=UTF-8"); // 设置邮件内容类型为html
+        message.setSentDate(new Date()); // 设置发信时间
+        message.saveChanges(); // 存储邮件信息
+
+        // 发送邮件
+        Transport transport = session.getTransport();
+        transport.connect(fromMail, fromMailPwd);
+        transport.sendMessage(message, message.getAllRecipients());
+        transport.close();
+    }
+
+    /**
+     * 读取html文件为String
+     * @param htmlFileName
+     * @return
+     * @throws Exception
+     */
+    public static String readHtmlToString(String htmlFileName) throws Exception{
+        InputStream is = null;
+        Reader reader = null;
+        try {
+            is = MailUtil.class.getClassLoader().getResourceAsStream(htmlFileName);
+            if (is ==  null) {
+                throw new Exception("未找到模板文件");
+            }
+            reader = new InputStreamReader(is, "UTF-8");
+            StringBuilder sb = new StringBuilder();
+            int bufferSize = 1024;
+            char[] buffer = new char[bufferSize];
+            int length = 0;
+            while ((length = reader.read(buffer, 0, bufferSize)) != -1){
+                sb.append(buffer, 0, length);
+            }
+            return sb.toString();
+        } finally {
+            try {
+                if (is != null) {
+                    is.close();
+                }
+            } catch (IOException e) {
+                log.error("关闭io流异常", e);
+            }
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch ( IOException e) {
+                log.error("关闭io流异常", e);
+            }
+        }
+    }
+    public static String readFile(String filePath) {
+        StringBuilder content = new StringBuilder();
+        File fileName = new File(filePath);
+        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                content.append(line).append("\n");
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return content.toString();
+    }
+
+    /**
+     *  读取本地html文件里的html代码
+     * @return
+     */
+    public static String toHtmlString(String filePath) {
+        File file = new File(filePath);
+        // 获取HTML文件流
+        StringBuffer htmlSb = new StringBuffer();
+        try {
+            BufferedReader br = new BufferedReader(new InputStreamReader(
+                    new FileInputStream(file), "UTF-8"));
+            while (br.ready()) {
+                htmlSb.append(br.readLine());
+            }
+            br.close();
+            // 删除临时文件
+            //file.delete();
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        // HTML文件字符串
+        String htmlStr = htmlSb.toString();
+        // 返回经过清洁的html文本
+        return htmlStr;
+    }
+
+    public static String readResourceFile(String filePath) throws IOException {
+//        ClassLoader classLoader = MailUtil.class.getClassLoader();
+//        File file = new File(classLoader.getResource(filePath).getFile());
+        File file = new File(filePath);
+//        Document document = Jsoup.parse(file, "UTF-8");
+        StringBuilder content = new StringBuilder();
+//        InputStream inputStream = MailUtil.class.getClassLoader().getResourceAsStream(filePath);
+        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
+            String line;
+            while ((line = br.readLine()) != null) {
+                content.append(line).append("\n");
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return content.toString();
+    }
+
+    /**
+     * 将修改后的内容写回HTML文件
+     * @param filePath
+     * @param content
+     */
+    public static void writeFile(String filePath, String content) {
+        try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath))) {
+            bw.write(content);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    public static String replacePlaceholders(String template, Map<String, String> valueMap) {
+        // 遍历Map,替换模板中的占位符
+        for (Map.Entry<String, String> entry : valueMap.entrySet()) {
+            String placeholder = "{" + entry.getKey() + "}";
+            String replacement = entry.getValue();
+            template = template.replace(placeholder, replacement);
+        }
+        return template;
+    }
+}

+ 1 - 1
slope-modules/slope-detection/src/main/resources/bootstrap.yml

@@ -6,7 +6,7 @@ spring:
     name: slope-detection
 #    name: slope-detection
   profiles:
-    active: sky
+    active: lfdc
     #active: ${DEPLOY_MODE:dev}
   main:
     allow-bean-definition-overriding: true

+ 127 - 0
slope-modules/slope-detection/src/main/resources/template/Email.html

@@ -0,0 +1,127 @@
+<html lang="zh-CN">
+	<head>
+		<meta charset="UTF-8" />
+        		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
+        		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<title>Document</title>
+	</head>
+	<body>
+	<!-- <div class="title-box">
+    			<div class="title">www.saas.com</div>
+    		</div>
+-->
+    		<div class="content">
+    			<div class="content-box">
+    				<div class="top-box">
+    					<div class="top-img">
+    						<img src="https://kaiwu-saas.oss-cn-chengdu.aliyuncs.com/kll/uploads/20231208/96d6f2e7e1f705ab5e59c84a6dc009b21702019251237.png" alt=""/>
+    					</div>
+    					<div class="top-text">
+						<div class="text-title">设备数值告警通知</div>
+						<div class="p1">发件人:四川开物信息有限公司</div>
+					</div>
+				</div>
+				<div class="content-c">
+					<div class="content-c-title">正文</div>
+					<div class="text-box-title">尊敬的开物云ECO用户</div>
+					<div class="text-p">
+                        <!--&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-->在北京时间的<b>{time}</b>,监控到了
+                        <b>{level}</b>告警,告警类型为<b>{type}。</b>
+					</div>
+					<div class="text-p">告警设备名称:<b>{name};</b></div>
+					<div class="text-p">设备所属项目:<b>{project};</b></div>
+					<div class="text-p">触发告警内容:<b>{element};</b></div>
+					<div class="text-p">触发告警值:<b>{value};</b></div>
+					<div class="text-p">
+						请在收到邮件后尽快处理,或者查阅操作手册文档,确认告警和通知相关信息,以避免制造成相关损失。
+					</div>
+                    <div class="text-p2">
+<!--						&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-->
+                        {createTime}
+					</div>
+				</div>
+			</div>
+		</div>
+		<style>
+			body {
+				margin: 0;
+				box-sizing: border-box;
+			}
+			.title-box {
+				width: 100%;
+				height: 40px;
+				background-color: #000;
+				text-align: right;
+				font-size: 16px;
+				color: #fff;
+				line-height: 40px;
+			}
+			.title {
+				margin-right: 20px;
+			}
+			.content {
+				width: 100%;
+				background-color: #f2f2f2;
+				border: 1px solid #a6a6a6;
+				position: relative;
+			}
+			.content-box {
+				margin: 20px auto;
+				width: 500px;
+				height: 600px;
+				padding: 10px;
+				border: 1px solid #797979;
+			}
+			.top-box {
+				display: flex;
+				width: 100%;
+			}
+			.top-img {
+				width: 100px;
+				height: 100px;
+			}
+			.top-img img {
+				width: 100%;
+				height: 100%;
+			}
+			.top-text {
+				width: calc(100% - 100px);
+				height: 100px;
+				padding-left: 20px;
+			}
+			.top-text .text-title {
+				font-size: 20px;
+				font-weight: bold;
+				text-align: center;
+				margin-bottom: 20px;
+			}
+			.top-text .p1 {
+				font-size: 16px;
+				line-height: 30px;
+			}
+			.content-c {
+			}
+			.content-c-title {
+				width: 100%;
+				padding: 20px;
+				border-bottom: 1px solid #e3e3e3;
+				margin-bottom: 20px;
+			}
+			.text-box-title {
+				margin-bottom: 20px;
+			}
+			.text-p {
+				margin-left: 20px;
+                text-indent: 2em;
+			}
+            .text-p2 {
+				margin-left: 20px;
+                text-indent: 20em;
+               <!--color: red;-->
+			}
+            .text-p b{
+				color: red;
+			}
+		</style>
+	</body>
+</html>

+ 648 - 0
slope-modules/slope-detection/src/main/resources/template/EmailTemplate.html

@@ -0,0 +1,648 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"
+      style="width:100%;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;padding:0;Margin:0">
+<head>
+    <meta charset="UTF-8">
+    <meta content="width=device-width, initial-scale=1" name="viewport">
+    <meta name="x-apple-disable-message-reformatting">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta content="telephone=no" name="format-detection">
+    <title>Reminder : Renew License ETE Platform</title>
+    <!--[if (mso 16)]>
+    <style type="text/css">'{'
+        text-decoration: none;'}'
+    </style>
+    <![endif]-->
+    <!--[if gte mso 9]>
+    <style>sup
+
+    '{'
+    font-size:
+
+    100
+    %
+    !important
+    ;
+    '}'</style><![endif]-->
+    <!--[if gte mso 9]>
+    <xml>
+        <o:OfficeDocumentSettings>
+            <o:AllowPNG></o:AllowPNG>
+            <o:PixelsPerInch>96</o:PixelsPerInch>
+        </o:OfficeDocumentSettings>
+    </xml>
+    <![endif]-->
+    <!--[if !mso]><!-- -->
+    <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700,700i" rel="stylesheet">
+    <!--<![endif]-->
+    <style type="text/css">
+        @media only screen and (max-width: 600px) '{'p, ul li, ol li, a
+
+        '{' font-size:
+
+        16px
+
+        !important; line-height:
+
+        150%!important '}' h1
+
+        '{' font-size:
+
+        32px
+
+        !important; text-align:center
+
+        ; line-height:
+
+        120%!important '}' h2
+
+        '{' font-size:
+
+        26px
+
+        !important; text-align:center
+
+        ; line-height:
+
+        120%!important '}' h3
+
+        '{' font-size:
+
+        20px
+
+        !important; text-align:center
+
+        ; line-height:
+
+        120%!important '}' h1 a
+
+        '{' font-size:
+
+        32px
+
+        !important '}' h2 a
+
+        '{' font-size:
+
+        26px
+
+        !important '}' h3 a
+
+        '{' font-size:
+
+        20px
+
+        !important '}' .es-menu td a
+
+        '{' font-size:
+
+        16px
+
+        !important '}' .es-header-body p, .es-header-body ul li, .es-header-body ol li, .es-header-body a
+
+        '{' font-size:
+
+        16px
+
+        !important '}' .es-footer-body p, .es-footer-body ul li, .es-footer-body ol li, .es-footer-body a
+
+        '{' font-size:
+
+        16px
+
+        !important '}' .es-infoblock p, .es-infoblock ul li, .es-infoblock ol li, .es-infoblock a
+
+        '{' font-size:
+
+        12px
+
+        !important '}' *[class="gmail-fix"]
+
+        '{' display:none
+
+        !important '}' .es-m-txt-c, .es-m-txt-c h1, .es-m-txt-c h2, .es-m-txt-c h3
+
+        '{' text-align:center
+
+        !important '}' .es-m-txt-r, .es-m-txt-r h1, .es-m-txt-r h2, .es-m-txt-r h3
+
+        '{' text-align:right
+
+        !important '}' .es-m-txt-l, .es-m-txt-l h1, .es-m-txt-l h2, .es-m-txt-l h3
+
+        '{' text-align:left
+
+        !important '}' .es-m-txt-r img, .es-m-txt-c img, .es-m-txt-l img
+
+        '{' display:inline
+
+        !important '}' .es-button-border
+
+        '{' display:inline-block
+
+        !important '}' a.es-button
+
+        '{' font-size:
+
+        16px
+
+        !important; display:inline-block
+
+        !important; border-width:
+
+        15px
+
+        30px
+
+        15px
+
+        30px
+
+        !important '}' .es-btn-fw
+
+        '{' border-width:
+
+        10px
+
+        0px
+
+        !important; text-align:center
+
+        !important '}' .es-adaptive table, .es-btn-fw, .es-btn-fw-brdr, .es-left, .es-right
+
+        '{' width:
+
+        100%!important '}' .es-content table, .es-header table, .es-footer table, .es-content, .es-footer, .es-header
+
+        '{' width:
+
+        100%!important; max-width:
+
+        600px
+
+        !important '}' .es-adapt-td
+
+        '{' display:block
+
+        !important; width:
+
+        100%!important '}' .adapt-img
+
+        '{' width:
+
+        100%!important; height:auto
+
+        !important '}' .es-m-p0
+
+        '{' padding:
+
+        0px
+
+        !important '}' .es-m-p0r
+
+        '{' padding-right:
+
+        0px
+
+        !important '}' .es-m-p0l
+
+        '{' padding-left:
+
+        0px
+
+        !important '}' .es-m-p0t
+
+        '{' padding-top:
+
+        0px
+
+        !important '}' .es-m-p0b
+
+        '{' padding-bottom:
+
+        0!important '}' .es-m-p20b
+
+        '{' padding-bottom:
+
+        20px
+
+        !important '}' .es-mobile-hidden, .es-hidden
+
+        '{' display:none
+
+        !important '}' tr.es-desk-hidden, td.es-desk-hidden, table.es-desk-hidden
+
+        '{' display:table-row
+
+        !important; width:auto
+
+        !important; overflow:visible
+
+        !important; float:none
+
+        !important; max-height:inherit
+
+        !important; line-height:inherit
+
+        !important '}' .es-desk-menu-hidden
+
+        '{' display:table-cell
+
+        !important '}' table.es-table-not-adapt, .esd-block-html table
+
+        '{' width:auto
+
+        !important '}' table.es-social
+
+        '{' display:inline-block
+
+        !important '}' table.es-social td
+
+        '{' display:inline-block
+
+        !important '}' '}'
+            #outlook a
+
+        '{'
+            padding:
+
+        0;
+        '}'
+            .ExternalClass
+
+        '{'
+            width:
+
+        100%;
+        '}'
+            .ExternalClass,
+            .ExternalClass p,
+            .ExternalClass span,
+            .ExternalClass font,
+            .ExternalClass td,
+            .ExternalClass div
+
+        '{'
+            line-height:
+
+        100%;
+        '}'
+            .es-button
+
+        '{'
+            mso-style-priority:
+
+        100!important;
+            text-decoration:none
+
+        !important;
+        '}'
+            a[x-apple-data-detectors]
+
+        '{'
+            color:inherit
+
+        !important;
+            text-decoration:none
+
+        !important;
+            font-size:inherit
+
+        !important;
+            font-family:inherit!important;
+            font-weight:inherit!important;
+            line-height:inherit!important;'}'
+            .es-desk-hidden'{'
+            display:none;
+            float:left;
+            overflow:hidden;
+            width:0;
+            max-height:0;
+            line-height:0;
+            mso-hide:all;'}'
+    </style>
+</head>
+<body style="width:100%;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;padding:0;Margin:0">
+<div class="es-wrapper-color" style="background-color:#EEEEEE">
+    <!--[if gte mso 9]>
+    <v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t">
+        <v:fill type="tile" color="#eeeeee"></v:fill>
+    </v:background>
+    <![endif]-->
+    <table class="es-wrapper" width="100%" cellspacing="0" cellpadding="0"
+           style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;padding:0;Margin:0;width:100%;height:100%;background-repeat:repeat;background-position:center top">
+        <tr style="border-collapse:collapse">
+            <td valign="top" style="padding:0;Margin:0">
+                <table class="es-content" cellspacing="0" cellpadding="0" align="center"
+                       style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%">
+                    <tr style="border-collapse:collapse">
+                        <td align="center" style="padding:0;Margin:0">
+                            <table class="es-content-body"
+                                   style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:transparent;width:600px"
+                                   cellspacing="0" cellpadding="0" align="center">
+                                <tr style="border-collapse:collapse">
+                                    <td align="left"
+                                        style="Margin:0;padding-left:10px;padding-right:10px;padding-top:15px;padding-bottom:15px">
+                                        <!--[if mso]>
+                                        <table style="width:580px" cellpadding="0" cellspacing="0">
+                                            <tr>
+                                                <td style="width:282px" valign="top"><![endif]-->
+                                        <table class="es-left" cellspacing="0" cellpadding="0" align="left"
+                                               style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;float:left">
+                                            <tr style="border-collapse:collapse">
+                                                <td align="left" style="padding:0;Margin:0;width:282px">
+                                                </td>
+                                            </tr>
+
+                                            <!--[if mso]></td></tr></table><![endif]-->
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+                </table>
+                <table class="es-content" cellspacing="0" cellpadding="0" align="center"
+                       style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%">
+                    <tr style="border-collapse:collapse"></tr>
+                    <tr style="border-collapse:collapse">
+                        <td align="center" style="padding:0;Margin:0">
+                            <table class="es-header-body"
+                                   style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:#044767;width:600px"
+                                   cellspacing="0" cellpadding="0" bgcolor="#044767" align="center">
+                                <tr style="border-collapse:collapse">
+                                    <td align="left"
+                                        style="Margin:0;padding-top:35px;padding-bottom:35px;padding-left:35px;padding-right:35px">
+                                        <!--[if mso]>
+                                        <table style="width:530px" cellpadding="0" cellspacing="0">
+                                            <tr>
+                                                <td style="width:340px" valign="top"><![endif]-->
+                                        <table class="es-left" cellspacing="0" cellpadding="0" align="left"
+                                               style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;float:left">
+                                            <tr style="border-collapse:collapse">
+                                                <td class="es-m-p0r es-m-p20b" valign="top" align="center"
+                                                    style="padding:0;Margin:0;width:340px">
+                                                    <table width="100%" cellspacing="0" cellpadding="0"
+                                                           role="presentation"
+                                                           style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                                        <tr style="border-collapse:collapse">
+                                                            <td class="es-m-txt-c" align="left"
+                                                                style="padding:0;Margin:0"><h1
+                                                                    style="Margin:0;line-height:36px;mso-line-height-rule:exactly;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;font-size:32px;font-style:normal;font-weight:bold;color:#FFFFFF">
+                                                                ETE Health Platform</h1></td>
+                                                        </tr>
+                                                    </table>
+                                                </td>
+                                            </tr>
+                                        </table>
+                                        <!--[if mso]></td>
+                                    <td style="width:20px"></td>
+                                    <td style="width:170px" valign="top"><![endif]-->
+                                        <table cellspacing="0" cellpadding="0" align="right"
+                                               style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                            <tr class="es-hidden" style="border-collapse:collapse">
+                                                <td class="es-m-p20b" align="left"
+                                                    style="padding:0;Margin:0;width:170px">
+                                                    <table width="100%" cellspacing="0" cellpadding="0"
+                                                           style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                                        <tr style="border-collapse:collapse">
+                                                            <td style="padding:0;Margin:0">
+                                                                <table cellspacing="0" cellpadding="0" align="right"
+                                                                       style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                                                    <tr style="border-collapse:collapse">
+                                                                        <td align="center"
+                                                                            style="padding:0;Margin:0;display:none"></td>
+                                                                    </tr>
+                                                                </table>
+                                                            </td>
+                                                        </tr>
+                                                    </table>
+                                                </td>
+                                            </tr>
+                                        </table>
+                                        <!--[if mso]></td></tr></table><![endif]--></td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+                </table>
+                <table class="es-content" cellspacing="0" cellpadding="0" align="center"
+                       style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%">
+                    <tr style="border-collapse:collapse">
+                        <td align="center" style="padding:0;Margin:0">
+                            <table class="es-content-body" cellspacing="0" cellpadding="0" bgcolor="#ffffff"
+                                   align="center"
+                                   style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:#FFFFFF;width:600px">
+                                <tr style="border-collapse:collapse">
+                                    <td style="Margin:0;padding-bottom:35px;padding-left:35px;padding-right:35px;padding-top:40px;background-color:#F7F7F7"
+                                        bgcolor="#f7f7f7" align="left">
+                                        <table width="100%" cellspacing="0" cellpadding="0"
+                                               style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                            <tr style="border-collapse:collapse">
+                                                <td valign="top" align="center" style="padding:0;Margin:0;width:530px">
+                                                    <table width="100%" cellspacing="0" cellpadding="0"
+                                                           role="presentation"
+                                                           style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                                        <tr style="border-collapse:collapse">
+                                                            <td style="Margin:0;padding-top:20px;padding-bottom:25px;padding-left:35px;padding-right:35px;font-size:0"
+                                                                align="center"><a target="_blank"
+                                                                                  style="-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;font-size:15px;text-decoration:none;color:#ED8E20"><img
+                                                                    src="https://eyetoeyesecurity.info/etelogoblack.png"
+                                                                    alt="ship"
+                                                                    style="display:block;border:0;outline:none;text-decoration:none;-ms-interpolation-mode:bicubic"
+                                                                    title="ship" width="150"></a></td>
+                                                        </tr>
+                                                        <tr style="border-collapse:collapse">
+                                                            <td align="center"
+                                                                style="padding:0;Margin:0;padding-bottom:15px"><h2
+                                                                    style="Margin:0;line-height:36px;mso-line-height-rule:exactly;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;font-size:30px;font-style:normal;font-weight:bold;color:#333333">
+                                                                License will expire in 30 days<br></h2></td>
+                                                        </tr>
+                                                        <tr style="border-collapse:collapse">
+                                                            <td class="es-m-txt-l" align="left"
+                                                                style="padding:0;Margin:0;padding-top:20px"><h3
+                                                                    style="Margin:0;line-height:22px;mso-line-height-rule:exactly;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;font-size:18px;font-style:normal;font-weight:bold;color:#333333">
+                                                                Dear
+                                                                {0},</h3></td>
+                                                        </tr>
+                                                        <tr style="border-collapse:collapse">
+                                                            <td align="left"
+                                                                style="padding:0;Margin:0;padding-bottom:10px;padding-top:15px">
+                                                                <p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:16px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:24px;color:#777777">
+                                                                    Thank you for using our ETE Health Platform.</p>
+                                                                <p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:16px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:24px;color:#777777">
+                                                                    We would like to remind you that your license
+                                                                    expires in 30 days. To continue enjoying the
+                                                                    benefits of our services, please renew your license
+                                                                    before the expiry date</p></td>
+                                                        </tr>
+                                                        <tr>
+                                                            <td align="left"
+                                                                style="padding:0;Margin:0;padding-bottom:10px;padding-top:15px">
+                                                                <p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:16px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:24px;color:#777777"></p>
+                                                                <p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:16px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:24px;color:#777777">
+                                                                    Please verify with <a
+                                                                        href="mailto:info@eyetoeyesecurity.com">info@eyetoeyesecurity.com</a>
+                                                                    on how to renew it.</p></td>
+                                                        </tr>
+                                                    </table>
+                                                </td>
+                                            </tr>
+                                        </table>
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+                </table>
+                <table class="es-content" cellspacing="0" cellpadding="0" align="center"
+                       style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%">
+                    <tr style="border-collapse:collapse">
+                        <td align="center" style="padding:0;Margin:0">
+                            <table class="es-content-body" cellspacing="0" cellpadding="0" bgcolor="#ffffff"
+                                   align="center"
+                                   style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:#FFFFFF;width:600px">
+                                <tr style="border-collapse:collapse">
+                                    <td align="left"
+                                        style="padding:0;Margin:0;padding-top:20px;padding-left:35px;padding-right:35px">
+                                        <table width="100%" cellspacing="0" cellpadding="0"
+                                               style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                            <tr style="border-collapse:collapse">
+                                                <td valign="top" align="center" style="padding:0;Margin:0;width:530px">
+                                                    <table width="100%" cellspacing="0" cellpadding="0"
+                                                           role="presentation"
+                                                           style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                                        <tr style="border-collapse:collapse">
+                                                            <td bgcolor="#eeeeee" align="left"
+                                                                style="Margin:0;padding-top:10px;padding-bottom:10px;padding-left:10px;padding-right:10px">
+                                                                <table style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;width:500px"
+                                                                       class="cke_show_border" cellspacing="1"
+                                                                       cellpadding="1" border="0" align="left"
+                                                                       role="presentation">
+                                                                    <tr style="border-collapse:collapse">
+                                                                        <td width="80%" style="padding:0;Margin:0"><h4
+                                                                                style="Margin:0;line-height:120%;mso-line-height-rule:exactly;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif">
+                                                                            Information</h4></td>
+                                                                        <td width="20%" style="padding:0;Margin:0"><h4
+                                                                                style="Margin:0;line-height:120%;mso-line-height-rule:exactly;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif">
+                                                                            <br></h4></td>
+                                                                    </tr>
+                                                                </table>
+                                                            </td>
+                                                        </tr>
+                                                    </table>
+                                                </td>
+                                            </tr>
+                                        </table>
+                                    </td>
+                                </tr>
+                                <tr style="border-collapse:collapse">
+                                    <td align="left" style="padding:0;Margin:0;padding-left:35px;padding-right:35px">
+                                        <table width="100%" cellspacing="0" cellpadding="0"
+                                               style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                            <tr style="border-collapse:collapse">
+                                                <td valign="top" align="center" style="padding:0;Margin:0;width:530px">
+                                                    <table style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:separate;border-spacing:0px;border-color:transparent;border-style:solid;border-width:3px;border-radius:14px"
+                                                           width="100%" cellspacing="0" cellpadding="0"
+                                                           role="presentation">
+                                                        <tr style="border-collapse:collapse">
+                                                            <td align="center"
+                                                                style="Margin:0;padding-top:10px;padding-bottom:10px;padding-left:10px;padding-right:10px">
+                                                                <table style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;width:500px"
+                                                                       class="cke_show_border" cellspacing="1"
+                                                                       cellpadding="1" border="0" align="left"
+                                                                       role="presentation">
+                                                                    <tr style="border-collapse:collapse">
+                                                                        <td style="padding:5px 10px 5px 0;Margin:0"
+                                                                            width="80%" align="left"><p
+                                                                                style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:15px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:23px;color:#333333">
+                                                                            Number of Available Licenses</p></td>
+                                                                        <td style="padding:5px 0;Margin:0" width="20%"
+                                                                            align="left"><p
+                                                                                style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:15px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:23px;color:#333333">
+                                                                            {1}</p></td>
+                                                                    </tr>
+                                                                    <tr>
+                                                                        <td style="padding:5px 10px 5px 0;Margin:0"
+                                                                            width="80%" align="left"><p
+                                                                                style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:15px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:23px;color:#333333">
+                                                                            Valid until</p></td>
+                                                                        <td style="padding:5px 0;Margin:0" width="20%"
+                                                                            align="left"><p
+                                                                                style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:15px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:23px;color:#333333">
+                                                                            {2}</p></td>
+                                                                    </tr>
+                                                                    <tr>
+                                                                        <td style="padding:5px 10px 5px 0;Margin:0"
+                                                                            width="80%" align="left"><p
+                                                                                style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:15px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:23px;color:#333333">
+                                                                            Server ID</p></td>
+                                                                        <td style="padding:5px 0;Margin:0" width="20%"
+                                                                            align="left"><p
+                                                                                style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-size:15px;font-family:\'open sans\', \'helvetica neue\', helvetica, arial, sans-serif;line-height:23px;color:#333333">
+                                                                            {3}</p></td>
+                                                                    </tr>
+                                                                </table>
+                                                            </td>
+                                                        </tr>
+                                                    </table>
+                                                </td>
+                                            </tr>
+                                        </table>
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+                </table>
+                <table class="es-footer" cellspacing="0" cellpadding="0" align="center"
+                       style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%;background-color:transparent;background-repeat:repeat;background-position:center top">
+                    <tr style="border-collapse:collapse">
+                        <td align="center" style="padding:0;Margin:0">
+                            <table class="es-footer-body" cellspacing="0" cellpadding="0" align="center"
+                                   style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:#FFFFFF;width:600px">
+                                <tr style="border-collapse:collapse">
+                                    <td align="left"
+                                        style="Margin:0;padding-top:35px;padding-left:35px;padding-right:35px;padding-bottom:40px">
+                                        <table width="100%" cellspacing="0" cellpadding="0"
+                                               style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                            <tr style="border-collapse:collapse">
+                                                <td valign="top" align="center" style="padding:0;Margin:0;width:530px">
+                                                </td>
+                                            </tr>
+                                        </table>
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+                </table>
+                <table class="es-content" cellspacing="0" cellpadding="0" align="center"
+                       style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%">
+                    <tr style="border-collapse:collapse">
+                        <td align="center" style="padding:0;Margin:0">
+                            <table class="es-content-body"
+                                   style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:transparent;width:600px"
+                                   cellspacing="0" cellpadding="0" align="center">
+                                <tr style="border-collapse:collapse">
+                                    <td align="left"
+                                        style="Margin:0;padding-left:20px;padding-right:20px;padding-top:30px;padding-bottom:30px">
+                                        <table width="100%" cellspacing="0" cellpadding="0"
+                                               style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
+                                            <tr style="border-collapse:collapse">
+                                                <td valign="top" align="center" style="padding:0;Margin:0;width:560px">
+                                                </td>
+                                            </tr>
+                                        </table>
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+                </table>
+            </td>
+        </tr>
+    </table>
+</div>
+</body>
+</html>

BIN
slope-modules/slope-detection/src/main/resources/template/logo.png