最近公司准备搞分布式,需要将原代码按功能等一个个抽离出来,我负责弄邮箱短信部分,经历了半个月的折磨,总算搞出来了,这里做个总结,有许多之前没弄过的,算是由0到1的一个过程。
整个思路过程如下:
比较简单的,对外暴露的仅仅只是api接口,内部最重要的是一个实体类,用于存储需上传的数据,再发送给mq,然后消费mq中的信息。
这其中有几个问题需要总结的,算是这个过程中卡得我难受的点。
第一:设计上,对外暴露的api接口只有存储对象和mq发送方法,这里是一个小技巧,逻辑处理封装起来,并不对外提供服务。存储对象类设计上还是有点意思的,用到了内部类,这里记录下来,以后做参考:
package cn.coralglobal.message.api.service;
import cn.coralglobal.message.api.exception.MessageCenterBuilderException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
/**
* @Description: 消息主体对象
* @Author chenjianwen
* @Date 2020-03-25
**/
@Data
@Slf4j
public class MessageSubject implements Serializable {
private static final long serialVersionUID = 4901512199571414865L;
/**
* 发送时间
*/
private long timestamp = System.currentTimeMillis();
/**
* 模版名称
*/
private String templateName;
/**
* 模版code
*/
private String templateCode;
/**
* 替换文本,和模版中的占位符严格一一对应
*/
private String[] replace;
/**
* 消息受众(可以是userId或者用户手机号或邮箱)
*/
private String[] users;
/**
* 发送消息的站台(coralglobal或crm或其他)
*/
private String platform;
public MessageSubject(){}
public MessageSubject(Builder builder){
setTimestamp(builder.timestamp);
setTemplateName(builder.templateName);
setTemplateCode(builder.templateCode);
setReplace(builder.replace);
setUsers(builder.users);
setPlatform(builder.platform);
}
public static Builder newBuilder(){
return new Builder();
}
public static Builder newBuilder(MessageSubject ms){
Builder b = new Builder();
b.timestamp = ms.getTimestamp();
b.templateName = ms.getTemplateName();
b.templateCode = ms.getTemplateCode();
b.replace = ms.getReplace();
b.users = ms.getUsers();
b.platform = ms.getPlatform();
return b;
}
public static final class Builder{
private long timestamp;
private String templateName;
private String templateCode;
private String[] replace;
private String[] users;
private String platform;
private Builder(){}
public Builder timestamp(long timestamp){
this.timestamp = timestamp;
return this;
}
public Builder name(String templateName){
this.templateName = templateName;
return this;
}
public Builder template(String code) throws MessageCenterBuilderException {
if(code == null || "".equals(code)){
log.error("message-center-api|MessageSubject|template(String code)|模版编码不能为空");
throw new MessageCenterBuilderException("message-center|MessageSubject|template(String code)|模版编码不能为空");
}
this.templateCode = code;
return this;
}
public Builder replace(String... replace){
this.replace = replace;
return this;
}
public Builder users(String... users){
this.users = users;
return this;
}
public Builder platform(String platform){
this.platform = platform;
return this;
}
public MessageSubject build() throws MessageCenterBuilderException {
if(this.templateCode == null || "".equals(this.templateCode)){
log.error("message-center-api|MessageSubject|build()|templateCode不能为空");
throw new MessageCenterBuilderException("message-center-api|MessageSubject|build()|templateCode不能为空");
}
return new MessageSubject(this);
}
}
}
第二:用mq发送数据的时候,先将数据序列化成字节数据会好点,用下面这个方法:
com.fasterxml.jackson.databind.ObjectMapper;
其maven依赖是:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.3</version>
</dependency>
然后监听器那边转换一下即可:
第三: 由于它是一个单独的服务,因此最终需要打成jar到我们公司本地的私服并生成maven依赖,以供其他的项目使用。所以需要分两步完成:
(1)我需要现在本地repository仓库生成jar,那问题来了,如何在本地生成jar,这里涉及到了maven的三个命令:
mvn clean package 在项目目录下的target目录下生成jar
mvn clean install 打包jar到target目录下,且打包到本地repository
mvn clean deploy 打包jar到target目录下,且打包到本地repository,且打包到远程repository
这里在本地仓库测试,因此使用mvn clean install,或者直接在idea上的maven可视化操作,如下:
(2)在公司私服生成jar有点麻烦,花了很多时间才搞定,首先,需要在settings.xml中<servers></servers>标签对中添加本地私服的服务器用户名和密码,如下:
然后在项目中的pom.xml文件中添加如下标签对:
然后使用mvn clean deploy命令就能将服务打成jar包上传到本地私服。
(2)如何引用本地私服的maven依赖,除了在pom.xml中引用<dependency></dependency>依赖之外,还需要引用本地私服仓库的地址,如下:
这样,就能引用本地私服仓库的maven的jar包了。
=========================================================================================
后面有个需求,要求邮箱可以发送文件,由于我邮箱是走rabbitmq的,所以刚开始总是出问题:
我先是尝试走controller用postman调试的,如下:
MultipartFile文件类引用的是spring的:
import org.springframework.web.multipart.MultipartFile;
由于我这里是走mq的,如果直接将文件放进去,序列化成字节数组会报如下错误:
No serializer found for class java.io.FileDescriptor and no properties disconnected
所以,我需要先把文件转换成字符串,这样就能通过mq发出去了,如下:
//multifile是import org.springframework.web.multipart.MultipartFile;类型数据
//获取文件名称
String fileName = multifile.getOriginalFilename();
byte[] bytes=new byte[(int)multifile.getSize()];
InputStream is = multifile.getInputStream();
is.read(bytes);
//通过Base64讲字节数组转换为字符串
String fileContent = Base64.getEncoder().encodeToString(bytes);
//EmailFile这个对象存储文件名称和文件
EmailFile ef= new EmailFile();
ef.setFileName(fileName);
ef.setFileContent(fileContent);
以上是通过postman上传文件请求controller发送邮件,这里由于http限制,最多只能传10m以内的数据。
当然也可以直接上传本地文件,这样就没有大小限制的问题
//读取本地文件
FileReader fileReader = new FileReader("/Users/chenjianwen/Downloads/QQ_6.6.5.dmg");
//将文件转换成字节数组
byte[] result = fileReader.readBytes();
//通过Base64将字节数组转换成字符串
String fileContent = Base64.getEncoder().encodeToString(result);
EmailFile ef= new EmailFile();
ef.setFileName("QQ_6.6.5.dmg"); //自定义文件名称
ef.setFileContent(fileContent);
FileReader引入的是hutool工具类,其maven依赖如下:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.7</version>
</dependency>
Base64是java.util包中的,jdk自带的。
由于我邮件文件发送是通过InputStream发送的,所以我需要在mq监听器内将字符串的文件转换成InputStream
//multipartFile是我的文件对象
String fileName = multipartFile.getFileName(); //文件名称
String fileContent = multipartFile.getFileContent(); //文件,这里转换成了String类型
byte[] fileByte = Base64.decode(fileContent); //Base64降序列化
InputStream is = new ByteArrayInputStream(fileByte); //将字节转换成InputStream
sendEmail.send(email, content, template.getEmailTemplateName(), is, fileName); //发送邮件
这样,就可以通过我的邮件发送工具发送邮件了,如下
package cn.coralglobal.message.provider.core;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.util.ByteArrayDataSource;
/**
* @Description: 邮件发送
* @Author chenjianwen
* @Date 2020/4/13
**/
@Slf4j
@Component
public class SendEmail {
private static final String COMPANY_MARK = "这里写公司名称";
/**
*
* @param email 邮箱
* @param content 邮件内容
* @param head 邮件名称
* @param is 文件流信息
* @param fileName 文件名称
* @throws IOException
*/
public void send(String email, String content, String head, InputStream is, String fileName) throws IOException {
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
boolean b = false;
String from = "公司邮箱";
final String userName = "用户名";
final String password = "密码";
String host = "smtp.exmail.qq.com";
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.socketFactory.class", SSL_FACTORY);
props.put("mail.smtp.socketFactory.fallback", "false");
props.put("mail.smtp.host", host);
props.put("mail.smtp.port", "465");// 根据http://help.163.com/10/0731/11/6CTUBPT300753VB8.html,非SSL协议的端口为25
props.put("mail.smtp.socketFactory.port", "465");
// 获取会话对象
Session session = Session.getInstance(props, new Authenticator() {
// Authenticator类是一个抽象类{
// 这个getPasswordAuthentication()原本是return null的。
// 需要重写这个方法,也是这个类里面最重要的一个方法。
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password);
}
});
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(email));
String subject = MimeUtility.encodeWord(this.COMPANY_MARK + head, "UTF-8", "Q");
message.setSubject(subject);
//创建Multipart对象,内部包含文件或内容
Multipart multipart = new MimeMultipart();
//发送文件
if(is != null && !StringUtils.isEmpty(fileName)){
MimeBodyPart fileBody = new MimeBodyPart();
DataSource source = new ByteArrayDataSource(is, "application/msexcel");
fileBody.setDataHandler(new DataHandler(source));
fileBody.setFileName(MimeUtility.encodeText(fileName));
multipart.addBodyPart(fileBody);
}
//发送内容
MimeBodyPart text = new MimeBodyPart();
text.setText(content,"UTF-8");
text.setHeader("Content-Type", "text/html; charset=UTF-8");
multipart.addBodyPart(text);
message.setContent(multipart);
message.saveChanges();
Transport.send(message);
} catch (MessagingException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
邮件发送工具引入的包如下:
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
以上就是邮件发送文件的总结。