生成验证码图片失败IOException: Can’t create cache file和NoSuchFileException: /tmp/imageio86013132132.tmp

背景

互联网摸鱼搬砖人美好的周一从9点线上出现大面积无法登录,手机被打爆开始~


项目已经正常运行了好多天了,but 前一天晚上运维更新了服务器的openssl的漏洞,手动编译openssl


登录页面需要前端请求服务器验证码captcha接口获取图片验证码base64到前端展示,但接口返回500,无法生成验证码且登录login接口做了强校验且无法通过配置关闭图片验证码校验


软件架构:Springboot+Mybatis-Plus+Spring redis

项目架构:nginx+centos+mysql+redis


错误日志

登录页面需要前端请求服务器captcha接口获取图片验证码base64到前端展示,但是接口一直报500,搜了一下ELK日志报错如下:


javax.imageio.IIOException: Can’t create output stream!

at javax.imageio.ImageIO.write(ImageIO.java:1574) ~[?:1.8.0_201]

Caused by: javax.imageio.IIOException: Can’t create cache file!

at javax.imageio.ImageIO.createImageOutputStream(ImageIO.java:423) ~[?:1.8.0_201]

at javax.imageio.ImageIO.write(ImageIO.java:1572) ~[?:1.8.0_201]

… 86 more

Caused by: java.nio.file.NoSuchFileException: /tmp/imageio860683512383612131.tmp

at sun.nio.fs.UnixException.translateToIOException(UnixException.java:86) ~[?:1.8.0_201]

at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102) ~[?:1.8.0_201]

at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107) ~[?:1.8.0_201]

at sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:214) ~[?:1.8.0_201]

at java.nio.file.Files.newByteChannel(Files.java:361) ~[?:1.8.0_201]

at java.nio.file.Files.createFile(Files.java:632) ~[?:1.8.0_201]

at java.nio.file.TempFileHelper.create(TempFileHelper.java:138) ~[?:1.8.0_201]

at java.nio.file.TempFileHelper.createTempFile(TempFileHelper.java:161) ~[?:1.8.0_201]

at java.nio.file.Files.createTempFile(Files.java:897) ~[?:1.8.0_201]

at javax.imageio.stream.FileCacheImageOutputStream.(FileCacheImageOutputStream.java:88) ~[?:1.8.0_201]

at com.sun.imageio.spi.OutputStreamImageOutputStreamSpi.createOutputStreamInstance(OutputStreamImageOutputStreamSpi.java:68) ~[?:1.8.0_201]

at javax.imageio.ImageIO.createImageOutputStream(ImageIO.java:419) ~[?:1.8.0_201]

at javax.imageio.ImageIO.write(ImageIO.java:1572) ~[?:1.8.0_201]

… 86 more


问题原因

1.运维更新服务器openssl导致linux的/tmp目录被清空,同时/tmp目录Linux也有自己的清空策略导致程序运行一段时间后生成验证码时找不到文件

2.linux环境下javax.ImageIO 默认 /tmp 文件夹作临时目录

验证码图片是通过javax.imageio.ImageIO类生成的,默认使用java.io.tmpdir配置的文件夹。Linux环境下默认/tmp文件夹。在ImageIO写流的时候会新建一个文件,文件夹名生成方式代码是Files.createTempFile,文件名生成是由TempFileHelper.generatePath生成。


ImageIO生成文件的具体执行方法,这里cacheDir.toPath()的值就是 /tmp

Files.createTempFile最总调用的是TempFileHelper.create方法,具体看下面代码片段


package javax.imageio.stream;

public class FileCacheImageOutputStream extends ImageOutputStreamImpl {


public FileCacheImageOutputStream(OutputStream stream, File cacheDir){

if (cacheDir == null)

            this.cacheFile = Files.createTempFile("imageio", ".tmp").toFile();

        else

        //这里cacheDir.toPath()的值就是 /tmp

            this.cacheFile = Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp")

                                  .toFile();

        …

}

}

 

体生成文件夹名称是/tmp + “/imageio” + 随机数绝对值 + .tmp

比如/tmp/imageio860683512383612131.tmp


class TempFileHelper {

//暴露在外的静态方法

static Path createTempFile(Path dir,String prefix,String suffix,

FileAttribute<?>[] attrs)throws IOException{

        return create(dir, prefix, suffix, false, attrs);

    }

    //createTempFile调用 create 方法 ,其中生成文件夹名称的是generatePath

    private static Path create(Path dir,String prefix,String suffix,

                               boolean createDirectory,FileAttribute<?>[] attrs){

    …….

    try {

                f = generatePath(prefix, suffix, dir);

            } catch (InvalidPathException e) {

                if (sm != null)

                    throw new IllegalArgumentException("Invalid prefix or suffix");

                throw e;

            }

        ………

    }

    //具体文件名生成方法

private static Path generatePath(String prefix, String suffix, Path dir) {

        long n = random.nextLong();

        n = (n == Long.MIN_VALUE) ? 0 : Math.abs(n);

        //这里prefix="/imageio" , n=随机数绝对值 , suffix=".tmp"

        Path name = dir.getFileSystem().getPath(prefix + Long.toString(n) + suffix);

        if (name.getParent() != null)

            throw new IllegalArgumentException("Invalid prefix or suffix");

        return dir.resolve(name);

    }

}


 

当运维更新openssl的时候删除了/tmp文件夹后,导致生成好/tmp/imageio860683512383612131.tmp的文件夹名找不到报java.nio.file.NoSuchFileException


解决方案

java程序启动参数配置 -Djava.io.tmpdir参数并重启

为了避免Linux删除/tmp文件夹,我们在启动服务的时候指定其他的文件路径就能解决了。下面更改shell脚本


TEMP_DIR="/home/Apps/tmp"

# 检查日志路径是否存在

if [ ! -d "${TEMP_DIR}" ]; then

  mkdir "${TEMP_DIR}"

fi

function start(){

nohup java -jar app.jar -Djava.ip.tmpdir=${TEMP_DIR}

}

 

具体看文件夹效果, 文件.8820结尾的就是项目的端口


[Apps@ tmp]$ pwd

/home/Apps/tmp

[Apps@ tmp]$ ll

total 24

drwxrwxr-x 2 Apps Apps 4096 Apr 30  2021 poifiles

drwxrwxr-x 2 Apps Apps 4096 Dec 28 20:47 tomcat-docbase.1190394056485531049.8820

drwxrwxr-x 3 Apps Apps 4096 Aug 17  2020 work

[Apps@ tmp]$


————————————————

版权声明:本文为CSDN博主「旋转跳跃摸鱼无极限」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/sinat_39696159/article/details/124810446