需求背景

  • 日常工作中,对于一个码农来说,熟练使用框架之外,还需要灵活使用各类工具类,类似于导入导出,上传下载,邮件短信,语音视频等常用功能也是非常常见的需求,网上搜索的相关资料也是非常的多,今天就描述一个需求的场景,难度不是太难,只是需要注意的小细节还是挺多的,故借此契机,二狗在此将我个人实现的过程全程记录下来,希望有类似需求的朋友可以参考,但是话说到底,编程这个东西可以看,可以摘录,可以背诵,但是只有自己手敲出来的才是自己的,希望二狗的文章可以帮助更多的朋友把时间花在研究技术原理以及各类底层实现的设计艺术上,而对于业务类的需求,永远千变万化,只需要理解清楚,想顺畅了,就可以动手开干~以上一些个人理解,如有

实现功能:

  • 每周二上午10点,以当天时间为参照时间节点,到当天之前的一周内的这个时间段,把后台的律师信息表中的律师信息,进行汇总,然后通过excel附件的形式,使用阿里邮箱发送到市场运营人员的邮箱;

需求分析:

  1. 每周二上午10点–定时任务解决(需要注意线上多台服务器跑批,执行定时任务需要添加分布锁,避免重复跑批)
  2. 时间段为:(当天-当天前一周]
  3. 通过Excel 附件形式,考虑使用POI生成Excel模板文件,后端读取数据进行填充
  4. 使用阿里邮箱,需要准备好连接邮箱的Host,端口,协议等参数,数据上准备好收件人邮箱账号+发件人邮箱账号,密码

项目技术架构选型描述

  • 本项目使用的是Springboot框架,一些固定参数,比如邮箱的Host,端口,协议等参数,数据上准备好收件人邮箱账号发件人邮箱账号,密码等均参数化的配置在boot框架的yml配置文件中
  • 定时任务使用注解@Scheduled(cron="")方式进行跑批,当然启动类Application类需要添加@EnableScheduling 注解以便于执行定是脚本,在此不再赘述,因为不是本文探讨的重点;
  • 本本文描述的代码基于Idea+ jdk1.8+ 数据库mysql+ Mybatis+ Gradle导包+ Redis缓存

代码过程展示
1、build.gradle 文件 的dependencies { }中先导包,如果你使用的是Maven原理也是一样,可以去 Maven中央仓库搜索你想要的包的版本的Maven写法,在此就不再赘述

//poiimplementation('org.apache.poi:poi:3.16')implementation('org.apache.poi:poi-ooxml:3.14')implementation('org.apache.poi:poi-scratchpad:3.15')//emailimplementation('org.springframework:spring-context-support')implementation('javax.mail:javax.mail-api:1.5.1')implementation('com.sun.mail:javax.mail:1.5.4')

2、yml文件内配置邮箱相关参数

email:host: smtpdm.aliyun.comport: 80senderMail: autoAdmin@wahaha.cnsenderPassword: Wahaha2019sendTo: autoMarketOne@wahahatech.com,autoMarketSecond@wahahatech.com

3、律师信息实体类–使用的Lombok插件通过@Data生成setter 和getter

@Data
public class LawyerAuthenticate {/*** 主键* */private Long id;/*** 律师姓名* */private String  lawName;/*** 所属律所* */private String lawFirm;/*** 律师手机号* */private String phone;/*** 律师常用邮箱* */private String  mail;/*** 律师执业证号* */private String licenseNumber;/*** 律师执业证照片名字* */private String  licenseName;/*** 律师执业证照片url* */private String licenseUrl;/*** 身份证照片名字* */private String idcardName;/*** 身份证照片url* */private String idcardUrl;/*** 创建日期* */private Date createDate;/*** 起始时间--不存库* */private Date startTime;/*** 截止时间--不存库* */private Date endTime;}

4、日期工具类–在本功能内仅仅用到了:nextDay(int num) 这个方法,其他日期常用方法,为了篇幅的简洁,故在此就不一一列举了

/*** 日期相关工具*/
public class DateUtils  {/*** 获取当前日期指定天数之后的日期.* @param num   相隔天数* @return Date 日期* @since 1.0*/public static Date nextDay(int num) {Calendar curr = Calendar.getInstance();curr.set(Calendar.DAY_OF_MONTH, curr.get(Calendar.DAY_OF_MONTH) + num);return curr.getTime();}
}

4、定时任务脚本代码
*注意该接口类下的方法,为本次需求的核心方法:sendMailLawyerAuthService.sendMail();

@Slf4j
@Component
public class SendMailOfLawyerAuthJob {@Autowiredprivate SendMailLawyerAuthService sendMailLawyerAuthService;@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Scheduled(cron = "0 0 10 ? * TUE")protected synchronized void executeInternal() throws InterruptedException {Thread.sleep((int) (Math.random() * 8000));Object judgementCount = this.getJudgementCount();if (null == judgementCount) {// 分布式锁redisTemplate.execute(new RedisCallback<Object>() {@Overridepublic Object doInRedis(RedisConnection connection) throws DataAccessException {byte[] key = redisTemplate.getStringSerializer().serialize("SendMailOfLawyerAuthJob");byte[] value = SerializationHelper.serialize("SendMailOfLawyerAuthJob");connection.setEx(key, 120, value);return null;}});Date now = new Date();//新版律师信息汇总定期发送邮件成功!try {sendMailLawyerAuthService.sendMail();} catch (Exception e){log.error("ErrorMessage: "+ e.getMessage());}//Test测试打印log.info("新版律师信息汇总定期发送邮件任务:SendMailOfLawyerAuthJob 执行完毕!");}}//分布式锁public Object getJudgementCount() {return redisTemplate.execute(new RedisCallback<Object>() {@Overridepublic Object doInRedis(RedisConnection connection) throws DataAccessException {byte[] key = redisTemplate.getStringSerializer().serialize("SendMailOfLawyerAuthJob");byte[] value = connection.get(key);return SerializationHelper.unSerialize(value);}});}}

5、核心方法sendMail() 代码示例

/**发送邮件核心方法*/
public interface SendMailLawyerAuthService {void sendMail() throws IOException;
}

实现类方法—此处非常重要,此处非常重要,此处非常重要(重要的是说三遍!)

/*** Created by 陈二狗 on 2019-09-18.* Desc: 新版律师信息汇总定期发送邮件*/
@Service
public class SendMailLawyerAuthServiceImpl implements SendMailLawyerAuthService {@Autowiredprivate LawyerAuthenticateMapper lawyerAuthenticateMapper;private static final Logger log = LoggerFactory.getLogger(SendMailLawyerAuthServiceImpl.class);@Value("${email.host}")private String emailHost;@Value("${email.port}")private int emailPort;@Value("${email.senderMail}")private String senderMail;@Value("${email.senderPassword}")private String senderPassword;@Value("${email.sendTo}")private List<String> sendTo;/**发送邮件的附件标题后缀*/private static final String FILENAME_END_PREFIX = "新版律师信息汇总.xls";/*** 发送带Excel附件的阿里企业邮件* @throws IOException*/@Overridepublic void sendMail() throws IOException {//获取当前日期的前一周日期Date startTime = DateUtils.nextDay(-7);Date endTime = new Date();//首先查库获取周期范围内的数据信息List<LawyerAuthenticate>  lists = lawyerAuthenticateMapper.sendMailOfLawyerAuth(startTime, endTime);if (CollectionUtils.isEmpty(lists)) {log.info("没有需要发送的认证律师相关的邮件!");return ;}//1、调用创建邮件对象辅助方法Map<String, Object> resultMap = createSendMailHelper();Properties props = (Properties)resultMap.get("props");Authenticator authenticator = (Authenticator)resultMap.get("authenticator");Session session = Session.getInstance(props,authenticator);MimeMessage message = new MimeMessage(session);try {/*** 2、设置发件人* 其中 InternetAddress 的三个参数分别为: 邮箱, 显示的昵称(只用于显示, 没有特别的要求), 昵称的字符集编码*/message.setFrom(new InternetAddress(senderMail, "wusong", "UTF-8"));/*** 3、设置收件人--支持多个,基于收件人肯定存在的情况下哈,因为发邮件如果一个收件人都没有,我·····* To收件人   CC 抄送  BCC密送*/if (sendTo.size() > 0) {InternetAddress[] sendToInfo = new InternetAddress[sendTo.size()];for (int i = 0; i < sendTo.size(); i++) {sendToInfo[i] = new InternetAddress(sendTo.get(i), "", "UTF-8");}message.addRecipients(MimeMessage.RecipientType.TO, sendToInfo);}/*** 4、设置标题*/log.info("导出到Excel");message.setSubject("新版律师认证信息汇总表","UTF-8");HSSFWorkbook workbook = new HSSFWorkbook();CreationHelper helper = workbook.getCreationHelper();HSSFSheet sheet = workbook.createSheet("新版律师认证信息汇总表");//调用设置文件以及填充对象数据方法,具象模板文件setFileFromLawyerInfolHelper(sheet, lists);/****workBook写入输出流******/log.info("workBook写入输出流");ByteArrayOutputStream baos = new ByteArrayOutputStream();workbook.write(baos);baos.flush();baos.close();DataSource fds = new ByteArrayDataSource(baos.toByteArray(), "application/vnd.ms-excel");MimeBodyPart mbp1 = new MimeBodyPart();//此处设置的是邮件内容的body部分,可以对比效果图查看mbp1.setText("你好:\n                 律师信息邮件汇总,请注意查收!");MimeBodyPart mbp2 = new MimeBodyPart();mbp2.setDataHandler(new DataHandler(fds));//5、调用格式化日期方法,生成附件标题String fileNameStr = formatDateHelper();//Javamail源码中MimeBodyPart在setFileName 时候会对文件名进行强制转码,So,针对此进行强制设置编码,避免出现乱码现象mbp2.setFileName(new String(fileNameStr.getBytes(), "ISO8859-1"));Multipart mp = new MimeMultipart();mp.addBodyPart(mbp1);mp.addBodyPart(mbp2);message.setContent(mp);/*** 6、保存邮件并发送*/log.info("保存邮件并发送");message.saveChanges();Transport transport = session.getTransport("smtp");transport.connect(emailHost, senderMail, senderPassword);transport.sendMessage(message,message.getAllRecipients());transport.close();} catch (MessagingException e) {log.error("异常信息为:"+e.getMessage());} catch (UnsupportedEncodingException e) {log.error("异常信息为:"+e.getMessage());}}/**** 创建邮件相关对象辅助方法-001* */private Map<String, Object> createSendMailHelper(){Properties props = new Properties();/**表示SMTP发送邮件,需要进行身份验证*/props.put("mail.smtp.auth", "true");/**是否启用调试*/props.put("mail.debug", "false");/**设置链接超时*/props.put("mail.smtp.timeout", "50000");/**设置端口*/props.put("mail.smtp.port", Integer.toString(emailPort));/**设置ssl端口*/props.put("mail.smtp.socketFactory.port", Integer.toString(emailPort));props.put("mail.smtp.socketFactory.fallback", "false");props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");// 发件人的账号props.put("mail.user", senderMail);// 访问SMTP服务时需要提供的密码(邮箱密码)props.put("mail.password", senderPassword);// 构建授权信息,用于进行SMTP进行身份验证Authenticator authenticator = new Authenticator() {@Overrideprotected PasswordAuthentication getPasswordAuthentication() {// 用户名、密码String userName = props.getProperty("mail.user");String password = props.getProperty("mail.password");return new PasswordAuthentication(userName, password);}};//封装返回对象参数数据Map<String, Object>  mailMap = new HashMap<>();mailMap.put("props",props);mailMap.put("authenticator",authenticator);return mailMap;}/*** 设置文件以及填充数据辅助方法-002* */private void  setFileFromLawyerInfolHelper(HSSFSheet sheet, List<LawyerAuthenticate> lists){//设置列宽度sheet.setColumnWidth(0, 20 * 256);sheet.setColumnWidth(1, 20 * 256);sheet.setColumnWidth(2, 20 * 256);sheet.setColumnWidth(3, 20 * 256);sheet.setColumnWidth(4, 20 * 256);sheet.setColumnWidth(5, 20 * 256);sheet.setColumnWidth(6, 80 * 256);sheet.setColumnWidth(7, 80 * 256);//新增数据行,并且设置单元格数据int rowNum = 1;String[] headers = { "序号","律师姓名","手机号","所属律所","邮箱","执业证号","律师执照照片","手持身份证照片"};//headers表示excel表中第一行的表头HSSFRow row = sheet.createRow(0);//在excel表中添加表头for(int i=0;i<headers.length;i++){HSSFCell cell = row.createCell(i);HSSFRichTextString text = new HSSFRichTextString(headers[i]);cell.setCellValue(text);}//在表中存放查询到的数据放入对应的列for (LawyerAuthenticate lawyerAuthInfo : lists) {HSSFRow row1 = sheet.createRow(rowNum);row1.createCell(0).setCellValue(rowNum);row1.createCell(1).setCellValue(lawyerAuthInfo.getLawName());row1.createCell(2).setCellValue(lawyerAuthInfo.getPhone());row1.createCell(3).setCellValue(lawyerAuthInfo.getLawFirm());row1.createCell(4).setCellValue(lawyerAuthInfo.getMail());row1.createCell(5).setCellValue(lawyerAuthInfo.getLicenseNumber());//由于字段的选填性,手动设置过滤字段为空if (StringUtils.isEmpty(lawyerAuthInfo.getLicenseUrl())){row1.createCell(6).setCellValue("");}else {row1.createCell(6).setCellValue(lawyerAuthInfo.getLicenseUrl());}if (StringUtils.isEmpty(lawyerAuthInfo.getIdcardUrl())){row1.createCell(7).setCellValue("");}else {row1.createCell(7).setCellValue(lawyerAuthInfo.getIdcardUrl());}rowNum++;}}/**** 创建邮件相关对象辅助方法-003* */private String formatDateHelper(){//格式化日期Date startTimeStr = DateUtils.nextDay(-7);Date endTimeStr = new Date();DateFormat sdf = new SimpleDateFormat("yyyyMMdd");String tempfileName = sdf.format(startTimeStr) + "-" + sdf.format(endTimeStr) + FILENAME_END_PREFIX ;return tempfileName;}}

邮件发送代码说明:
1)附件主题乱码,通过读取源码的setFilename()方法可以看到,源码底层对字符串进行了转码,所以我们在代码中,进行了处理,需要注意的是,不同的邮箱可能处理的方式不尽相同,此处我这里使用的是阿里企业邮箱,以下为我的处理方式:

 String fileNameStr = formatDateHelper();//Javamail源码中MimeBodyPart在setFileName 时候会对文件名进行强制转码,So,针对此进行强制设置编码,避免出现乱码现象
mbp2.setFileName(new String(fileNameStr.getBytes(), "ISO8859-1"));

网上很多资料说的方式是在发送邮件之前,添加修改编码代码,如果大家感兴趣可以自行百度,此处不再过多赘述;

2)附件的excel 内各列的宽度可以自定义设置调整,各位看官根据实际需要变更即可

附上效果图:红色1:发件人;红色2:多个收件人

附件详情效果图:

  • 综上所述,整体的功能实现到此基本全部完毕,代码虽然进行了一定的抽离但是届于自身能力以及工作技术架构选型的的局限,解决方案不一定是最优解,但是可以作为有类似需求的看官浏览使用,不足之处,欢迎各位看官提出宝贵的意见,我也会积极响应,长路漫漫,其修远兮,共勉!

Java使用阿里邮箱生成excle邮件附件发送相关推荐

  1. JAVA实现QQ邮箱推送邮件

    JAVA实现QQ邮箱推送邮件 文章目录 JAVA实现QQ邮箱推送邮件 准备 一.在项目中引入JavaMail包 二.开始实现 1.我们在Service类里面编写一个方法,这个方法接收两个参数:1收件人 ...

  2. outlook搜索栏跑到上面去了_Outlook邮箱批量下载邮件附件+快速复制文件名

    今天的文章很简单,但很实用,可用于批量接收并下载别人发给你的邮件附件. 背景 作为老师,每年都会收到许多必须要通过附件接收的学生邮件,但学生多了.作业多了,老师下载学生附件的操作就变得极为枯燥和浪费时 ...

  3. Java 集成阿里大鱼平台短信服务发送验证码到手机

    点击前往:阿里大鱼 --- 短信接口调用错误码(错误原因及处理方式) 上一篇:阿里大鱼短信服务 --- 发送验证码.短信通知 下一篇:Java 集成阿里大鱼平台短信服务发送验证码 --- 补齐注册部分 ...

  4. Java调用阿里云短信接口,发送短信

    Java调用阿里云短信接口,发送短信 1.短信服务这个很简单,只需要知道accessKeyId(AK).accessKeySecret(SK).短信签名.短信模板即可. 2.域名和产品名称是固定的,使 ...

  5. java中让数据生成excle文件并且支持下载

    首先你要是用的是maven项目,你需要下一个jar包,添加一个配置: //在maven中配jar包<dependency> <groupId>net.sourceforge.j ...

  6. Outlook 阿里邮箱 重复收取邮件 的情况 及解决方案

    邮箱 重复收取邮件 的原因 总结原因: 1.客户端异常, 2.网速不好, 3.邮件监控软件(或杀毒软件), 4.邮箱设置代理 5.接收时点了取消或接收超时 最终无外于:客户端问题,网速问题,自己的操作 ...

  7. java 延时发送邮件_java编程实现邮件定时发送的方法

    本文实例讲述了java编程实现邮件定时发送的方法.分享给大家供大家参考,具体如下: 最近做项目时客户提出了一个需求:系统定时发送E-mail到其客户,达到通知的效果.先将实例分享给大家,如果确实有一些 ...

  8. 探讨如何利用C#登录QQ邮箱进行群邮件的发送

    http://www.cnblogs.com/wuhuacong/archive/2011/01/18/1937329.html 网络步入了营销的时代,营销则进入精准.细化的操作阶段,QQ用户群的分类 ...

  9. 如何编写兼容各主流邮箱的HTML邮件并发送

    编写兼容的网页 http://www.kuqin.com/webpagedesign/20080516/8539.html 几乎每个会员制网站都需要通过后台发送邮件来与会员进行沟通,如注册确认.营销推 ...

最新文章

  1. python查找指定文件路径_Python寻找路径和查找文件路径的示例
  2. php背景图添加字,怎样给视频后面加背景图 视频加背景图片并添加一行广告文字...
  3. 微服务架构到底是什么鬼?
  4. 【2018.05.04学习笔记】【linux基础知识10.1-10.5】
  5. 手机modem开发(12)---MTK 平台如何PUSH modem 到手机中进行调试
  6. 无法拒绝APP测试的理由,如果你还不知道,是我的错!
  7. 矩池云课程版使用教程
  8. 开启Mac原生NTFS支持
  9. 飞客蠕虫(Conficker)
  10. 开源VOSK引擎免费语音转文字部署
  11. SDUT-3337 计算长方体、四棱锥的表面积和体积
  12. poj2096(概率dp)
  13. MeteoInfo 生成等值面
  14. 技术管理角度看C++游戏程序员发展
  15. 今日恐慌与贪婪指数为10 恐慌程度有所上升
  16. ms17010利用失败解决一则
  17. 输出青蛙跳台所有路径
  18. Java Statement一次执行多条sql语句
  19. python编程基础-task4-FOR、IF以及while
  20. 国内各大银行数据汇总

热门文章

  1. Reading Ingestion —— Bigtable: A Distributed Storage System for Structured Data
  2. EAS提示对不起当前对象正在被其他操作锁定问题处理
  3. UVA 10118 简单DP
  4. Web后端servlet—使用servlet的Part接口实现单文件多文件上传、以及日期格式转换为sql日期格式的实现
  5. 据说vite还是有坑,不行,那就还用vue-cli吧,命令vue create gua12,记一下,可能过一个星期不看,又忘了
  6. 无监督学习之聚类方法
  7. 惠普测试c语言,HP的分院测试转自HP超话
  8. 前端优化-改善滑动流畅度的几类方法
  9. 【TDA2x学习】番外篇一、TDA2x工程学习手记
  10. Android强行进阶,自定义控件—LayoutManager,android开发视频