Spring Boot开发MongoDB应用实践

本文继续上一篇定时任务中提到的邮件服务,简单讲解 Spring Boot 中如何使用 MongoDB 进行应用开发。

上文中提到的这个简易邮件系统大致设计思路如下:

1、发送邮件支持同步和异步发送两种

2、邮件使用 MongDB 进行持久化保存

3、异步发送,直接将邮件批量保存在 MongoDB 中,然后通过后台定时任务发送

4、同步发送,先调用 Spring 的发送邮件功能,接着将邮件批量保存至 MongoDB

5、不论同步还是异步,邮件发送失败,定时任务可配置为进行 N 次重试

一、MongoDB

MongoDB 现在已经是应用比较广泛的文档型 NoSQL 产品,有不少公司都拿 MongoDB 来开发日志系统。随着 MongoDB 的不断迭代更新,据说最新版已经支持 ACID 和事务了。不过鉴于历史上 MongoDB 应用的一些问题,以及考虑到数据持久化和运维的要求,核心业务系统存储的技术选型要非常慎重。

1、什么是 MongoDB

MongoDB 是由 C++ 语言编写的一个基于分布式文件存储的开源数据库系统。MongoDB 将数据存储为一个文档,数据结构由键值 (key=>value) 对组成。MongoDB 文档类似于 JSON 对象(也就是 BSON,10gen 开发的一个数据格式),字段值可以包含其他文档,数组及文档数组。主要优点可以概括如下:

(1)、SchemaLess, 结构灵活,表结构更改非常自由,不用每次修改的时候都付出代价 (想想 RDBMS 修改表结构要注意什么),适合业务快速迭代表结构非常不确定的场景,而且 json 和大多数的语言有天然的契合,还支持数组,嵌套文档等数据类型

(2)、自带高可用,自动主从切换(副本集)

(3)、自带水平分片(分片),内置了路由,配置管理,应用只要连接路由,对应用来说是透明的

2、添加依赖

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
     </dependency>
mongodb

3、添加配置

配置 MongoDB 连接串:

spring.data.mongodb.uri=mongodb://name:pass@ip:port/database?maxPoolSize=256

如果使用多台 MongoDB 数据库服务器,参考配置如下:

spring.data.mongodb.uri=mongodb://user:pwd@ip1:port1,ip2:port2/database?maxPoolSize=512

连接串的一般配置,可以参考:猛击这里

环境搭建好了,下面就是着手编码了。

通常我们会有各种语言的 MongoDB 客户端,直接引入调用 API。在 Spring Boot 中,直接使用 MongoTemplate 即可。

4、定义 DAO 接口

package com.power.demo.mongodb;

import com.power.demo.domain.MailDO;

import java.util.Date;
import java.util.List;

public interface MailDao {
/**
* 批量创建对象
*
*
@param entList
*/
void batchInsert(List<MailDO> entList);

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 创建对象
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> ent
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> insert(MailDO ent);

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 根据ID查询对象
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> mailId
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span>
 <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
MailDO findByMailId(Long mailId);

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 查询一段时间范围内待发送的邮件
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> startTime 开始时间
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> endTime   结束时间
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span>
 <span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
List</span>&lt;MailDO&gt;<span style="color: rgba(0, 0, 0, 1)"> findToSendList(Date startTime, Date endTime);

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 更新
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> ent
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> update(MailDO ent);

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 删除
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> mailId
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> delete(Long mailId);

}

MailDao

5、实现 DAO

package com.power.demo.mongodb;

import com.power.demo.common.AppConst;
import com.power.demo.common.SendStatusType;
import com.power.demo.domain.MailDO;
import com.power.demo.util.CollectionHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;

@Component
public class MailDaoImpl implements MailDao {

@Autowired
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> MongoTemplate mongoTemplate;

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> batchInsert(List&lt;MailDO&gt;<span style="color: rgba(0, 0, 0, 1)"> entList) {

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">分组批量多次插入 每次2000条</span>
    List&lt;List&lt;MailDO&gt;&gt; groupList =<span style="color: rgba(0, 0, 0, 1)"> CollectionHelper.spliceArrays(entList, AppConst.BATCH_RECORD_COUNT);
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (List&lt;MailDO&gt;<span style="color: rgba(0, 0, 0, 1)"> list : groupList) {
        mongoTemplate.insert(list, MailDO.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> insert(MailDO ent) {
    mongoTemplate.save(ent);

}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MailDO findByMailId(Long mailId) {
    Query query </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Query(Criteria.where("mailId"<span style="color: rgba(0, 0, 0, 1)">).is(mailId));
    MailDO ent </span>= mongoTemplate.findOne(query, MailDO.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ent;
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 查询一段时间范围内待发送的邮件
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> startTime 开始时间
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> endTime   结束时间
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span>
 <span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> List&lt;MailDO&gt;<span style="color: rgba(0, 0, 0, 1)"> findToSendList(Date startTime, Date endTime) {
    Query query </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Query(Criteria.where("create_time"<span style="color: rgba(0, 0, 0, 1)">).gte(startTime).lt(endTime)
            .and(</span>"has_delete"<span style="color: rgba(0, 0, 0, 1)">).is(Boolean.FALSE)
            .and(</span>"send_status"<span style="color: rgba(0, 0, 0, 1)">).ne(SendStatusType.SendSuccess.toString())
            .and(</span>"retry_count").lt(AppConst.MAX_RETRY_COUNT)) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">重试次数小于3的记录</span>
            .limit(AppConst.RECORD_COUNT); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">每次取20条</span>
List<MailDO> entList = mongoTemplate.find(query, MailDO.class); return entList; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> update(MailDO ent) {
    Query query </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Query(Criteria.where("_id"<span style="color: rgba(0, 0, 0, 1)">).is(ent.getMailId()));
    Update update </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Update()
            .set(</span>"send_status"<span style="color: rgba(0, 0, 0, 1)">, ent.getSendStatus())
            .set(</span>"retry_count"<span style="color: rgba(0, 0, 0, 1)">, ent.getRetryCount())
            .set(</span>"remark"<span style="color: rgba(0, 0, 0, 1)">, ent.getRemark())
            .set(</span>"modify_time"<span style="color: rgba(0, 0, 0, 1)">, ent.getModifyTime())
            .set(</span>"modify_user"<span style="color: rgba(0, 0, 0, 1)">, ent.getModifyUser());
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">更新查询返回结果集的第一条</span>
    mongoTemplate.updateFirst(query, update, MailDO.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> delete(Long mailId) {
    Query query </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Query(Criteria.where("_id"<span style="color: rgba(0, 0, 0, 1)">).is(mailId));
    mongoTemplate.remove(query, MailDO.</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
}

}

MailDaoImpl

6、数据访问对象实体

实体 MailDO 这里只列举了我在实际开发应用中经常用到的字段,这个实体抽象如下:

package com.power.demo.domain;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.io.Serializable;
import java.util.Date;

@Data
@Document(collection = "mailinfo")
public class MailDO implements Serializable {

</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">long</span> serialVersionUID = 1L<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">唯一主键</span>

@Id
@Field(
"mail_id")
private String mailId;

@Field(</span>"mail_no"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Long mailNo;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">邮件类型 如:Text表示纯文本、HTML等</span>
@Field("mail_type"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String mailType;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">邮件发送人</span>
@Field("from_address"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String fromAddress;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">邮件接收人</span>
@Field("to_address"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String toAddress;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">CC邮件接收人</span>
@Field("cc_address"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String ccAddress;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">BC邮件接收人</span>
@Field("bcc_address"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String bccAddress;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">邮件标题</span>
@Field("subject"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String subject;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">邮件内容</span>
@Field("mail_body"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String mailBody;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">发送优先级 如:Normal表示普通</span>
@Field("send_priority"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String sendPriority;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">处理状态 如:SendWait表示等待发送</span>
@Field("send_status"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String sendStatus;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">是否有附件</span>
@Field("has_attachment"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> hasAttatchment;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">附件保存的绝对地址,如fastdfs返回的url</span>
@Field("attatchment_urls"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String[] attatchmentUrls;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">客户端应用编号或名称  如:CRM、订单、财务、运营等</span>
@Field("client_appid"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String clientAppId;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">是否删除</span>
@Field("has_delete"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> hasDelete;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">发送次数</span>
@Field("retry_count"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> retryCount;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">创建时间</span>
@Field("create_time"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Date createTime;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">创建人</span>
@Field("create_user"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String createUser;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">更新时间</span>
@Field("modify_time"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Date modifyTime;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">更新人</span>
@Field("modify_user"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String modifyUser;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">备注</span>
@Field("remark"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String remark;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">扩展信息</span>
@Field("extend_info"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String extendInfo;

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getMailId() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mailId;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setMailId(String mailId) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.mailId =<span style="color: rgba(0, 0, 0, 1)"> mailId;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Long getMailNo() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mailNo;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setMailNo(Long mailNo) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.mailNo =<span style="color: rgba(0, 0, 0, 1)"> mailNo;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getMailType() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mailType;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setMailType(String mailType) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.mailType =<span style="color: rgba(0, 0, 0, 1)"> mailType;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getFromAddress() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> fromAddress;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setFromAddress(String fromAddress) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.fromAddress =<span style="color: rgba(0, 0, 0, 1)"> fromAddress;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getToAddress() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> toAddress;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setToAddress(String toAddress) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.toAddress =<span style="color: rgba(0, 0, 0, 1)"> toAddress;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getCcAddress() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ccAddress;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setCcAddress(String ccAddress) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.ccAddress =<span style="color: rgba(0, 0, 0, 1)"> ccAddress;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getBccAddress() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bccAddress;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setBccAddress(String bccAddress) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.bccAddress =<span style="color: rgba(0, 0, 0, 1)"> bccAddress;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getSubject() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> subject;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setSubject(String subject) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.subject =<span style="color: rgba(0, 0, 0, 1)"> subject;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getMailBody() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mailBody;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setMailBody(String mailBody) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.mailBody =<span style="color: rgba(0, 0, 0, 1)"> mailBody;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getSendPriority() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sendPriority;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setSendPriority(String sendPriority) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.sendPriority =<span style="color: rgba(0, 0, 0, 1)"> sendPriority;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getSendStatus() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sendStatus;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setSendStatus(String sendStatus) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.sendStatus =<span style="color: rgba(0, 0, 0, 1)"> sendStatus;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> isHasAttatchment() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> hasAttatchment;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> setHasAttatchment(<span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> hasAttatchment) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.hasAttatchment =<span style="color: rgba(0, 0, 0, 1)"> hasAttatchment;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String[] getAttatchmentUrls() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> attatchmentUrls;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setAttatchmentUrls(String[] attatchmentUrls) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.attatchmentUrls =<span style="color: rgba(0, 0, 0, 1)"> attatchmentUrls;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getClientAppId() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> clientAppId;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setClientAppId(String clientAppId) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.clientAppId =<span style="color: rgba(0, 0, 0, 1)"> clientAppId;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> isHasDelete() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> hasDelete;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> setHasDelete(<span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> hasDelete) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.hasDelete =<span style="color: rgba(0, 0, 0, 1)"> hasDelete;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> getRetryCount() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> retryCount;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> setRetryCount(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> retryCount) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.retryCount =<span style="color: rgba(0, 0, 0, 1)"> retryCount;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Date getCreateTime() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> createTime;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setCreateTime(Date createTime) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.createTime =<span style="color: rgba(0, 0, 0, 1)"> createTime;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getCreateUser() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> createUser;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setCreateUser(String createUser) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.createUser =<span style="color: rgba(0, 0, 0, 1)"> createUser;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Date getModifyTime() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> modifyTime;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setModifyTime(Date modifyTime) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.modifyTime =<span style="color: rgba(0, 0, 0, 1)"> modifyTime;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getModifyUser() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> modifyUser;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setModifyUser(String modifyUser) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.modifyUser =<span style="color: rgba(0, 0, 0, 1)"> modifyUser;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getRemark() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> remark;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setRemark(String remark) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.remark =<span style="color: rgba(0, 0, 0, 1)"> remark;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getExtendInfo() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> extendInfo;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setExtendInfo(String extendInfo) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.extendInfo =<span style="color: rgba(0, 0, 0, 1)"> extendInfo;
}

}

MailDO

请大家注意实体上的注解,@Document(collection = "mailinfo") 将会在文档数据库中创建一个 mailinfo 的表,@Id 表示指定该字段为主键, @Field("mail_no") 表示实体字段 mailNo 存在 MongoDB 中的字段名称为 mail_no。

根据 MongoDB 官方文档介绍,如果在插入数据时没有指定主键,MongoDB 会自动给插入行自动加上一个主键 _id,MongoDB 客户端把这个 id 类型称为 ObjectId,看上去就是一个 UUID。我们可以通过注解自己设置主键类型,但是根据实践,_id 名称是无法改变的。@Id 和 @Field("mail_id") 表面看上去是我想创建一个 mail_id 为主键的表,但是实际主键只有 _id 而没有 mail_id。当然,主键的类型不一定非要是 UUID,可以是你自己根据业务生成的唯一流水号等等。

同时,还需要注意 attatchment_urls 这个字段,看上去数组也可以直接存进 MongoDB 中,毕竟 SchemaLess 曾经是 MongoDB 宣传过的比 RDBMS 最明显的优势之一。

7、邮件接口

package com.power.demo.apiservice.impl;

import com.google.common.collect.Lists;
import com.power.demo.apientity.request.BatchSendEmailRequest;
import com.power.demo.apientity.response.BatchSendEmailResponse;
import com.power.demo.apiservice.contract.MailApiService;
import com.power.demo.common.*;
import com.power.demo.domain.MailDO;
import com.power.demo.entity.vo.MailVO;
import com.power.demo.mongodb.MailDao;
import com.power.demo.service.contract.MailService;
import com.power.demo.util.ConfigUtil;
import com.power.demo.util.FastMapperUtil;
import com.power.demo.util.SerialNumberUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

@Component
public class MailApiServiceImpl implements MailApiService {

@Autowired
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> MailService mailService;

@Autowired
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> MailDao mailDao;

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 发送邮件
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> request 请求
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 发送失败的邮件
 *</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> BatchSendEmailResponse batchSendEmail(BatchSendEmailRequest request) {
    BatchSendEmailResponse response </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> BatchSendEmailResponse();
    response.setSuccess(</span>""<span style="color: rgba(0, 0, 0, 1)">);

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (request == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
        response.setFail(</span>"请求为空"<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (request.getMailList() == <span style="color: rgba(0, 0, 255, 1)">null</span> || request.getMailList().size() == 0<span style="color: rgba(0, 0, 0, 1)">) {
        response.setFail(</span>"待发送邮件为空"<span style="color: rgba(0, 0, 0, 1)">);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (response.getIsOK() == <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> response;
    }

    List</span>&lt;MailVO&gt; failedMails = Lists.newArrayList();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">没有处理成功的邮件
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构造邮件对象</span>
    List&lt;MailVO&gt; allMails =<span style="color: rgba(0, 0, 0, 1)"> generateMails(request);

    failedMails </span>=<span style="color: rgba(0, 0, 0, 1)"> processSendMail(allMails);

    response.setFailMailList(failedMails);

    response.setSuccess(String.format(</span>"发送邮件提交成功,发送失败的记录为:%d"<span style="color: rgba(0, 0, 0, 1)">, failedMails.size()));

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> response;
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 构造待发送邮件 特殊字段赋值
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> request 请求
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 发送失败的邮件
 *</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> List&lt;MailVO&gt;<span style="color: rgba(0, 0, 0, 1)"> generateMails(BatchSendEmailRequest request) {
    List</span>&lt;MailVO&gt; allMails =<span style="color: rgba(0, 0, 0, 1)"> Lists.newArrayList();

    </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (MailVO mail : request.getMailList()) {
        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mail == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;
        }

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">默认字段赋值</span>
        mail.setCreateTime(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date());
        mail.setModifyTime(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date());
        mail.setRetryCount(</span>0<span style="color: rgba(0, 0, 0, 1)">);
        mail.setHasDelete(</span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
        mail.setMailNo(SerialNumberUtil.create());

        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StringUtils.isEmpty(mail.getMailType())) {
            mail.setMailType(MailType.TEXT.toString());
        } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (Arrays.stream(MailType.values()).filter(x -&gt; x.toString().equalsIgnoreCase(mail.getMailType())).count() == 0<span style="color: rgba(0, 0, 0, 1)">) {
            mail.setMailType(MailType.TEXT.toString());
        }
        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StringUtils.isEmpty(mail.getSendStatus())) {
            mail.setSendStatus(SendStatusType.SendWait.toString());
        } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (Arrays.stream(SendStatusType.values()).filter(x -&gt; x.toString().equalsIgnoreCase(mail.getSendStatus())).count() == 0<span style="color: rgba(0, 0, 0, 1)">) {
            mail.setSendStatus(SendStatusType.SendWait.toString());
        }
        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StringUtils.isEmpty(mail.getSendPriority())) {
            mail.setSendPriority(SendPriorityType.Normal.toString());
        } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (Arrays.stream(SendPriorityType.values()).filter(x -&gt; x.toString().equalsIgnoreCase(mail.getSendPriority())).count() == 0<span style="color: rgba(0, 0, 0, 1)">) {
            mail.setSendPriority(SendPriorityType.Normal.toString());
        }

        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StringUtils.isEmpty(mail.getMailId())) {
            mail.setMailId(String.valueOf(SerialNumberUtil.create()));
        }

        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StringUtils.isEmpty(mail.getFromAddress())) {
            String fromAddr </span>=<span style="color: rgba(0, 0, 0, 1)"> ConfigUtil.getConfigVal(AppField.MAIL_SENDER_ADDR);
            mail.setFromAddress(fromAddr);
        }

        allMails.add(mail);
    }


    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> allMails;
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 处理邮件
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> allMails 所有邮件
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> 发送失败的邮件
 *</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> List&lt;MailVO&gt; processSendMail(List&lt;MailVO&gt;<span style="color: rgba(0, 0, 0, 1)"> allMails) {
    List</span>&lt;MailVO&gt; failedMails = Lists.newArrayList();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">没有处理成功的邮件</span>
List<MailVO> asyncMails = Lists.newArrayList();//待异步处理的邮件
    <span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (MailVO mail : allMails) {

        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (mail.isSync() == <span style="color: rgba(0, 0, 255, 1)">false</span>) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">异步处理</span>
            <span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">;
        }

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">同步调用</span>
        BizResult&lt;String&gt; bizResult = safeSendMail(mail);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">发送邮件成功</span>
        <span style="color: rgba(0, 0, 255, 1)">if</span> (bizResult.getIsOK() == <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) {

            mail.setSendStatus(SendStatusType.SendSuccess.toString());
            mail.setRemark(</span>"同步发送邮件成功"<span style="color: rgba(0, 0, 0, 1)">);
        } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            mail.setSendStatus(SendStatusType.SendFail.toString());
            mail.setRemark(String.format(</span>"同步发送邮件失败:%s"<span style="color: rgba(0, 0, 0, 1)">, bizResult.getMessage()));

            failedMails.add(mail);
        }

    }

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">批量保存邮件至MongoDB</span>

safeStoreMailList(allMails);

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> failedMails;
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 发送邮件
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> ent 邮件信息
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)">
 *</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> BizResult&lt;String&gt;<span style="color: rgba(0, 0, 0, 1)"> safeSendMail(MailVO ent) {
    BizResult</span>&lt;String&gt; bizSendResult = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;


    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (MailType.TEXT.toString().equalsIgnoreCase(ent.getMailType())) {
        bizSendResult </span>=<span style="color: rgba(0, 0, 0, 1)"> mailService.sendSimpleMail(ent);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (MailType.HTML.toString().equalsIgnoreCase(ent.getMailType())) {
        bizSendResult </span>=<span style="color: rgba(0, 0, 0, 1)"> mailService.sendHtmlMail(ent);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (bizSendResult == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
        bizSendResult </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> BizResult&lt;&gt;(<span style="color: rgba(0, 0, 255, 1)">false</span>, AppConst.SUCCESS, "不支持的邮件类型"<span style="color: rgba(0, 0, 0, 1)">);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bizSendResult;
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 批量保存邮件
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> entList 邮件信息列表
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)">
 *</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> safeStoreMailList(List&lt;MailVO&gt;<span style="color: rgba(0, 0, 0, 1)"> entList) {
    </span><span style="color: rgba(0, 0, 255, 1)">boolean</span> isOK =<span style="color: rgba(0, 0, 0, 1)"> storeMailList(entList);

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (isOK == <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> isOK;
    }

    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 1; i &lt;= AppConst.MAX_RETRY_COUNT; i++<span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            Thread.sleep(</span>100 *<span style="color: rgba(0, 0, 0, 1)"> i);
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception te) {
            te.printStackTrace();
        }

        isOK </span>=<span style="color: rgba(0, 0, 0, 1)"> storeMailList(entList);

        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (isOK == <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
        }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> isOK;
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 存储邮件
 *
 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> entList 邮件信息列表
 * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)">
 *</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span> storeMailList(List&lt;MailVO&gt;<span style="color: rgba(0, 0, 0, 1)"> entList) {
    </span><span style="color: rgba(0, 0, 255, 1)">boolean</span> isOK = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {

        List</span>&lt;MailDO&gt; dbEntList =<span style="color: rgba(0, 0, 0, 1)"> Lists.newArrayList();
        entList.forEach(
                x </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> {
                    MailDO dbEnt </span>= FastMapperUtil.cloneObject(x, MailDO.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
                    dbEntList.add(dbEnt);
                }
        );

        mailDao.batchInsert(dbEntList);

        isOK </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;

    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
        e.printStackTrace();
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> isOK;
}

}

MailApiServiceImpl

到这里,MongoDB 的主要存储和查询就搞定了。

二、邮件

在上面的邮件接口 API 实现中,我们定义了邮件发送服务 MailService,在 Spring Boot 中发送邮件也非常简单。

1、邮件配置

## 邮件配置
spring.mail.host=smtp.xxx.com // 邮箱服务器地址
spring.mail.username=abc@xxx.com // 用户名
spring.mail.password=123456    // 密码
spring.mail.default-encoding=UTF-8
mail.sender.addr=abc@company.com  // 发送者邮箱
mailsetting

2、简单邮件

通过 Spring 的 JavaMailSender 对象,可以轻松实现邮件发送。

发送简单邮件代码:

    /**
     * 发送简单文本邮件
     *
     * @param ent 邮件信息
     **/
    public BizResult<String> sendSimpleMail(MailVO ent) {
        BizResult<String> bizResult = new BizResult<>(true, AppConst.SUCCESS);
        try {
            if (ent == null) {
                bizResult.setFail("邮件信息为空");
                return bizResult;
            }
        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StringUtils.isEmpty(ent.getToAddress())) {
            bizResult.setFail(</span>"简单邮件,接收人邮箱为空"<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bizResult;
        }

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">默认发件人设置</span>
        <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StringUtils.isEmpty(ent.getFromAddress())) {
            ent.setFromAddress(senderAddr);
        }

        SimpleMailMessage message </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SimpleMailMessage();
        message.setFrom(ent.getFromAddress());
        message.setTo(ent.getToAddress());
        message.setCc(ent.getCcAddress());
        message.setBcc(ent.getBccAddress());
        message.setSubject(ent.getSubject());
        message.setText(ent.getMailBody());
        message.setSentDate(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date());

        mailSender.send(message);
        bizResult.setSuccess(</span>"简单邮件已经发送"<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
        e.printStackTrace();
        PowerLogger.error(String.format(</span>"发送简单邮件时发生异常:%s"<span style="color: rgba(0, 0, 0, 1)">, e));

        bizResult.setFail(String.format(</span>"发送简单邮件时发生异常:%s"<span style="color: rgba(0, 0, 0, 1)">, e));

    } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> {
        PowerLogger.info(String.format(</span>"简单邮件,发送结果:%s"<span style="color: rgba(0, 0, 0, 1)">, SerializeUtil.Serialize(bizResult)));
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bizResult;
}</span></pre>
sendSimpleMail

3、HTML 邮件

同理,我们经常要发送带格式的 HTML 邮件,发送代码可以参考如下:

    /**
     * 发送 HTML 邮件
     *
     * @param ent 邮件信息
     **/
    public BizResult<String> sendHtmlMail(MailVO ent) {
        BizResult<String> bizResult = new BizResult<>(true, AppConst.SUCCESS);
        try {
            if (ent == null) {
                bizResult.setFail("邮件信息为空");
                return bizResult;
            }
        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StringUtils.isEmpty(ent.getToAddress())) {
            bizResult.setFail(</span>"HTML邮件,接收人邮箱为空"<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bizResult;
        }

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">默认发件人设置</span>
        <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StringUtils.isEmpty(ent.getFromAddress())) {
            ent.setFromAddress(senderAddr);
        }

        MimeMessage message </span>=<span style="color: rgba(0, 0, 0, 1)"> mailSender.createMimeMessage();

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true表示需要创建一个multipart message</span>
        MimeMessageHelper helper = <span style="color: rgba(0, 0, 255, 1)">new</span> MimeMessageHelper(message, <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);

        helper.setFrom(ent.getFromAddress());
        helper.setTo(ent.getToAddress());
        helper.setCc(ent.getCcAddress());
        helper.setBcc(ent.getBccAddress());
        helper.setSubject(ent.getSubject());
        helper.setText(ent.getMailBody(), </span><span style="color: rgba(0, 0, 255, 1)">true</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true表示是html邮件</span>
        helper.setSentDate(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date());

        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">判断有无附件 循环添加附件</span>
        <span style="color: rgba(0, 0, 255, 1)">if</span> (ent.isHasAttatchment() &amp;&amp; ent.getAttatchmentUrls() != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (String filePath : ent.getAttatchmentUrls()) {
                FileSystemResource file </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> FileSystemResource(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> File(filePath));
                String fileName </span>=<span style="color: rgba(0, 0, 0, 1)"> filePath.substring(filePath.lastIndexOf(File.separator));
                helper.addAttachment(fileName, file);
            }
        }

        mailSender.send(message);
        bizResult.setSuccess(</span>"HTML邮件已经发送"<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
        e.printStackTrace();
        PowerLogger.error(String.format(</span>"发送HTML邮件时发生异常:%s"<span style="color: rgba(0, 0, 0, 1)">, e));

        bizResult.setFail(String.format(</span>"发送HTML邮件时发生异常:%s"<span style="color: rgba(0, 0, 0, 1)">, e));
    } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> {
        PowerLogger.info(String.format(</span>"HTML邮件,发送结果:%s"<span style="color: rgba(0, 0, 0, 1)">, SerializeUtil.Serialize(bizResult)));
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bizResult;
}</span></pre>
sendHtmlMail

邮件附件的处理,本文仅仅是简单示例,实际情况是通常都免不了要上传分布式文件系统,如 FastDFS 等,有空我会继续写一下 Spring Boot 和分布式文件系统的应用实践。

还记得上一篇文章里的定时任务发送邮件吗?贴一下 MailServiceImpl 下的补偿发送实现:

    /**
     * 自动查询并发送邮件
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return
     **/
    public void autoSend(Date startTime, Date endTime) {
        StopWatch watch = DateTimeUtil.StartNew();
    List</span>&lt;MailDO&gt; mailDOList =<span style="color: rgba(0, 0, 0, 1)"> mailDao.findToSendList(startTime, endTime);

    </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (MailDO dbEnt : mailDOList) {

        MailVO ent </span>= FastMapperUtil.cloneObject(dbEnt, MailVO.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);

        BizResult</span>&lt;String&gt; bizSendResult = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;

        </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (MailType.TEXT.toString().equalsIgnoreCase(ent.getMailType())) {
            bizSendResult </span>=<span style="color: rgba(0, 0, 0, 1)"> sendSimpleMail(ent);
        } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (MailType.HTML.toString().equalsIgnoreCase(ent.getMailType())) {
            bizSendResult </span>=<span style="color: rgba(0, 0, 0, 1)"> sendHtmlMail(ent);
        }
        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (bizSendResult == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            bizSendResult </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> BizResult&lt;&gt;(<span style="color: rgba(0, 0, 255, 1)">false</span>, AppConst.SUCCESS, "不支持的邮件类型"<span style="color: rgba(0, 0, 0, 1)">);
        }

        </span><span style="color: rgba(0, 0, 255, 1)">if</span> (bizSendResult.getIsOK() == <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) {

            dbEnt.setSendStatus(SendStatusType.SendSuccess.toString());

        } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            dbEnt.setSendStatus(SendStatusType.SendFail.toString());
        }

        dbEnt.setRetryCount(dbEnt.getRetryCount() </span>+ 1);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">重试次数+1</span>

dbEnt.setRemark(SerializeUtil.Serialize(bizSendResult));
dbEnt.setModifyTime(
new Date());
dbEnt.setModifyUser(
"QuartMailTask");

        mailDao.update(dbEnt);
    }

    watch.stop();

    PowerLogger.info(String.format(</span>"本次共处理记录数:%s,总耗时:%s"<span style="color: rgba(0, 0, 0, 1)">, mailDOList.size(), watch.getTotalTimeMillis()));

}</span></pre>
自动查询并发送邮件

这里贴出来的示例代码是线性的一个一个发送邮件,我们完全可以改造成多线程的并行处理方式来提升邮件发送处理能力。

三、MongoDB 注意事项

1、常见参数设置问题

MongoDB 的默认最大连接数是 100,不同的客户端有不同的实现,对于读多写多的应用,最大连接数可能成为瓶颈。

不过设置最大连接数也要注意内存开销,合理配置连接池 maxPoolSize。

其中,生产环境为了保证高可用,通常会配置副本集连接字符串格式 mongodb://username:password@host1:port1,host2:port2[,...,hostN:portN]/database?options

options 是连接配置中的可选项,replicaSet 是其中的一个子项。

最终的配置连接串可能形如:mongodb://username:password@host1:port1,host2:port2[,...,hostN:portN]/database?replicaSet=yourreplset&maxPoolSize=512

批量插入可以减少数据向服务器提交次数,提高性能,但是批量提交的 BSON 不能超过 48M,不注意这个细节很容易造成数据丢失。

关于常用连接参数,可以参考这里

2、MongoDB 事务性

早期版本的 MongoDB 已经支持行级的事务,支持简单的行级操作原子性,单行的操作要么全部成功,要么全部失败。

MongoDB 的WiredTiger引擎本身支持事务,官方在最新版本中,号称完全支持 ACID 和事务。

3、MongoDB 如何提升查询速度

可以选取合适字段创建索引,和 RDBMS 一样,MongoDB 的索引也有很多种,如:单字段索引、复合索引、多 Key 索引、哈希索引等。

在常见的查询字段上合理添加索引,或者定期归档数据,减少查询数据量,这些手段都可以有效提高查询速度。

还有一种非常常见的手段就是 Sharding,也就是数据库分片技术。当数据量比较大的时候,我们需要把数据分片运行在不同的机器中,以降低 CPU、内存和 IO 的压力。MongoDB 分片技术类似 MySQL 的水平切分和垂直切分,主要由两种方式做 Sharding:垂直扩展和横向切分。垂直扩展的方式就是进行集群扩展,添加更多的 CPU,内存,磁盘空间等。横向切分则是通过数据分片的方式,通过集群统一提供服务。

4、MongoDB 的高可用方案

高可用是绝大多数数据库管理系统的核心目标之一。真正的高可用系统,很少有单实例的应用形态存在。

MongoDB 支持主从方式、双机双工方式(互备互援)和集群工作方式(多服务器互备方式),减少单点出故障的可能。

如果要想生产数据在发生故障后依然可用,就需要确保为生产数据库多部署一台服务器。MongoDB 副本集提供了数据的保护、高可用和灾难恢复的机制。在 MongoDB 中,有两种数据冗余方式, 一种是 Master-Slave 模式(主从复制),一种是 Replica Sets 模式(副本集)。主从复制和副本集使用了相同的复制机制,但是副本集额外增加了自动化灾备机制:如果主节点宕机,其中一个从节点会自动提升为从节点。除此之外,副本集还提供了其他改进,比如更易于恢复和更复杂地部署拓扑网络。集群中没有特定的主库,主库是选举产生,如果主库 down 了,会再选举出一台主库。

 

参考:

<<MongoDB 权威指南 >>

https://docs.mongodb.com/

http://www.runoob.com/mongodb/mongodb-tutorial.html

https://www.cnblogs.com/binyue/p/5901328.html

https://yq.aliyun.com/articles/33726

https://yq.aliyun.com/articles/66623

http://www.cnblogs.com/l1pe1/p/7871790.html