Java代码读取电子邮件(含附件)并解析简历文件

前述

使用场景是系统中要导入一部分简历,而这部分简历是保存在邮件中的,需要每一封都查询下,并解析出来保存到数据库中。
1、读取邮件列表,并筛查是否是需要的简历
2、对简历进行解析,并将解析字段保存到数据库中

Java读取邮件

pom引入(含后面的上传阿里云和json解析)

		
			org.springframework.boot
			spring-boot-starter-mail
		
		
			com.aliyun
			aliyun-java-sdk-core
			4.4.0
		
		
			com.aliyun.oss
			aliyun-sdk-oss
			3.10.2
		
		
			com.alibaba
			fastjson
			1.2.60
		
		
			org.jsoup
			jsoup
			1.12.1
		
		
			org.apache.httpcomponents
			httpmime
		
		
			org.apache.commons
			commons-lang3
		

有几个辅助方法。

文本解码

很多时候读取出来的邮件标题等字段,需要解码后才能正常显示,该方法用来将部分字段以汉字形式展示。

	private static String decodeText(String encodeText) throws UnsupportedEncodingException {
		if (encodeText == null || "".equals(encodeText)) {
			return "";
		} else {
			return MimeUtility.decodeText(encodeText);
		}
	}
读取附件

读取附件,将附件名称赋值给传入的fileName,并返回附件的InputStream。
下面代码的问题是只会返回第一个附件,如果邮件有多个附件需要返回,可自行进行改造。因为本人项目只会有一个附件,所以就这样用了。

	private static InputStream getAttachment(Part part, StringBuffer fileName)
			throws UnsupportedEncodingException, MessagingException, FileNotFoundException, IOException {
		if (part.isMimeType("multipart/*")) {
			Multipart multipart = (Multipart) part.getContent();
			int partCount = multipart.getCount();
			for (int i = 1; i < partCount; i++) {
				BodyPart bodyPart = multipart.getBodyPart(i);
				if(bodyPart == null) {
					continue;
				}
				String disp = bodyPart.getDisposition();
				if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
					InputStream is = bodyPart.getInputStream();
					fileName = fileName.append(MimeUtility.decodeText(bodyPart.getFileName()));
					return is;
				} else if (bodyPart.isMimeType("multipart/*")) {
					return getAttachment(bodyPart, fileName);
				} else {
					String contentType = bodyPart.getContentType();
					if (contentType.indexOf("name") != -1 || contentType.indexOf("application") != -1) {
						InputStream is = bodyPart.getInputStream();
						fileName = fileName.append(MimeUtility.decodeText(bodyPart.getFileName()));
						return is;
					}
				}
			}
		} else if (part.isMimeType("message/rfc822")) {
			return getAttachment((Part) part.getContent(), fileName);
		}
		return null;
	}

如果有场景需要将InputStream转为文件存储,可以使用下面的方式

	private void saveFile(InputStream is, String destDir, String fileName)
			throws FileNotFoundException, IOException {
		BufferedInputStream bis = new BufferedInputStream(is);
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(destDir + fileName)));
		int len = -1;
		while ((len = bis.read()) != -1) {
			bos.write(len);
			bos.flush();
		}
		bos.close();
		bis.close();
	}
读取邮件正文

正文信息会赋值给传入的content

	private static void getMailTextContent(Part part, StringBuffer content) throws MessagingException, IOException {
		boolean isContainTextAttach = part.getContentType().indexOf("name") > 0;
		if (part.isMimeType("text/*") && !isContainTextAttach) {
			content.append(part.getContent().toString());
		} else if (part.isMimeType("message/rfc822")) {
			getMailTextContent((Part) part.getContent(), content);
		} else if (part.isMimeType("multipart/*")) {
			Multipart multipart = (Multipart) part.getContent();
			int partCount = multipart.getCount();
			for (int i = 0; i < partCount; i++) {
				BodyPart bodyPart = multipart.getBodyPart(i);
				getMailTextContent(bodyPart, content);
			}
		}
	}
获取邮件优先级

1(High):紧急 3:普通(Normal) 5:低(Low)

	private String getPriority(MimeMessage msg) throws MessagingException {
		String priority = "普通";
		String[] headers = msg.getHeader("X-Priority");
		if (headers != null) {
			String headerPriority = headers[0];
			if (headerPriority.indexOf("1") != -1 || headerPriority.indexOf("High") != -1)
				priority = "紧急";
			else if (headerPriority.indexOf("5") != -1 || headerPriority.indexOf("Low") != -1)
				priority = "低";
			else
				priority = "普通";
		}
		return priority;
	}
判断是否有附件
	private boolean isContainAttachment(Part part) throws MessagingException, IOException {
		boolean flag = false;
		if (part.isMimeType("multipart/*")) {
			MimeMultipart multipart = (MimeMultipart) part.getContent();
			int partCount = multipart.getCount();
			for (int i = 0; i < partCount; i++) {
				BodyPart bodyPart = multipart.getBodyPart(i);
				String disp = bodyPart.getDisposition();
				if (disp != null && (disp.equalsIgnoreCase(Part.ATTACHMENT) || disp.equalsIgnoreCase(Part.INLINE))) {
					flag = true;
				} else if (bodyPart.isMimeType("multipart/*")) {
					flag = isContainAttachment(bodyPart);
				} else {
					String contentType = bodyPart.getContentType();
					if (contentType.indexOf("application") != -1) {
						flag = true;
					}
					if (contentType.indexOf("name") != -1) {
						flag = true;
					}
				}
				if (flag)
					break;
			}
		} else if (part.isMimeType("message/rfc822")) {
			flag = isContainAttachment((Part) part.getContent());
		}
		return flag;
	}
读取收件人

收件人可能是常规收件人,也可能是密送或者抄送的,所以方法需要传递要哪种的收件人。当然也可以传递null。
Message.RecipientType.TO 收件人
Message.RecipientType.CC 抄送
Message.RecipientType.BCC 密送

	public String getReceiveAddress(MimeMessage msg, Message.RecipientType type) throws MessagingException {
		StringBuffer receiveAddress = new StringBuffer();
		Address[] addresss = null;
		if (type == null) {
			addresss = msg.getAllRecipients();
		} else {
			addresss = msg.getRecipients(type);
		}
		if (addresss == null || addresss.length < 1)
			throw new MessagingException("没有收件人!");
		for (Address address : addresss) {
			InternetAddress internetAddress = (InternetAddress) address;
			receiveAddress.append(internetAddress.toUnicodeString()).append(",");
		}
		receiveAddress.deleteCharAt(receiveAddress.length() - 1);
		return receiveAddress.toString();
	}
读取发件人

这个有一定意义,比如只获取指定发件人的邮件(像简历这种,只会是部分发件人,简单筛选即可)。当然也可以设置哪些发件人的过滤点不看。
有时候发件人的姓名需要解码才能正常显示,但大部分情况又不需要解码,代码中加了注释。

	private String[] getFrom(MimeMessage msg) throws MessagingException, UnsupportedEncodingException {
		String[] from = new String[2];
		Address[] froms = msg.getFrom();
		if (froms.length < 1)
			throw new MessagingException("没有发件人!");
		InternetAddress address = (InternetAddress) froms[0];
		from[0] = address.getPersonal(); // 需要解码的话:MimeUtility.decodeText(address.getPersonal())
		from[1] = address.getAddress();
		return from;
	}
删除邮件

慎重使用,非必要不删除。

	private void deleteMessage(Message... messages) throws MessagingException, IOException {
		if (messages == null || messages.length < 1)
			throw new MessagingException("未找到要删除的邮件!");
		for (int i = 0, count = messages.length; i < count; i++) {
			Message message = messages[i];
			String subject = message.getSubject();
			message.setFlag(Flags.Flag.DELETED, true);
			System.out.println("删除邮件: " + subject);
		}
	}
解析邮件

需要注意的地方
1、获取MimeMessage的时候,常规是直接MimeMessage msg = (MimeMessage) messages[i];即可,但是这种对于发送退回的邮件、部分平台的通知邮件等,都会报错javax.mail.MessagingException: Unable to load BODYSTRUCTURE,所以需要将获取到的数据重新new一下使用。
2、代码中理论上是按照收到邮件时间倒序排列的,但是如果邮件数量非常大,比如有三四百,这时候顺序就不准了,最后面十封左右是中间位置的。要实现完全的时间倒序,只能全部读取出来,然后用代码再排序一次。
3、得到的参数是inputstream,如果需要传递参数,必须转化为byte[]或者存储文件后使用路径。存储文件的方法上面已经提供过,转化为byte[]以及从byte[]转化会inputstream方法会提供。
4、代码中没有解析邮件的唯一值,如果需要,可以使用String messageId = msg.getMessageID();拿到

	private void parseMessage(Message... messages) throws MessagingException, IOException {
		if (messages == null || messages.length < 1)
			throw new MessagingException("未找到要解析的邮件!");
		// 解析所有邮件
		int count = messages.length;
		for (int i = count - 1; i >= 0; i--) {
			MimeMessage msg2 = (MimeMessage) messages[i];
			MimeMessage msg = new MimeMessage(msg2);
			System.out.println("------------------解析第" + msg.getMessageNumber() + "封邮件-------------------- ");
			// 如果有编码,可以先解码: MimeUtility.decodeText(msg.getSubject());
			System.out.println("主题: " + msg.getSubject());
			System.out.println("发件人名称: " + getFrom(msg)[0]);
			System.out.println("发件人邮箱: " + getFrom(msg)[1]);
			System.out.println("收件人:" + getReceiveAddress(msg, null));
			System.out.println("发送时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(msg.getSentDate()));
			System.out.println("是否已读:" + msg.getFlags().contains(Flags.Flag.SEEN));
			System.out.println("邮件优先级:" + getPriority(msg));
			System.out.println("是否需要回执:" + (msg.getHeader("Disposition-Notification-To") != null ? true : false));
			System.out.println("邮件大小:" + msg.getSize() * 1024 + "kb");
			StringBuffer content = new StringBuffer(100);
			getMailTextContent(msg, content);
			System.out.println("邮件正文:" + content);
			boolean isContainerAttachment = isContainAttachment(msg);
			System.out.println("是否包含附件:" + isContainerAttachment);
			if (isContainerAttachment) {
				InputStream is = getAttachment(msg, fileName);
				// 这里可以开始使用is,比如保存附件或者上传到其他地方
				// 如果是传递参数,不能直接使用inputstream,可以先转换为byte[]或者存储为文件然后传递文件地址
			}
			System.out.println("------------------第" + msg.getMessageNumber() + "封邮件解析结束-------------------- ");
			System.out.println();

		}
	}

inputstream转byte[]

				byte[] byteBuffer = null;
				ByteArrayOutputStream outByte = new ByteArrayOutputStream();
				byte[] tmpByte = new byte[2048];
				int len = 0;
				while ((len = is.read(tmpByte)) != -1) {
				      outByte.write(tmpByte, 0, len);
				}
				byteBuffer = outByte.toByteArray();

byte[]转inputstream

					InputStream is = new ByteArrayInputStream(b);
读取设置

1、代码中是以腾讯企业邮箱为例,其他邮箱需更换host、port等。
2、代码中的email指的是邮箱地址,password指的是邮箱密码(部分邮箱需要使用授权码,而不是登录密码)
3、如果附件超过1M,请务必加上mail.imap.partialfetch和mail.imaps.partialfetch,不然读取或非常非常慢,大概率超时
4、代码中读取的是收件箱INBOX,可以替换这个收取不同位置邮件,如垃圾箱Junk、已删除Deleted Messages、草稿Drafts、已发送Sent Messages

	public void resceive() throws Exception {
		Properties props = System.getProperties();
		props.setProperty("mail.imap.host", host);
		props.setProperty("mail.imap.port", "143");
		props.setProperty("mail.store.protocol", "imap");
		props.setProperty("mail.imap.partialfetch", "false");
		props.setProperty("mail.imaps.partialfetch", "false");
		Session session = Session.getInstance(props);
		IMAPStore store = (IMAPStore) session.getStore("imap");
		store.connect(email, password);
		/**
		 * 获得收件箱INBOX 
		 * 除了收件箱,其他的可以使用以下方法查看到 
		 * Folder defaultFolder = store.getDefaultFolder();
		 * Folder[] allFolder = defaultFolder.list();
		 * for(int i = 0; i < allFolder.length; i++) {
		 * System.out.println(allFolder[i].getName()); 
		 * } 
		 * 比如垃圾箱:Junk 已删除:Deleted Messages 草稿:Drafts 已发送:Sent Messages
		 */
		Folder folder = store.getFolder("INBOX");
		folder.open(Folder.READ_WRITE); //Folder.READ_ONLY:只读权限 Folder.READ_WRITE:可读可写(可以修改邮件的状态)
		System.out.println("未读邮件数: " + folder.getUnreadMessageCount());
		System.out.println("邮件总数: " + folder.getMessageCount());
		// 得到收件箱中的所有邮件,并解析
		Message[] messages = folder.getMessages();
		parseMessage(messages);
		// 释放资源
		folder.close(true);
		store.close();
	}

上传附件到阿里云OSS

上面代码已经获取到了附件的inputsream,可以使用这个直接上传到阿里云oss,做个文件备份,也方便后续使用。
其中uploadOssFile方法第一个参数可以带上路径,oss会自动创建文件夹保存。
public class AliyunUtil {

private static final String ACCESS_KEY_ID = "查看阿里云后台";
private static final String SECRET = "查看阿里云后台";

/**
 * 文件地址获取
 */
public static String getOssUrl(String filePathName) {
	OSS ossClient = new OSSClientBuilder().build("http://oss-cn-beijing.aliyuncs.com", ACCESS_KEY_ID, SECRET);
	Date expiration = new Date(new Date().getTime() + 3 * 3600 * 1000);
	URL url = ossClient.generatePresignedUrl("xingchenlie", filePathName, expiration);
	ossClient.shutdown();
	return url.toString();
}

/**
 * OSS文件上传
 */
public static boolean uploadOssFile(String fileName, InputStream inputStream) {
	OSS ossClient = new OSSClientBuilder().build("http://oss-cn-beijing.aliyuncs.com", ACCESS_KEY_ID, SECRET);
	ossClient.putObject("查看OSS后台的bucketName", fileName, inputStream);
	ossClient.shutdown();
	return true;
}

}

解析简历

没有自定义方法,而是从网上找了几个,也包括阿里云市场上的,费用都不高,平均下来不到一毛钱。
但是but,程序猿想要免费,所有又找了一个免费的解析网站,大概解析十几次就挂了,封了我的ip。于是乎挂上了ip代理,最终愉快的跑完了两千多份的简历解析。不足的是不支持pdf中的图片简历,只能是文字类型。索性我的简历只有一封是图片的。
Java代码读取电子邮件(含附件)并解析简历文件_第1张图片

如果需要简历解析网站,可以联系微信fymod1988。如果想要代理设置代码,可以看其他文章,接下来的一篇就是介绍代理的使用,包括访问网站设置代理以及上传文件设置代理。

你可能感兴趣的:(职位简历维护系统开发,java,爬虫)