背景
使用的依赖如下:
org.springframework.boot:spring-boot-starter-mail -> 2.2.1.RELEASE
该依赖下用于发送邮件的jar包如下:
org.springframework:spring-context-support:5.2.1.RELEASE
com.sun.mail:jakarta.mail:1.6.4
如果你的项目中引入了com.sun.mail:javax.mail:1.5.2及之前的版本,你会发现并不会出现附件中文乱码,当使用了com.sun.mail:javax.mail:1.5.3以上的版本或者使用jakarta.mail的版本,就会出现附件中文乱码。
注意:javax.mail最新版截至1.6.2,之后启用jakarta.mail,其版本从1.6.3开始
原因剖析及解决方案
作者在网上找到了很多对该问题的解决方案的文章,但是都没有对该问题的出现原因进行分析。下面我就通过源码对该问题做个简单的解释,文章基于你已经完成了邮件的发送。
// 这里设置编码,如果没有设置,也会出现中文乱码,不过这个是最基本的MimeMessageHelper messageHelper =newMimeMessageHelper(mimeMessage,true,"UTF-8");//这里添加附件,fileName文件名称,fileSystemResource为附件
messageHelper.addAttachment(fileName, fileSystemResource);
上面的代码是我们发送邮件添加附件的基本操作,问题就出现在添加附件时,我们进入源码
/**
* org.springframework.mail.javamail.MimeMessageHelper#addAttachment(java.lang.String, org.springframework.core.io.InputStreamSource)
*/publicvoidaddAttachment(String attachmentFilename,InputStreamSource inputStreamSource)throwsMessagingException{String contentType =getFileTypeMap().getContentType(attachmentFilename);addAttachment(attachmentFilename, inputStreamSource, contentType);}publicvoidaddAttachment(String attachmentFilename,InputStreamSource inputStreamSource,String contentType)throwsMessagingException{...DataSource dataSource =createDataSource(inputStreamSource, contentType, attachmentFilename);addAttachment(attachmentFilename, dataSource);}publicvoidaddAttachment(String attachmentFilename,DataSource dataSource)throwsMessagingException{...try{MimeBodyPart mimeBodyPart =newMimeBodyPart();
mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT);//问题就出现在这里
mimeBodyPart.setFileName(MimeUtility.encodeText(attachmentFilename));
mimeBodyPart.setDataHandler(newDataHandler(dataSource));getRootMimeMultipart().addBodyPart(mimeBodyPart);}catch(UnsupportedEncodingException ex){thrownewMessagingException("Failed to encode attachment filename", ex);}}
可以看到最终是在addAttachment方法中调用mimeBodyPart.setFileName来设计文件名称,参数是对输入的文件名进行编码。进入mimeBodyPart.setFileName
staticvoidsetFileName(MimePart part,String name)throwsMessagingException{...// Set the Content-Disposition "filename" parameterString s = part.getHeader("Content-Disposition",null);ContentDisposition cd =newContentDisposition(s ==null?Part.ATTACHMENT: s);
cd.setParameter("filename", name);
part.setHeader("Content-Disposition", cd.toString());...}
可以看到这里是在header中设置Content-Disposition参数,也就是真正设置文件名称的地方,最终调用了ContentDisposition的toString方法
privateParameterList list;publicStringtoString(){if(primaryType ==null|| subType ==null)// need bothreturn"";StringBuffer sb =newStringBuffer();
sb.append(primaryType).append('/').append(subType);if(list !=null)//在setFileName时 设置parameter,这里的list就是这个参数,可以理解list存储的就是文件名//细节可以自己研究,这里不做深入,也就是最终调用list.toString方法
sb.append(list.toString(sb.length()+14));return sb.toString();}
终于进入正题 ParameterList.toString方法,1.5.2版本之前如下:
publicStringtoString(int used){ToStringBuffer sb =newToStringBuffer(used);Iterator e = list.keySet().iterator();while(e.hasNext()){String name =(String)e.next();Object v = list.get(name);if(v instanceofMultiValue){MultiValue vv =(MultiValue)v;String ns = name +"*";for(int i =0; i < vv.size(); i++){Object va = vv.get(i);if(va instanceofValue)
sb.addNV(ns + i +"*",((Value)va).encodedValue);else
sb.addNV(ns + i,(String)va);}}elseif(v instanceofValue)
sb.addNV(name +"*",((Value)v).encodedValue);else//最终调用这里
sb.addNV(name,(String)v);}return sb.toString();}
1.5.3版本以上
publicStringtoString(int used){ToStringBuffer sb =newToStringBuffer(used);Iterator<Map.Entry<String,Object>> e = list.entrySet().iterator();while(e.hasNext()){Map.Entry<String,Object> ent = e.next();String name = ent.getKey();String value;Object v = ent.getValue();if(v instanceofMultiValue){MultiValue vv =(MultiValue)v;
name +="*";for(int i =0; i < vv.size(); i++){Object va = vv.get(i);String ns;if(va instanceofValue){
ns = name + i +"*";
value =((Value)va).encodedValue;}else{
ns = name + i;
value =(String)va;}
sb.addNV(ns,quote(value));}}elseif(v instanceofLiteralValue){
value =((LiteralValue)v).value;
sb.addNV(name,quote(value));}elseif(v instanceofValue){/*
* XXX - We could split the encoded value into multiple
* segments if it's too long, but that's more difficult.
*/
name +="*";
value =((Value)v).encodedValue;
sb.addNV(name,quote(value));}else{
value =(String)v;if(value.length()>60&&
splitLongParameters && encodeParameters){int seg =0;
name +="*";while(value.length()>60){
sb.addNV(name + seg,quote(value.substring(0,60)));
value = value.substring(60);
seg++;}if(value.length()>0)
sb.addNV(name + seg,quote(value));}else{
sb.addNV(name,quote(value));}}}return sb.toString();}
对比两个版本可以发现,1.5.3版本之后多出了一段逻辑,在value.length() > 60 &&
splitLongParameters && encodeParameters 这个条件下会对文件名称做截断,导致文件名乱码,那么 splitLongParameters 和 encodeParameters 参数分别是由哪里控制的
privatestaticfinalboolean encodeParameters =PropUtil.getBooleanSystemProperty("mail.mime.encodeparameters",true);privatestaticfinalboolean splitLongParameters =PropUtil.getBooleanSystemProperty("mail.mime.splitlongparameters",true);
看到这里,大家就明白,为什么设置 System.getProperties().setProperty(“mail.mime.splitlongparameters”, “false”); 可以解决中文乱码了,其实就是让文件名不要被截断,继续走以前的老逻辑。
版权归原作者 wl1411956542 所有, 如有侵权,请联系我们删除。