!125 add 新增sms4j-email-jakarta分支

* update 删除多余的内容
* add 新增sms4j-email-jakarta分支
This commit is contained in:
Bleachtred 2024-01-04 00:34:22 +00:00 committed by 风如歌
parent 135459c544
commit a36cc7eeca
27 changed files with 1449 additions and 25 deletions

23
pom.xml
View File

@ -22,6 +22,7 @@
<module>sms4j-javase-plugin</module> <module>sms4j-javase-plugin</module>
<module>sms4j-Email-plugin</module> <module>sms4j-Email-plugin</module>
<module>sms4j-oa-plugin</module> <module>sms4j-oa-plugin</module>
<module>sms4j-email-jakarta</module>
</modules> </modules>
<!-- 开源协议 Apache 2.0 --> <!-- 开源协议 Apache 2.0 -->
@ -55,16 +56,15 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring.boot.version>2.7.15</spring.boot.version> <spring.boot.version>2.7.18</spring.boot.version>
<solon.version>2.5.4</solon.version> <solon.version>2.5.4</solon.version>
<redisson.version>3.17.0</redisson.version> <redisson.version>3.17.0</redisson.version>
<jdcloud.version>1.3.3</jdcloud.version> <jdcloud.version>1.3.3</jdcloud.version>
<hutool.version>5.8.20</hutool.version> <hutool.version>5.8.24</hutool.version>
<xmlblend.version>2.3.0</xmlblend.version> <xmlblend.version>2.3.0</xmlblend.version>
<activation.version>1.1.1</activation.version> <activation.version>1.1.1</activation.version>
<mail.version>1.6.2</mail.version> <mail.version>1.6.2</mail.version>
<sunactivation.version>1.2.0</sunactivation.version> <sunactivation.version>1.2.0</sunactivation.version>
<jakarta.activation.version>1.2.2</jakarta.activation.version>
<snakeyaml.version>2.0</snakeyaml.version> <snakeyaml.version>2.0</snakeyaml.version>
</properties> </properties>
@ -77,6 +77,16 @@
<version>${spring.boot.version}</version> <version>${spring.boot.version}</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
<exclusions>
<exclusion>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
@ -169,12 +179,6 @@
<version>${sunactivation.version}</version> <version>${sunactivation.version}</version>
</dependency> </dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>${jakarta.activation.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@ -311,4 +315,5 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@ -57,11 +57,6 @@
<version>${sunactivation.version}</version> <version>${sunactivation.version}</version>
</dependency> </dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>${jakarta.activation.version}</version>
</dependency>
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-cron</artifactId> <artifactId>hutool-cron</artifactId>

View File

@ -13,10 +13,6 @@
<name>sms4j-Email-core</name> <name>sms4j-Email-core</name>
<description>sms4j-Email-core</description> <description>sms4j-Email-core</description>
<properties>
</properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.dromara.sms4j</groupId> <groupId>org.dromara.sms4j</groupId>
@ -27,13 +23,6 @@
<groupId>com.sun.activation</groupId> <groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId> <artifactId>javax.activation</artifactId>
</dependency> </dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>pom</packaging>
<artifactId>sms4j-email-jakarta</artifactId>
<modules>
<module>sms4j-email-jakarta-api</module>
<module>sms4j-email-jakarta-comm</module>
<module>sms4j-email-jakarta-core</module>
</modules>
<name>sms4j-email-jakarta</name>
<description>JDK 11 或更高版本中使用</description>
<properties>
<jakarta-mail.version>2.1.2</jakarta-mail.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-email-jakarta-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-email-jakarta-comm</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-email-jakarta-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>${jakarta-mail.version}</version>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<version>${jakarta-mail.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-email-jakarta</artifactId>
<version>${revision}</version>
</parent>
<artifactId>sms4j-email-jakarta-api</artifactId>
<description>email-api</description>
<dependencies>
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-email-jakarta-comm</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,14 @@
package org.dromara.email.jakarta.api;
import java.util.List;
/**
* Blacklist
* <p> 黑名单实现 实现此接口发送邮件时将自动排除调黑名单中的收件人
* @author :Wind
* 2023/6/8 23:05
**/
public interface Blacklist {
List<String> getBlacklist();
}

View File

@ -0,0 +1,14 @@
package org.dromara.email.jakarta.api;
import org.dromara.email.jakarta.comm.entity.MailMessage;
public interface MailClient {
/**
* 发送邮件可以通过对象构造群体发送或者单体发送取决于添加进去的收件人同时可以添加
* 密送人抄送人附件等参数
* @param mailMessage 发送邮件参数对象
* @author :Wind
*/
void send(MailMessage mailMessage);
}

View File

@ -0,0 +1,21 @@
package org.dromara.email.jakarta.api;
import org.dromara.email.jakarta.comm.entity.MonitorMessage;
/**
* Monitor
* <p> 监听接口实现此接口用于监听并获取邮件消息
* @author :Wind
* 2023/7/18 15:57
**/
public interface Monitor {
/**
* monitor
* <p> 监听系统的邮件消息
* @param monitorMessage 系统监听到的消息内容
* @return true为标记已读否则不标记
* @author :Wind
*/
boolean monitor(MonitorMessage monitorMessage);
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-email-jakarta</artifactId>
<version>${revision}</version>
</parent>
<artifactId>sms4j-email-jakarta-comm</artifactId>
<description>email通用配置</description>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-cron</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,19 @@
package org.dromara.email.jakarta.comm.config;
import lombok.Data;
@Data
public class MailImapConfig {
/** imap服务地址*/
private String imapServer;
/** 要监听的邮箱账号*/
private String username;
/** 要监听的邮箱授权码或密码*/
private String accessToken;
/** 监听周期(秒)*/
private Integer cycle = 5;
}

View File

@ -0,0 +1,67 @@
package org.dromara.email.jakarta.comm.config;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
/**
* MailSmtpConfig
* <p> smtp协议配置文件
* @author :Wind
* 2023/6/7 21:19
**/
@Builder
@ToString
@Getter
@EqualsAndHashCode
public class MailSmtpConfig {
/**
* 端口号
* */
private String port;
/**
* 发件人地址
* */
private String fromAddress;
/**
* 服务器地址
* */
private String smtpServer;
/**
* 账号
* */
private String username;
/**
* 密码
* */
private String password;
/**
* 是否开启ssl 默认开启
* */
@Builder.Default
private String isSSL = "true";
/**
* 是否开启验证 默认开启
* */
@Builder.Default
private String isAuth = "true";
/**
* 重试间隔单位默认为5秒
*/
@Builder.Default
private int retryInterval = 5;
/**
* 重试次数默认为1次
*/
@Builder.Default
private int maxRetries = 1;
}

View File

@ -0,0 +1,9 @@
package org.dromara.email.jakarta.comm.constants;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FileConstants {
public static final String IO_FILE_TYPE = "application/octet-stream";
}

View File

@ -0,0 +1,178 @@
package org.dromara.email.jakarta.comm.entity;
import lombok.Getter;
import org.dromara.email.jakarta.comm.utils.ReflectUtil;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Getter
public class MailMessage {
/** 收件人地址*/
private List<String> mailAddress;
/** 邮件主题*/
private String title;
/** 文字正文*/
private String body;
/** html模板文件路径resources目录下的路径*/
private String htmlPath;
/** html模板文件的输入流可来自任意可读取位置*/
private InputStream htmlInputStream;
/** html 模板参数*/
private Map<String,String> htmlValues;
/** 抄送人*/
private List<String> cc;
/** 密送人*/
private List<String> bcc;
/** 附件*/
private Map<String,String> files;
/** 压缩文件名称*/
private String zipName;
public static MailsBuilder Builder(){
return new MailsBuilder();
}
public static class MailsBuilder{
private final MailMessage mailMessage = new MailMessage();
public MailsBuilder() {
}
public MailMessage build(){
return mailMessage;
}
/** 收件人地址*/
public MailsBuilder mailAddress(List<String> mailAddress) {
mailMessage.mailAddress = mailAddress;
return this;
}
/** 收件人地址*/
public MailsBuilder mailAddress(String mailAddress){
if ( mailMessage.mailAddress == null){
mailMessage.mailAddress = new ArrayList<>();
}
mailMessage.mailAddress.add(mailAddress);
return this;
}
/** 邮件主题*/
public MailsBuilder title(String title){
mailMessage.title = title;
return this;
}
/** 文字正文*/
public MailsBuilder body(String body){
mailMessage.body = body;
return this;
}
/** 抄送人*/
public MailsBuilder cc(List<String> cc){
mailMessage.cc = cc;
return this;
}
/** 抄送人*/
public MailsBuilder cc(String cc){
if (mailMessage.cc == null){
mailMessage.cc = new ArrayList<>();
}
mailMessage.cc.add(cc);
return this;
}
/** 密送人*/
public MailsBuilder bcc(List<String> bcc){
mailMessage.bcc = bcc;
return this;
}
/** 密送人*/
public MailsBuilder bcc(String bcc){
if (mailMessage.bcc == null){
mailMessage.bcc = new ArrayList<>();
}
mailMessage.bcc.add(bcc);
return this;
}
/** 附件*/
public MailsBuilder files(Map<String, String> files){
if (mailMessage.files == null){
mailMessage.files = new HashMap<>();
}
mailMessage.files.putAll(files);
return this;
}
/** 附件*/
public MailsBuilder files(String fileName,String filePath){
if (mailMessage.files == null){
mailMessage.files = new HashMap<>();
}
mailMessage.files.put(fileName,filePath);
return this;
}
/** html模板文件路径resources目录下的路径*/
public MailsBuilder html(String htmlPath){
mailMessage.htmlPath = htmlPath;
return this;
}
/** html模板文件的输入流可来自任意可读取位置*/
public MailsBuilder html(InputStream htmlInputStream){
mailMessage.htmlInputStream = htmlInputStream;
return this;
}
/** html 模板参数*/
public MailsBuilder htmlValues(String key, String value){
if (mailMessage.htmlValues == null){
mailMessage.htmlValues = new HashMap<>();
}
mailMessage.htmlValues.put(key,value);
return this;
}
/** html 模板参数*/
public MailsBuilder htmlValues(Map<String,String> htmlValues){
if (mailMessage.htmlValues == null){
mailMessage.htmlValues = new HashMap<>();
}
mailMessage.htmlValues.putAll(htmlValues);
return this;
}
/** html 模板参数*/
public MailsBuilder htmlValues(Parameter parameter){
Map<String, String> values = ReflectUtil.getValues(parameter);
if (mailMessage.htmlValues == null){
mailMessage.htmlValues = new HashMap<>();
}
mailMessage.htmlValues.putAll(values);
return this;
}
/** 压缩文件名称*/
public MailsBuilder zipName(String zipName){
mailMessage.zipName = zipName;
return this;
}
}
}

View File

@ -0,0 +1,40 @@
package org.dromara.email.jakarta.comm.entity;
import lombok.Data;
import jakarta.mail.Multipart;
import java.util.Date;
/**
* MonitorMessage
* <p> 监听获取到的邮件内容
* @author :Wind
* 2023/7/18 15:59
**/
@Data
public class MonitorMessage {
/** 邮件主题*/
private String title;
/** 邮件文字内容*/
private String text;
/** 解析的html内容*/
private String htmlText;
/** 邮件发送时间*/
private Date sendDate;
/** 邮件内容(复杂内容)*/
private Multipart body;
/** 发送人*/
private String fromAddress;
/** 邮件消息编号*/
private Integer messageIndex;
/** 接收时间*/
private Long acceptTime;
}

View File

@ -0,0 +1,11 @@
package org.dromara.email.jakarta.comm.entity;
/**
* Parameter
* <p> 空接口用于标定用户自己的实体类型
* 用于发送html模板邮件时候 用户传递自己的实体序列化进行的类型标定
* @author :Wind
* 2023/6/8 19:36
**/
public interface Parameter {
}

View File

@ -0,0 +1,23 @@
package org.dromara.email.jakarta.comm.errors;
public class MailException extends RuntimeException {
public MailException() {
super();
}
public MailException(String message) {
super(message);
}
public MailException(String message, Throwable cause) {
super(message, cause);
}
public MailException(Throwable cause) {
super(cause);
}
protected MailException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,15 @@
package org.dromara.email.jakarta.comm.utils;
public class BaseUtil {
/**
* getPathName
* <p>分隔文件路径并获取文件名
* @param path 文件路径
* @author :Wind
*/
public static String getPathName(String path) {
String[] split = path.split("/");
return split[split.length - 1];
}
}

View File

@ -0,0 +1,130 @@
package org.dromara.email.jakarta.comm.utils;
import org.dromara.email.jakarta.comm.errors.MailException;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* HtmlUtil
* <p> Html相关工具
*
* @author :Wind
* 2023/6/7 20:15
**/
public final class HtmlUtil {
private static final HtmlUtil htmlUtil = new HtmlUtil();
private HtmlUtil() {
}
/**
* readHtml
* <p>从resource读取模板文件
*
* @param name 模板文件名
* @author :Wind
*/
public static List<String> readHtml(String name) throws MailException {
try (InputStream is = HtmlUtil.class.getResourceAsStream("/template/" + name)) {
return readHtml(is);
} catch (IOException e) {
throw new MailException(e);
}
}
/**
* readHtml
* <p>从自定义路径读取模板文件
*
* @param file 自定义路径file
* @author :Wind
*/
public static List<String> readHtml(File file) throws MailException {
try (InputStream ip = Files.newInputStream(file.toPath())) {
return readHtml(ip);
} catch (IOException e) {
throw new MailException(e);
}
}
/**
* readHtml
* <p>从输入流读取模板文件
*
* @param inputStream 输入流
* @author :Wind
*/
public static List<String> readHtml(InputStream inputStream) throws MailException {
List<String> data = new ArrayList<>();
if (Objects.isNull(inputStream)) {
throw new MailException("The template could not be found!");
}
try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = br.readLine()) != null) {
data.add(line);
}
} catch (IOException e) {
throw new MailException(e);
}
return data;
}
/**
* replacePlaceholder
* <p>将所包含占位符的字符串替换为固定值
*
* @param data 源数据
* @param parameter key为占位符名称 value为占位符应替换的值
* @author :Wind
*/
public static List<String> replacePlaceholder(List<String> data, Map<String, String> parameter) {
for (int i = 0; i < data.size(); i++) {
for (Map.Entry<String, String> s : parameter.entrySet()) {
String piece = piece(s.getKey());
if (data.get(i).contains(piece)){
String replace = data.get(i).replace(piece, s.getValue());
data.set(i,replace);
}
}
}
return data;
}
/**
* pieceHtml
* <p>将数据拼合为html
*
* @param data 需要拼合的数据
* @author :Wind
*/
public static String pieceHtml(List<String> data) {
StringBuilder sb = new StringBuilder();
for (String datum : data) {
sb.append(datum);
sb.append("\r\n");
}
return sb.toString();
}
/**
* piece
* <p>将参数拼合为完整占位符
*
* @author :Wind
*/
public static String piece(String parameter) {
return "#{" + parameter + "}";
}
}

View File

@ -0,0 +1,36 @@
package org.dromara.email.jakarta.comm.utils;
import org.dromara.email.jakarta.comm.entity.Parameter;
import org.dromara.email.jakarta.comm.errors.MailException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class ReflectUtil {
/**
* 反射获取接口对象的原类名
*/
public static String getObjectName(Parameter parameter) {
return parameter.getClass().getTypeName();
}
/**
* 将对象的属性和属性值变为map
* */
public static Map<String, String> getValues(Parameter parameter) {
try {
Map<String ,String> map = new HashMap<>();
Class<?> clazz = Class.forName(getObjectName(parameter));
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
map.put(declaredField.getName(), (String) declaredField.get(parameter));
}
return map;
} catch (Exception e) {
throw new MailException(e);
}
}
}

View File

@ -0,0 +1,111 @@
package org.dromara.email.jakarta.comm.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.http.HttpUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.Pipe;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 压缩包处理类
*
* @author Bleachtred
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ZipUtils extends ZipUtil {
private final static Integer TEMP_SIZE = 2048;
/**
* 压缩方法支持 本地文件/目录 + oss网络路径 混合
* @param files 文件列表
* @author Bleachtred
*/
public static void zipFilePip(Map<String, String> files, OutputStream outputStream) {
try(WritableByteChannel out = Channels.newChannel(outputStream)) {
Pipe pipe = Pipe.open();
//异步任务
CompletableFuture.runAsync(() -> runTask(pipe, files));
//获取读通道
try (ReadableByteChannel readableByteChannel = pipe.source()) {
ByteBuffer buffer = ByteBuffer.allocate(TEMP_SIZE);
while (readableByteChannel.read(buffer) >= 0) {
buffer.flip();
out.write(buffer);
buffer.clear();
}
}
}catch (Exception e){
e.printStackTrace();
}
}
private static void runTask(Pipe pipe, Map<String, String> files) {
try(ZipOutputStream zos = new ZipOutputStream(Channels.newOutputStream(pipe.sink()));
WritableByteChannel out = Channels.newChannel(zos)) {
for (Map.Entry<String, String> entry : files.entrySet()) {
taskFunction(zos, out, entry.getKey(), entry.getValue());
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 打包文件
* @param zos 压缩包输出
* @param out 缓冲区通道
* @param fileName 文件名称
* @param file 文件
* @throws IOException IOException
*/
private static void taskFunction(ZipOutputStream zos, WritableByteChannel out, String fileName, File file) throws IOException {
// 是否为目录
if (file.isDirectory()) {
File[] files = file.listFiles();
fileName = StrUtil.isEmpty(fileName) ? file.getName() + "/" : fileName + "/";
if (files == null || files.length == 0){
return;
}
for (File child : files) {
taskFunction(zos, out, fileName + child.getName(), child);
}
} else {
fileName = StrUtil.isEmpty(fileName) ? file.getName() : fileName;
zos.putNextEntry(new ZipEntry(fileName));
try(FileInputStream fis = new FileInputStream(file.getAbsolutePath())){
FileChannel fileChannel = fis.getChannel();
fileChannel.transferTo(0, fileChannel.size(), out);
fileChannel.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
private static void taskFunction(ZipOutputStream zos, WritableByteChannel out, String fileName, String file) throws IOException {
// 网络文件
if (file.startsWith("http")) {
zos.putNextEntry(new ZipEntry(fileName));
byte[] bytes = HttpUtil.downloadBytes(file);
out.write(ByteBuffer.wrap(bytes));
}else {
taskFunction(zos, out, fileName, new File(file));
}
}
}

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-email-jakarta</artifactId>
<version>${revision}</version>
</parent>
<artifactId>sms4j-email-jakarta-core</artifactId>
<description>email依赖引入包</description>
<dependencies>
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-email-jakarta-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,63 @@
package org.dromara.email.jakarta.core.factory;
import org.dromara.email.jakarta.api.Blacklist;
import org.dromara.email.jakarta.api.MailClient;
import org.dromara.email.jakarta.comm.config.MailSmtpConfig;
import org.dromara.email.jakarta.comm.errors.MailException;
import org.dromara.email.jakarta.core.service.MailBuild;
import jakarta.mail.MessagingException;
import java.util.HashMap;
import java.util.Map;
/**
* MailFactory
* <p> 配置工厂
* @author :Wind
* 2023/6/8 22:35
**/
public class MailFactory{
private static final Map<Object,MailSmtpConfig> configs = new HashMap<>();
/**
* createMailClient
* <p>从工厂获取一个邮件发送实例
* @param key 配置的标识key
* @author :Wind
*/
public static MailClient createMailClient(Object key){
try {
return MailBuild.build(configs.get(key));
} catch (MessagingException e) {
throw new MailException(e);
}
}
/**
* createMailClient
* <p>从工厂获取一个邮件发送实例,该实例发送短信将依照黑名单中的数据进行过滤
* @param key 配置的标识key
* @param blacklist 黑名单接口实例将从这里获取黑名单数据
* @author :Wind
*/
public static MailClient createMailClient(Object key, Blacklist blacklist){
try {
return MailBuild.build(configs.get(key),blacklist);
} catch (MessagingException e) {
throw new MailException(e);
}
}
/**
* set
* <p>将一个配置对象交给工厂
* @param key 标识
* @param config 配置对象
* @author :Wind
*/
public static void put(Object key, MailSmtpConfig config){
configs.put(key,config);
}
}

View File

@ -0,0 +1,65 @@
package org.dromara.email.jakarta.core.factory;
import org.dromara.email.jakarta.api.Monitor;
import org.dromara.email.jakarta.comm.config.MailImapConfig;
import org.dromara.email.jakarta.core.service.MonitorService;
import java.util.HashMap;
import java.util.Map;
/**
* MonitorFactory
* <p> 监听系统工厂通过向工厂添加配置可以启动或关闭监听
* 监听器通过连接指定的imap服务器监听指定的邮箱并以异步轮询的形式进行消息的监听除去轮询时间开销外个别imap服务器本身存在延迟故而邮件的监听可能存在较大延迟
* 在配置中可以设置轮询的间隔时间以增大或缩小监听灵敏度灵敏度调节周期为秒级默认周期为5秒
* <p>需要注意的是监听器所使用的线程并非任何线程池中的线程如想停止某邮箱的监听只需要调用stop方法即可他会在完成当前接收的任务后正常的终结线程
* 现成终结后可以通过调用start方法重新启用
* @author :Wind
* 2023/7/18 17:06
**/
public class MonitorFactory {
private final static Map<String, MonitorService> services = new HashMap<>();
/**
* put
* <p> 添加一个配置至系统中并绑定接收消息的对象
* @param key 监听标识
* @param config 监听配置
* @param monitor 回调对象
* @author :Wind
*/
public static void put(String key, MailImapConfig config, Monitor monitor){
services.put(key,new MonitorService(config,monitor));
}
/**
* start
* <p> 开始监听指定标识的邮箱
* @param key 标识
* @author :Wind
*/
public static void start(String key){
services.get(key).start();
}
/**
* stop
* <p> 停止监听指定标识的邮箱
* @param key 标识
* @author :Wind
*/
public static void stop(String key){
services.get(key).stop();
}
/**
* getConfig
* <p> 获取指定标识的配置信息
* @param key 标识
* @author :Wind
*/
public static MailImapConfig getConfig(String key) {
return services.get(key).getMailImapConfig();
}
}

View File

@ -0,0 +1,6 @@
/**
* <p> 邮件插件核心模块
* @author :Wind
* 2023/7/27 10:58
**/
package org.dromara.email.jakarta.core;

View File

@ -0,0 +1,107 @@
package org.dromara.email.jakarta.core.service;
import cn.hutool.core.collection.CollUtil;
import lombok.Data;
import org.dromara.email.jakarta.api.Blacklist;
import org.dromara.email.jakarta.api.MailClient;
import org.dromara.email.jakarta.comm.config.MailSmtpConfig;
import org.dromara.email.jakarta.comm.errors.MailException;
import jakarta.mail.Authenticator;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.PasswordAuthentication;
import jakarta.mail.Session;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
@Data
public class MailBuild {
private Message message;
private Session session;
private MailSmtpConfig config;
private Blacklist blacklist;
private int retryInterval;
private int maxRetries;
private MailBuild(MailSmtpConfig config) throws MessagingException {
Properties props = new Properties();
props.put("mail.smtp.host", config.getSmtpServer());
props.put("mail.smtp.auth", config.getIsAuth());
props.put("mail.smtp.port", config.getPort());
props.put("mail.smtp.ssl.enable", config.getIsSSL());
// props.put("mail.smtp.ssl.socketFactory", new MailSSLSocketFactory());
this.session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(config.getUsername(), config.getPassword());
}
});
this.message = new MimeMessage(session);
this.message.setFrom(new InternetAddress(config.getFromAddress()));
this.config = config;
this.retryInterval = config.getRetryInterval();
this.maxRetries = config.getMaxRetries();
}
private MailBuild(MailSmtpConfig config,Blacklist blacklist)throws MessagingException{
Properties props = new Properties();
props.put("mail.smtp.host", config.getSmtpServer());
props.put("mail.smtp.auth", config.getIsAuth());
props.put("mail.smtp.port", config.getPort());
props.put("mail.smtp.ssl.enable", config.getIsSSL());
// props.put("mail.smtp.ssl.socketFactory", new MailSSLSocketFactory());
this.session = Session.getInstance(props,
new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(config.getUsername(), config.getPassword());
}
});
this.message = new MimeMessage(session);
this.message.setFrom(new InternetAddress(config.getFromAddress()));
this.config = config;
this.blacklist = blacklist;
this.retryInterval = config.getRetryInterval();
this.maxRetries = config.getMaxRetries();
}
public static MailClient build(MailSmtpConfig config) throws MessagingException {
return MailService.instance(new MailBuild(config));
}
public static MailClient build(MailSmtpConfig config,Blacklist blacklist)throws MessagingException {
return MailService.instance(new MailBuild(config,blacklist));
}
/**
* eliminate
* <p>过滤黑名单内容
* @param source 需要过滤的源数据
* @author :Wind
*/
public InternetAddress[] eliminate(List<String> source) {
List<String> list = new ArrayList<>();
try {
if (Objects.isNull(blacklist)) {
return InternetAddress.parse(Objects.requireNonNull(CollUtil.join(source, ",")));
}
for (String s : blacklist.getBlacklist()) {
if (!source.contains(s)) {
list.add(s);
}
}
return InternetAddress.parse(CollUtil.join(list, ","));
} catch (AddressException e) {
throw new MailException(e);
}
}
}

View File

@ -0,0 +1,227 @@
package org.dromara.email.jakarta.core.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import jakarta.mail.util.ByteArrayDataSource;
import org.dromara.email.jakarta.api.MailClient;
import org.dromara.email.jakarta.comm.constants.FileConstants;
import org.dromara.email.jakarta.comm.entity.MailMessage;
import org.dromara.email.jakarta.comm.errors.MailException;
import org.dromara.email.jakarta.comm.utils.HtmlUtil;
import org.dromara.email.jakarta.comm.utils.ZipUtils;
import jakarta.activation.DataHandler;
import jakarta.activation.DataSource;
import jakarta.activation.FileDataSource;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.Transport;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMultipart;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public class MailService implements MailClient {
private static Logger logger = Logger.getLogger("mailLog");
private MailBuild mailBuild;
private MailService(MailBuild mailBuild) {
this.mailBuild = mailBuild;
}
public static MailClient instance(MailBuild mailBuild) {
return new MailService(mailBuild);
}
@Override
public void send(MailMessage mailMessage) {
List<String> html = null;
if (mailMessage.getHtmlInputStream() != null) {
html = HtmlUtil.readHtml(mailMessage.getHtmlInputStream());
}
if (StrUtil.isNotBlank(mailMessage.getHtmlPath())){
html = HtmlUtil.readHtml(mailMessage.getHtmlPath());
}
send(mailMessage.getMailAddress(),
mailMessage.getTitle(),
mailMessage.getBody(),
html,
mailMessage.getHtmlValues(),
mailMessage.getZipName(),
mailMessage.getFiles(),
mailMessage.getCc(),
mailMessage.getBcc()
);
}
private void forFiles(Multipart multipart, Map<String, String> files) throws MessagingException {
for (Map.Entry<String, String> entry : files.entrySet()) {
String k = entry.getKey();
String v = entry.getValue();
// 设置附件消息部分
MimeBodyPart messageBodyPart = new MimeBodyPart();
DataSource source;
if (v.startsWith("http")) {
byte[] bytes = HttpUtil.downloadBytes(v);
source = new ByteArrayDataSource(bytes, FileConstants.IO_FILE_TYPE);
} else {
source = new FileDataSource(v);
}
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(k);
multipart.addBodyPart(messageBodyPart);
}
}
private void zipFiles(Multipart multipart, String zipName, Map<String, String> files) throws MessagingException, IOException {
// 设置附件消息部分
MimeBodyPart messageBodyPart = new MimeBodyPart();
ByteArrayOutputStream os = new ByteArrayOutputStream();
ZipUtils.zipFilePip(files, os);
ByteArrayInputStream stream = IoUtil.toStream(os);
DataSource source = new ByteArrayDataSource(stream, FileConstants.IO_FILE_TYPE);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(StrUtil.isNotBlank(zipName) ? zipName : UUID.fastUUID() + ".zip");
multipart.addBodyPart(messageBodyPart);
}
private void send(List<String> mailAddress,
String title,
String body,
List<String> html,
Map<String, String> parameter,
String zipName,
Map<String, String> files,
List<String> cc,
List<String> bcc) {
try {
Message message = messageBuild(mailAddress, title, body, html, parameter, zipName, cc, bcc, files);
Transport.send(message);
logger.info("邮件发送成功!^_^");
} catch (MessagingException | IOException e) {
// 防止 maxRetries 数值小于0带来的其他问题
if (mailBuild.getMaxRetries() > 0) {
ReSendList(mailAddress, title, body, html, parameter, zipName, files, cc, bcc);
} else {
logger.warning(e.getMessage());
throw new MailException(e);
}
}
}
private void ReSendList(List<String> mailAddress,
String title,
String body,
List<String> html,
Map<String, String> parameter,
String zipName,
Map<String, String> files,
List<String> cc,
List<String> bcc) {
int maxRetries = mailBuild.getMaxRetries();
int retryCount = 1; // 初始值为1则while循环中少发送一次最后一次发送在判断 retryCount >= maxRetries 这里
boolean retryOnFailure = true;
while (retryOnFailure && retryCount < maxRetries) {
try {
logger.warning("邮件第 {" + retryCount + "} 次重新发送");
Message message;
if (html != null || parameter != null) {
message = messageBuild(mailAddress, title, body, html, parameter, zipName, cc, bcc, files);
} else {
message = messageBuild(mailAddress, title, body, null, null, zipName, cc, bcc, files);
}
Transport.send(message);
retryOnFailure = false; // 发送成功停止重试
} catch (MessagingException | IOException e) {
retryCount++;
try {
// 间隔秒数
TimeUnit.SECONDS.sleep(mailBuild.getRetryInterval());
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
if (retryCount >= maxRetries) {
try {
Message message;
if (html != null || parameter != null) {
message = messageBuild(mailAddress, title, body, html, parameter, null, cc, bcc, files);
} else {
message = messageBuild(mailAddress, title, body, null, null, null, cc, bcc, files);
}
Transport.send(message);
} catch (MessagingException | IOException e) {
throw new MailException(e);
}
}
}
public Message messageBuild(List<String> mailAddress,
String title,
String body,
List<String> html,
Map<String, String> parameter,
String zipName,
List<String> cc,
List<String> bcc,
Map<String, String> files) throws MessagingException, IOException {
Message message = mailBuild.getMessage();
message.setRecipients(Message.RecipientType.TO, mailBuild.eliminate(mailAddress));
message.setSubject(title);
Multipart multipart = new MimeMultipart("alternative");
if (CollUtil.isNotEmpty(html) && MapUtil.isNotEmpty(parameter)) {
//读取模板并进行变量替换
List<String> strings = HtmlUtil.replacePlaceholder(html, parameter);
//拼合HTML数据
String htmlData = HtmlUtil.pieceHtml(strings);
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlData, "text/html;charset=UTF-8");
multipart.addBodyPart(htmlPart);
}
if (StrUtil.isNotBlank(body)) {
// 创建文本正文部分
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(body);
multipart.addBodyPart(textPart);
}
//添加附件
if (MapUtil.isNotEmpty(files) && StrUtil.isNotBlank(zipName)) {
zipFiles(multipart, zipName, files);
} else {
if (MapUtil.isNotEmpty(files)) {
forFiles(multipart, files);
message.setContent(multipart);
}
}
if (CollUtil.isNotEmpty(cc) || CollUtil.isNotEmpty(bcc)) {
addCC(cc, bcc, message);
}
message.setContent(multipart);
return message;
}
private void addCC(List<String> cc, List<String> bcc, Message message) throws MessagingException {
if (CollUtil.isNotEmpty(cc)) {
message.addRecipients(Message.RecipientType.CC, mailBuild.eliminate(cc));
}
if (CollUtil.isNotEmpty(bcc)) {
message.addRecipients(Message.RecipientType.BCC, mailBuild.eliminate(bcc));
}
}
}

View File

@ -0,0 +1,133 @@
package org.dromara.email.jakarta.core.service;
import org.dromara.email.jakarta.api.Monitor;
import org.dromara.email.jakarta.comm.config.MailImapConfig;
import org.dromara.email.jakarta.comm.entity.MonitorMessage;
import org.dromara.email.jakarta.comm.errors.MailException;
import jakarta.mail.BodyPart;
import jakarta.mail.Flags;
import jakarta.mail.Folder;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.Session;
import jakarta.mail.Store;
import jakarta.mail.search.FlagTerm;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
/**
* MonitorService
* <p> 监听器服务
*
* @author :Wind
* 2023/7/18 16:10
**/
public class MonitorService{
private final Store store;
private Monitor monitor;
private MailImapConfig mailImapConfig;
private Timer timer;
public MonitorService(MailImapConfig config, Monitor monitor) {
Properties props = System.getProperties();
props.setProperty("mail.store.protocol", "imaps");
try {
Session session = Session.getDefaultInstance(props, null);
Store store = session.getStore("imaps");
store.connect(config.getImapServer(), config.getUsername(), config.getAccessToken());
this.store = store;
this.monitor = monitor;
this.mailImapConfig = config;
} catch (Exception e) {
throw new MailException(e);
}
}
private void startListening() {
try {
Folder inbox = store.getFolder("Inbox");
inbox.open(Folder.READ_WRITE);
Message[] messages = inbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
for (Message message : messages) {
MonitorMessage monitorMessage = new MonitorMessage();
// 获取邮件的发送者
monitorMessage.setFromAddress(message.getFrom()[0].toString());
// 获取邮件主题
monitorMessage.setTitle(message.getSubject());
// 获取邮件的内容
if (message.isMimeType("text/plain")) {
Object content = message.getContent();
if (content == null){
StringBuilder stringBuilder = getStringBuilder(message);
content = stringBuilder.toString();
}
monitorMessage.setText(content.toString());
} else if (message.isMimeType("multipart/*")) {
Multipart mp = (Multipart) message.getContent();
for (int i = 0; i < mp.getCount();i++){
BodyPart bodyPart = mp.getBodyPart(i);
String contentType = bodyPart.getContentType().toLowerCase();
if (contentType.startsWith("text/plain")) {
// 纯文本内容
monitorMessage.setText(bodyPart.getContent().toString());
} else if (contentType.startsWith("text/html")) {
// HTML内容
monitorMessage.setHtmlText(bodyPart.getContent().toString());
}
}
monitorMessage.setBody(mp);
}
monitorMessage.setMessageIndex(message.getMessageNumber());
monitorMessage.setSendDate(message.getSentDate());
monitorMessage.setAcceptTime(System.currentTimeMillis());
if (this.monitor.monitor(monitorMessage)) {
message.setFlag(Flags.Flag.SEEN, true);
}
}
inbox.close();
} catch (Exception e) {
throw new MailException(e);
}
}
private static StringBuilder getStringBuilder(Message message) throws IOException, MessagingException {
InputStream inputStream = message.getInputStream();
// 解析输入流以获取内容
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
}
return stringBuilder;
}
public MonitorService start(){
Timer timer = new Timer();
this.timer = timer;
timer.schedule(new TimerTask() {
@Override
public void run() {
startListening();
}
},0,mailImapConfig.getCycle()*1000);
return this;
}
public void stop(){
timer.cancel();
}
public MailImapConfig getMailImapConfig() {
return mailImapConfig;
}
}