本文继续上一篇定时任务中提到的邮件服务,简单讲解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、添加依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-mongodb</artifactId>
  4. </dependency>

mongodb

3、添加配置

配置MongoDB连接串:

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

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

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

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

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

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

4、定义DAO接口

  1. package com.power.demo.mongodb;
  2.  
  3. import com.power.demo.domain.MailDO;
  4.  
  5. import java.util.Date;
  6. import java.util.List;
  7.  
  8. public interface MailDao {
  9. /**
  10. * 批量创建对象
  11. *
  12. * @param entList
  13. */
  14. void batchInsert(List<MailDO> entList);
  15.  
  16. /**
  17. * 创建对象
  18. *
  19. * @param ent
  20. */
  21. void insert(MailDO ent);
  22.  
  23. /**
  24. * 根据ID查询对象
  25. *
  26. * @param mailId
  27. * @return
  28. */
  29. MailDO findByMailId(Long mailId);
  30.  
  31. /**
  32. * 查询一段时间范围内待发送的邮件
  33. *
  34. * @param startTime 开始时间
  35. * @param endTime 结束时间
  36. * @return
  37. */
  38. List<MailDO> findToSendList(Date startTime, Date endTime);
  39.  
  40. /**
  41. * 更新
  42. *
  43. * @param ent
  44. */
  45. void update(MailDO ent);
  46.  
  47. /**
  48. * 删除
  49. *
  50. * @param mailId
  51. */
  52. void delete(Long mailId);
  53.  
  54. }

MailDao

5、实现DAO

  1. package com.power.demo.mongodb;
  2.  
  3. import com.power.demo.common.AppConst;
  4. import com.power.demo.common.SendStatusType;
  5. import com.power.demo.domain.MailDO;
  6. import com.power.demo.util.CollectionHelper;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.data.mongodb.core.MongoTemplate;
  9. import org.springframework.data.mongodb.core.query.Criteria;
  10. import org.springframework.data.mongodb.core.query.Query;
  11. import org.springframework.data.mongodb.core.query.Update;
  12. import org.springframework.stereotype.Component;
  13.  
  14. import java.util.Date;
  15. import java.util.List;
  16.  
  17. @Component
  18. public class MailDaoImpl implements MailDao {
  19.  
  20. @Autowired
  21. private MongoTemplate mongoTemplate;
  22.  
  23. public void batchInsert(List<MailDO> entList) {
  24.  
  25. //分组批量多次插入 每次2000条
  26. List<List<MailDO>> groupList = CollectionHelper.spliceArrays(entList, AppConst.BATCH_RECORD_COUNT);
  27. for (List<MailDO> list : groupList) {
  28. mongoTemplate.insert(list, MailDO.class);
  29. }
  30. }
  31.  
  32. public void insert(MailDO ent) {
  33. mongoTemplate.save(ent);
  34.  
  35. }
  36.  
  37. public MailDO findByMailId(Long mailId) {
  38. Query query = new Query(Criteria.where("mailId").is(mailId));
  39. MailDO ent = mongoTemplate.findOne(query, MailDO.class);
  40. return ent;
  41. }
  42.  
  43. /**
  44. * 查询一段时间范围内待发送的邮件
  45. *
  46. * @param startTime 开始时间
  47. * @param endTime 结束时间
  48. * @return
  49. */
  50. public List<MailDO> findToSendList(Date startTime, Date endTime) {
  51. Query query = new Query(Criteria.where("create_time").gte(startTime).lt(endTime)
  52. .and("has_delete").is(Boolean.FALSE)
  53. .and("send_status").ne(SendStatusType.SendSuccess.toString())
  54. .and("retry_count").lt(AppConst.MAX_RETRY_COUNT)) //重试次数小于3的记录
  55. .limit(AppConst.RECORD_COUNT); //每次取20条
  56.  
  57. List<MailDO> entList = mongoTemplate.find(query, MailDO.class);
  58. return entList;
  59. }
  60.  
  61. public void update(MailDO ent) {
  62. Query query = new Query(Criteria.where("_id").is(ent.getMailId()));
  63. Update update = new Update()
  64. .set("send_status", ent.getSendStatus())
  65. .set("retry_count", ent.getRetryCount())
  66. .set("remark", ent.getRemark())
  67. .set("modify_time", ent.getModifyTime())
  68. .set("modify_user", ent.getModifyUser());
  69. //更新查询返回结果集的第一条
  70. mongoTemplate.updateFirst(query, update, MailDO.class);
  71. }
  72.  
  73. public void delete(Long mailId) {
  74. Query query = new Query(Criteria.where("_id").is(mailId));
  75. mongoTemplate.remove(query, MailDO.class);
  76. }
  77. }

MailDaoImpl

6、数据访问对象实体

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

  1. package com.power.demo.domain;
  2.  
  3. import lombok.Data;
  4. import org.springframework.data.annotation.Id;
  5. import org.springframework.data.mongodb.core.mapping.Document;
  6. import org.springframework.data.mongodb.core.mapping.Field;
  7.  
  8. import java.io.Serializable;
  9. import java.util.Date;
  10.  
  11. @Data
  12. @Document(collection = "mailinfo")
  13. public class MailDO implements Serializable {
  14.  
  15. private static final long serialVersionUID = 1L;
  16.  
  17. //唯一主键
  18. @Id
  19. @Field("mail_id")
  20. private String mailId;
  21.  
  22. @Field("mail_no")
  23. private Long mailNo;
  24.  
  25. //邮件类型 如:Text表示纯文本、HTML等
  26. @Field("mail_type")
  27. private String mailType;
  28.  
  29. //邮件发送人
  30. @Field("from_address")
  31. private String fromAddress;
  32.  
  33. //邮件接收人
  34. @Field("to_address")
  35. private String toAddress;
  36.  
  37. //CC邮件接收人
  38. @Field("cc_address")
  39. private String ccAddress;
  40.  
  41. //BC邮件接收人
  42. @Field("bcc_address")
  43. private String bccAddress;
  44.  
  45. //邮件标题
  46. @Field("subject")
  47. private String subject;
  48.  
  49. //邮件内容
  50. @Field("mail_body")
  51. private String mailBody;
  52.  
  53. //发送优先级 如:Normal表示普通
  54. @Field("send_priority")
  55. private String sendPriority;
  56.  
  57. //处理状态 如:SendWait表示等待发送
  58. @Field("send_status")
  59. private String sendStatus;
  60.  
  61. //是否有附件
  62. @Field("has_attachment")
  63. private boolean hasAttatchment;
  64.  
  65. //附件保存的绝对地址,如fastdfs返回的url
  66. @Field("attatchment_urls")
  67. private String[] attatchmentUrls;
  68.  
  69. //客户端应用编号或名称 如:CRM、订单、财务、运营等
  70. @Field("client_appid")
  71. private String clientAppId;
  72.  
  73. //是否删除
  74. @Field("has_delete")
  75. private boolean hasDelete;
  76.  
  77. //发送次数
  78. @Field("retry_count")
  79. private int retryCount;
  80.  
  81. //创建时间
  82. @Field("create_time")
  83. private Date createTime;
  84.  
  85. //创建人
  86. @Field("create_user")
  87. private String createUser;
  88.  
  89. //更新时间
  90. @Field("modify_time")
  91. private Date modifyTime;
  92.  
  93. //更新人
  94. @Field("modify_user")
  95. private String modifyUser;
  96.  
  97. //备注
  98. @Field("remark")
  99. private String remark;
  100.  
  101. //扩展信息
  102. @Field("extend_info")
  103. private String extendInfo;
  104.  
  105. public String getMailId() {
  106. return mailId;
  107. }
  108.  
  109. public void setMailId(String mailId) {
  110. this.mailId = mailId;
  111. }
  112.  
  113. public Long getMailNo() {
  114. return mailNo;
  115. }
  116.  
  117. public void setMailNo(Long mailNo) {
  118. this.mailNo = mailNo;
  119. }
  120.  
  121. public String getMailType() {
  122. return mailType;
  123. }
  124.  
  125. public void setMailType(String mailType) {
  126. this.mailType = mailType;
  127. }
  128.  
  129. public String getFromAddress() {
  130. return fromAddress;
  131. }
  132.  
  133. public void setFromAddress(String fromAddress) {
  134. this.fromAddress = fromAddress;
  135. }
  136.  
  137. public String getToAddress() {
  138. return toAddress;
  139. }
  140.  
  141. public void setToAddress(String toAddress) {
  142. this.toAddress = toAddress;
  143. }
  144.  
  145. public String getCcAddress() {
  146. return ccAddress;
  147. }
  148.  
  149. public void setCcAddress(String ccAddress) {
  150. this.ccAddress = ccAddress;
  151. }
  152.  
  153. public String getBccAddress() {
  154. return bccAddress;
  155. }
  156.  
  157. public void setBccAddress(String bccAddress) {
  158. this.bccAddress = bccAddress;
  159. }
  160.  
  161. public String getSubject() {
  162. return subject;
  163. }
  164.  
  165. public void setSubject(String subject) {
  166. this.subject = subject;
  167. }
  168.  
  169. public String getMailBody() {
  170. return mailBody;
  171. }
  172.  
  173. public void setMailBody(String mailBody) {
  174. this.mailBody = mailBody;
  175. }
  176.  
  177. public String getSendPriority() {
  178. return sendPriority;
  179. }
  180.  
  181. public void setSendPriority(String sendPriority) {
  182. this.sendPriority = sendPriority;
  183. }
  184.  
  185. public String getSendStatus() {
  186. return sendStatus;
  187. }
  188.  
  189. public void setSendStatus(String sendStatus) {
  190. this.sendStatus = sendStatus;
  191. }
  192.  
  193. public boolean isHasAttatchment() {
  194. return hasAttatchment;
  195. }
  196.  
  197. public void setHasAttatchment(boolean hasAttatchment) {
  198. this.hasAttatchment = hasAttatchment;
  199. }
  200.  
  201. public String[] getAttatchmentUrls() {
  202. return attatchmentUrls;
  203. }
  204.  
  205. public void setAttatchmentUrls(String[] attatchmentUrls) {
  206. this.attatchmentUrls = attatchmentUrls;
  207. }
  208.  
  209. public String getClientAppId() {
  210. return clientAppId;
  211. }
  212.  
  213. public void setClientAppId(String clientAppId) {
  214. this.clientAppId = clientAppId;
  215. }
  216.  
  217. public boolean isHasDelete() {
  218. return hasDelete;
  219. }
  220.  
  221. public void setHasDelete(boolean hasDelete) {
  222. this.hasDelete = hasDelete;
  223. }
  224.  
  225. public int getRetryCount() {
  226. return retryCount;
  227. }
  228.  
  229. public void setRetryCount(int retryCount) {
  230. this.retryCount = retryCount;
  231. }
  232.  
  233. public Date getCreateTime() {
  234. return createTime;
  235. }
  236.  
  237. public void setCreateTime(Date createTime) {
  238. this.createTime = createTime;
  239. }
  240.  
  241. public String getCreateUser() {
  242. return createUser;
  243. }
  244.  
  245. public void setCreateUser(String createUser) {
  246. this.createUser = createUser;
  247. }
  248.  
  249. public Date getModifyTime() {
  250. return modifyTime;
  251. }
  252.  
  253. public void setModifyTime(Date modifyTime) {
  254. this.modifyTime = modifyTime;
  255. }
  256.  
  257. public String getModifyUser() {
  258. return modifyUser;
  259. }
  260.  
  261. public void setModifyUser(String modifyUser) {
  262. this.modifyUser = modifyUser;
  263. }
  264.  
  265. public String getRemark() {
  266. return remark;
  267. }
  268.  
  269. public void setRemark(String remark) {
  270. this.remark = remark;
  271. }
  272.  
  273. public String getExtendInfo() {
  274. return extendInfo;
  275. }
  276.  
  277. public void setExtendInfo(String extendInfo) {
  278. this.extendInfo = extendInfo;
  279. }
  280. }

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、邮件接口

  1. package com.power.demo.apiservice.impl;
  2.  
  3. import com.google.common.collect.Lists;
  4. import com.power.demo.apientity.request.BatchSendEmailRequest;
  5. import com.power.demo.apientity.response.BatchSendEmailResponse;
  6. import com.power.demo.apiservice.contract.MailApiService;
  7. import com.power.demo.common.*;
  8. import com.power.demo.domain.MailDO;
  9. import com.power.demo.entity.vo.MailVO;
  10. import com.power.demo.mongodb.MailDao;
  11. import com.power.demo.service.contract.MailService;
  12. import com.power.demo.util.ConfigUtil;
  13. import com.power.demo.util.FastMapperUtil;
  14. import com.power.demo.util.SerialNumberUtil;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.stereotype.Component;
  17. import org.springframework.util.StringUtils;
  18.  
  19. import java.util.Arrays;
  20. import java.util.Date;
  21. import java.util.List;
  22.  
  23. @Component
  24. public class MailApiServiceImpl implements MailApiService {
  25.  
  26. @Autowired
  27. private MailService mailService;
  28.  
  29. @Autowired
  30. private MailDao mailDao;
  31.  
  32. /**
  33. * 发送邮件
  34. *
  35. * @param request 请求
  36. * @return 发送失败的邮件
  37. **/
  38. public BatchSendEmailResponse batchSendEmail(BatchSendEmailRequest request) {
  39. BatchSendEmailResponse response = new BatchSendEmailResponse();
  40. response.setSuccess("");
  41.  
  42. if (request == null) {
  43. response.setFail("请求为空");
  44. } else if (request.getMailList() == null || request.getMailList().size() == 0) {
  45. response.setFail("待发送邮件为空");
  46. }
  47.  
  48. if (response.getIsOK() == false) {
  49. return response;
  50. }
  51.  
  52. List<MailVO> failedMails = Lists.newArrayList();//没有处理成功的邮件
  53. //构造邮件对象
  54. List<MailVO> allMails = generateMails(request);
  55.  
  56. failedMails = processSendMail(allMails);
  57.  
  58. response.setFailMailList(failedMails);
  59.  
  60. response.setSuccess(String.format("发送邮件提交成功,发送失败的记录为:%d", failedMails.size()));
  61.  
  62. return response;
  63. }
  64.  
  65. /**
  66. * 构造待发送邮件 特殊字段赋值
  67. *
  68. * @param request 请求
  69. * @return 发送失败的邮件
  70. **/
  71. private List<MailVO> generateMails(BatchSendEmailRequest request) {
  72. List<MailVO> allMails = Lists.newArrayList();
  73.  
  74. for (MailVO mail : request.getMailList()) {
  75. if (mail == null) {
  76. continue;
  77. }
  78.  
  79. //默认字段赋值
  80. mail.setCreateTime(new Date());
  81. mail.setModifyTime(new Date());
  82. mail.setRetryCount(0);
  83. mail.setHasDelete(false);
  84. mail.setMailNo(SerialNumberUtil.create());
  85.  
  86. if (StringUtils.isEmpty(mail.getMailType())) {
  87. mail.setMailType(MailType.TEXT.toString());
  88. } else if (Arrays.stream(MailType.values()).filter(x -> x.toString().equalsIgnoreCase(mail.getMailType())).count() == 0) {
  89. mail.setMailType(MailType.TEXT.toString());
  90. }
  91. if (StringUtils.isEmpty(mail.getSendStatus())) {
  92. mail.setSendStatus(SendStatusType.SendWait.toString());
  93. } else if (Arrays.stream(SendStatusType.values()).filter(x -> x.toString().equalsIgnoreCase(mail.getSendStatus())).count() == 0) {
  94. mail.setSendStatus(SendStatusType.SendWait.toString());
  95. }
  96. if (StringUtils.isEmpty(mail.getSendPriority())) {
  97. mail.setSendPriority(SendPriorityType.Normal.toString());
  98. } else if (Arrays.stream(SendPriorityType.values()).filter(x -> x.toString().equalsIgnoreCase(mail.getSendPriority())).count() == 0) {
  99. mail.setSendPriority(SendPriorityType.Normal.toString());
  100. }
  101.  
  102. if (StringUtils.isEmpty(mail.getMailId())) {
  103. mail.setMailId(String.valueOf(SerialNumberUtil.create()));
  104. }
  105.  
  106. if (StringUtils.isEmpty(mail.getFromAddress())) {
  107. String fromAddr = ConfigUtil.getConfigVal(AppField.MAIL_SENDER_ADDR);
  108. mail.setFromAddress(fromAddr);
  109. }
  110.  
  111. allMails.add(mail);
  112. }
  113.  
  114. return allMails;
  115. }
  116.  
  117. /**
  118. * 处理邮件
  119. *
  120. * @param allMails 所有邮件
  121. * @return 发送失败的邮件
  122. **/
  123. private List<MailVO> processSendMail(List<MailVO> allMails) {
  124. List<MailVO> failedMails = Lists.newArrayList();//没有处理成功的邮件
  125.  
  126. List<MailVO> asyncMails = Lists.newArrayList();//待异步处理的邮件
  127.  
  128. for (MailVO mail : allMails) {
  129.  
  130. if (mail.isSync() == false) { //异步处理
  131. continue;
  132. }
  133.  
  134. //同步调用
  135. BizResult<String> bizResult = safeSendMail(mail);//发送邮件成功
  136. if (bizResult.getIsOK() == true) {
  137.  
  138. mail.setSendStatus(SendStatusType.SendSuccess.toString());
  139. mail.setRemark("同步发送邮件成功");
  140. } else {
  141. mail.setSendStatus(SendStatusType.SendFail.toString());
  142. mail.setRemark(String.format("同步发送邮件失败:%s", bizResult.getMessage()));
  143.  
  144. failedMails.add(mail);
  145. }
  146.  
  147. }
  148.  
  149. //批量保存邮件至MongoDB
  150. safeStoreMailList(allMails);
  151.  
  152. return failedMails;
  153. }
  154.  
  155. /**
  156. * 发送邮件
  157. *
  158. * @param ent 邮件信息
  159. * @return
  160. **/
  161. private BizResult<String> safeSendMail(MailVO ent) {
  162. BizResult<String> bizSendResult = null;
  163.  
  164. if (MailType.TEXT.toString().equalsIgnoreCase(ent.getMailType())) {
  165. bizSendResult = mailService.sendSimpleMail(ent);
  166. } else if (MailType.HTML.toString().equalsIgnoreCase(ent.getMailType())) {
  167. bizSendResult = mailService.sendHtmlMail(ent);
  168. }
  169.  
  170. if (bizSendResult == null) {
  171. bizSendResult = new BizResult<>(false, AppConst.SUCCESS, "不支持的邮件类型");
  172. }
  173.  
  174. return bizSendResult;
  175. }
  176.  
  177. /**
  178. * 批量保存邮件
  179. *
  180. * @param entList 邮件信息列表
  181. * @return
  182. **/
  183. private boolean safeStoreMailList(List<MailVO> entList) {
  184. boolean isOK = storeMailList(entList);
  185.  
  186. if (isOK == true) {
  187. return isOK;
  188. }
  189.  
  190. for (int i = 1; i <= AppConst.MAX_RETRY_COUNT; i++) {
  191. try {
  192. Thread.sleep(100 * i);
  193. } catch (Exception te) {
  194. te.printStackTrace();
  195. }
  196.  
  197. isOK = storeMailList(entList);
  198.  
  199. if (isOK == true) {
  200. break;
  201. }
  202. }
  203.  
  204. return isOK;
  205. }
  206.  
  207. /**
  208. * 存储邮件
  209. *
  210. * @param entList 邮件信息列表
  211. * @return
  212. **/
  213. private boolean storeMailList(List<MailVO> entList) {
  214. boolean isOK = false;
  215.  
  216. try {
  217.  
  218. List<MailDO> dbEntList = Lists.newArrayList();
  219. entList.forEach(
  220. x -> {
  221. MailDO dbEnt = FastMapperUtil.cloneObject(x, MailDO.class);
  222. dbEntList.add(dbEnt);
  223. }
  224. );
  225.  
  226. mailDao.batchInsert(dbEntList);
  227.  
  228. isOK = true;
  229.  
  230. } catch (Exception e) {
  231. e.printStackTrace();
  232. }
  233.  
  234. return isOK;
  235. }
  236.  
  237. }

MailApiServiceImpl

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

二、邮件

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

1、邮件配置

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

mailsetting

2、简单邮件

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

发送简单邮件代码:

  1. /**
  2. * 发送简单文本邮件
  3. *
  4. * @param ent 邮件信息
  5. **/
  6. public BizResult<String> sendSimpleMail(MailVO ent) {
  7. BizResult<String> bizResult = new BizResult<>(true, AppConst.SUCCESS);
  8. try {
  9. if (ent == null) {
  10. bizResult.setFail("邮件信息为空");
  11. return bizResult;
  12. }
  13.  
  14. if (StringUtils.isEmpty(ent.getToAddress())) {
  15. bizResult.setFail("简单邮件,接收人邮箱为空");
  16. return bizResult;
  17. }
  18.  
  19. //默认发件人设置
  20. if (StringUtils.isEmpty(ent.getFromAddress())) {
  21. ent.setFromAddress(senderAddr);
  22. }
  23.  
  24. SimpleMailMessage message = new SimpleMailMessage();
  25. message.setFrom(ent.getFromAddress());
  26. message.setTo(ent.getToAddress());
  27. message.setCc(ent.getCcAddress());
  28. message.setBcc(ent.getBccAddress());
  29. message.setSubject(ent.getSubject());
  30. message.setText(ent.getMailBody());
  31. message.setSentDate(new Date());
  32.  
  33. mailSender.send(message);
  34. bizResult.setSuccess("简单邮件已经发送");
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. PowerLogger.error(String.format("发送简单邮件时发生异常:%s", e));
  38.  
  39. bizResult.setFail(String.format("发送简单邮件时发生异常:%s", e));
  40.  
  41. } finally {
  42. PowerLogger.info(String.format("简单邮件,发送结果:%s", SerializeUtil.Serialize(bizResult)));
  43. }
  44.  
  45. return bizResult;
  46. }

sendSimpleMail

3、HTML邮件

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

  1. /**
  2. * 发送HTML邮件
  3. *
  4. * @param ent 邮件信息
  5. **/
  6. public BizResult<String> sendHtmlMail(MailVO ent) {
  7. BizResult<String> bizResult = new BizResult<>(true, AppConst.SUCCESS);
  8. try {
  9. if (ent == null) {
  10. bizResult.setFail("邮件信息为空");
  11. return bizResult;
  12. }
  13.  
  14. if (StringUtils.isEmpty(ent.getToAddress())) {
  15. bizResult.setFail("HTML邮件,接收人邮箱为空");
  16. return bizResult;
  17. }
  18.  
  19. //默认发件人设置
  20. if (StringUtils.isEmpty(ent.getFromAddress())) {
  21. ent.setFromAddress(senderAddr);
  22. }
  23.  
  24. MimeMessage message = mailSender.createMimeMessage();
  25.  
  26. //true表示需要创建一个multipart message
  27. MimeMessageHelper helper = new MimeMessageHelper(message, true);
  28.  
  29. helper.setFrom(ent.getFromAddress());
  30. helper.setTo(ent.getToAddress());
  31. helper.setCc(ent.getCcAddress());
  32. helper.setBcc(ent.getBccAddress());
  33. helper.setSubject(ent.getSubject());
  34. helper.setText(ent.getMailBody(), true);//true表示是html邮件
  35. helper.setSentDate(new Date());
  36.  
  37. //判断有无附件 循环添加附件
  38. if (ent.isHasAttatchment() && ent.getAttatchmentUrls() != null) {
  39. for (String filePath : ent.getAttatchmentUrls()) {
  40. FileSystemResource file = new FileSystemResource(new File(filePath));
  41. String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
  42. helper.addAttachment(fileName, file);
  43. }
  44. }
  45.  
  46. mailSender.send(message);
  47. bizResult.setSuccess("HTML邮件已经发送");
  48. } catch (Exception e) {
  49. e.printStackTrace();
  50. PowerLogger.error(String.format("发送HTML邮件时发生异常:%s", e));
  51.  
  52. bizResult.setFail(String.format("发送HTML邮件时发生异常:%s", e));
  53. } finally {
  54. PowerLogger.info(String.format("HTML邮件,发送结果:%s", SerializeUtil.Serialize(bizResult)));
  55. }
  56.  
  57. return bizResult;
  58. }

sendHtmlMail

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

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

  1. /**
  2. * 自动查询并发送邮件
  3. *
  4. * @param startTime 开始时间
  5. * @param endTime 结束时间
  6. * @return
  7. **/
  8. public void autoSend(Date startTime, Date endTime) {
  9. StopWatch watch = DateTimeUtil.StartNew();
  10.  
  11. List<MailDO> mailDOList = mailDao.findToSendList(startTime, endTime);
  12.  
  13. for (MailDO dbEnt : mailDOList) {
  14.  
  15. MailVO ent = FastMapperUtil.cloneObject(dbEnt, MailVO.class);
  16.  
  17. BizResult<String> bizSendResult = null;
  18.  
  19. if (MailType.TEXT.toString().equalsIgnoreCase(ent.getMailType())) {
  20. bizSendResult = sendSimpleMail(ent);
  21. } else if (MailType.HTML.toString().equalsIgnoreCase(ent.getMailType())) {
  22. bizSendResult = sendHtmlMail(ent);
  23. }
  24. if (bizSendResult == null) {
  25. bizSendResult = new BizResult<>(false, AppConst.SUCCESS, "不支持的邮件类型");
  26. }
  27.  
  28. if (bizSendResult.getIsOK() == true) {
  29.  
  30. dbEnt.setSendStatus(SendStatusType.SendSuccess.toString());
  31.  
  32. } else {
  33. dbEnt.setSendStatus(SendStatusType.SendFail.toString());
  34. }
  35.  
  36. dbEnt.setRetryCount(dbEnt.getRetryCount() + 1);//重试次数+1
  37. dbEnt.setRemark(SerializeUtil.Serialize(bizSendResult));
  38. dbEnt.setModifyTime(new Date());
  39. dbEnt.setModifyUser("QuartMailTask");
  40.  
  41. mailDao.update(dbEnt);
  42. }
  43.  
  44. watch.stop();
  45.  
  46. PowerLogger.info(String.format("本次共处理记录数:%s,总耗时:%s", mailDOList.size(), watch.getTotalTimeMillis()));
  47.  
  48. }

自动查询并发送邮件

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

三、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

Spring Boot开发MongoDB应用实践的更多相关文章

  1. springboot(十一):Spring boot中mongodb的使用

    mongodb是最早热门非关系数据库的之一,使用也比较普遍,一般会用做离线数据分析来使用,放到内网的居多.由于很多公司使用了云服务,服务器默认都开放了外网地址,导致前一阵子大批 MongoDB 因配置 ...

  2. (转)Spring Boot(十一):Spring Boot 中 MongoDB 的使用

    http://www.ityouknow.com/springboot/2017/05/08/spring-boot-mongodb.html MongoDB 是最早热门非关系数据库的之一,使用也比较 ...

  3. Spring Boot(十一):Spring Boot 中 MongoDB 的使用

    MongoDB 是最早热门非关系数据库的之一,使用也比较普遍,一般会用做离线数据分析来使用,放到内网的居多.由于很多公司使用了云服务,服务器默认都开放了外网地址,导致前一阵子大批 MongoDB 因配 ...

  4. 使用spring boot访问mongodb数据库

    一. spring boot中传参的方法 1.自动化配置 spring Boot 对于开发人员最大的好处在于可以对 Spring 应用进行自动配置.Spring Boot 会根据应用中声明的第三方依赖 ...

  5. 天天玩微信,Spring Boot 开发私有即时通信系统了解一下

    1/ 概述 利用Spring Boot作为基础框架,Spring Security作为安全框架,WebSocket作为通信框架,实现点对点聊天和群聊天. 2/ 所需依赖 Spring Boot 版本 ...

  6. Spring Boot 开发集成 WebSocket,实现私有即时通信系统

    1/ 概述 利用Spring Boot作为基础框架,Spring Security作为安全框架,WebSocket作为通信框架,实现点对点聊天和群聊天. 2/ 所需依赖 Spring Boot 版本 ...

  7. 使用Spring Boot开发Web项目(二)之添加HTTPS支持

    上篇博客使用Spring Boot开发Web项目我们简单介绍了使用如何使用Spring Boot创建一个使用了Thymeleaf模板引擎的Web项目,当然这还远远不够.今天我们再来看看如何给我们的We ...

  8. 使用Spring boot开发RestFul 风格项目PUT/DELETE方法不起作用

    在使用Spring boot 开发restful 风格的项目,put.delete方法不起作用,解决办法. 实体类Student @Data public class Student { privat ...

  9. Spring Boot 开发系列一 开发环境的一些九九

    从今天开始写这个Spring Boot 开发系列,我是第二周学习JAVA的,公司号称springboot把JAVA的开发提升到填空的能力,本人是NET转JAVA的,想看看这个填空的东西到底有多强.废话 ...

随机推荐

  1. Java-IO之ByteArrayInputStream

    ByteArrayInputStream是字节数组输入流,继承于InputStream.它包含了一个内部缓冲区,该缓冲区包含从流中读取的字节,其实内部缓冲区就是一个字节数组,而ByteArrayInp ...

  2. JSP标签JSTL的使用(1)--表达式操作

    单纯的使用jsp脚本来进行逻辑处理,显得代码很是杂乱.为了更加简洁也为了便于代码的阅读,于是JSTL应运而生. 库文件下载地址: 我自己上传的一份压缩文件,里面包含了所有需要的jar包,而且不需要积分 ...

  3. 见过的最全的iOS面试题

    之前看了很多面试题,感觉要不是不够就是过于冗余,于是我将网上的一些面试题进行了删减和重排,现在分享给大家.(题目来源于网络,侵删) 1. Object-c的类可以多重继承么?可以实现多个接口么?Cat ...

  4. WIP完工入库及完工退回的几个重要问题

    1.必须向CST_COMP_SNAP_INTERFACE表中插入此工单所有工序的数据(也就是说同样的工单插入多条,只是工序号不一样) 标准文档: Note: If there are multiple ...

  5. 【一天一道LeetCode】#95. Unique Binary Search Trees II

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...

  6. 《.NET最佳实践》与Ext JS/Touch的团队开发

    概述 持续集成 编码规范 测试 小结 概述 有不少开发人员都问过我,Ext JS/Touch是否支持团队开发?对于这个问题,我可以毫不犹豫的回答:支持.原因是在Sencha官网博客中客户示例中,有不少 ...

  7. TCP的定时器系列 — 超时重传定时器

    主要内容:TCP定时器概述,超时重传定时器.ER延迟定时器.PTO定时器的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd Q:一条TCP连接会使用 ...

  8. 小强的HTML5移动开发之路(16)——神奇的拖放功能

    来自:http://blog.csdn.net/dawanganban/article/details/18181273 在智能手机发展飞速的现在拖放功能已经成为一种时尚,但是在我们的浏览器上是不是还 ...

  9. (NO.00001)iOS游戏SpeedBoy Lite成形记(三十):增加排行榜功能3

    在这个例子中,我们的显示代码只需要选手的名字以及对应的成绩.根据选手名字取对应的成绩可以用前面实现的playerRecord:方法,我们只需要将按照成绩排序后的选手名字返回就可以了. 我只需要再实现一 ...

  10. Oracle Inventory Management Application Program Interface ( APIs)

    In this Document   Goal   Solution   References APPLIES TO: Oracle Inventory Management - Version 12 ...