0


JavaMail发送邮件,附件中文乱码原因解析及解决方案

背景

使用的依赖如下:
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”); 可以解决中文乱码了,其实就是让文件名不要被截断,继续走以前的老逻辑。

标签: java spring spring boot

本文转载自: https://blog.csdn.net/wl1411956542/article/details/129196136
版权归原作者 wl1411956542 所有, 如有侵权,请联系我们删除。

“JavaMail发送邮件,附件中文乱码原因解析及解决方案”的评论:

还没有评论