You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Groovy下载含JSON的ZIP文件并解压的问题求助

Groovy 下载并解压ZIP文件的正确实现

问题根源

你的代码存在三个核心问题:

  1. 错误使用字符流读取二进制ZIP数据:BufferedReader和InputStreamReader是用于处理文本的字符流,用它们读取ZIP这类二进制文件时,会将原始字节转换为UTF-8字符,直接破坏ZIP的二进制结构,导致后续无法正确解压。
  2. 误解响应格式:响应本身就是原始的ZIP二进制数据,并非Base64编码字符串,所以调用decodeBase64()会抛出"bad character in base64 value"错误。
  3. 冗余的输出流操作:如果是GET请求获取ZIP,无需向HttpURLConnection的输出流写入内容,这部分代码完全多余。

修正后的实现

1. 正确下载ZIP文件

以下提供两种下载方式,按需选择:

下载到本地文件

try {
    URL url = new URL(testUrl)
    HttpURLConnection conn = (HttpURLConnection) url.openConnection()
    conn.setRequestProperty("Authorization", "Bearer " + testPassword)
    conn.setRequestMethod("GET") // 明确指定GET请求

    // 用字节流读取响应并写入本地文件
    FileOutputStream fos = new FileOutputStream("downloaded.zip")
    byte[] buffer = new byte[4096]
    int bytesRead
    InputStream is = conn.getInputStream()
    while ((bytesRead = is.read(buffer)) != -1) {
        fos.write(buffer, 0, bytesRead)
    }

    // 关闭资源
    fos.close()
    is.close()
    conn.disconnect()

    println("ZIP文件下载完成")
} catch (Exception e) {
    println("下载错误: ${e.getMessage()}")
    e.printStackTrace()
}

读取到内存字节数组

try {
    URL url = new URL(testUrl)
    HttpURLConnection conn = (HttpURLConnection) url.openConnection()
    conn.setRequestProperty("Authorization", "Bearer " + testPassword)
    conn.setRequestMethod("GET")

    ByteArrayOutputStream baos = new ByteArrayOutputStream()
    byte[] buffer = new byte[4096]
    int bytesRead
    InputStream is = conn.getInputStream()
    while ((bytesRead = is.read(buffer)) != -1) {
        baos.write(buffer, 0, bytesRead)
    }
    byte[] zipBytes = baos.toByteArray()

    // 关闭资源
    baos.close()
    is.close()
    conn.disconnect()

    println("ZIP数据已读取到内存")
} catch (Exception e) {
    println("读取错误: ${e.getMessage()}")
    e.printStackTrace()
}

2. 解压ZIP文件

不管是本地ZIP文件还是内存中的字节数组,都可以用ZipInputStream来解压:

解压本地ZIP文件到指定目录

def unzipFile(String zipPath, String destDir) {
    File destDirectory = new File(destDir)
    if (!destDirectory.exists()) {
        destDirectory.mkdirs()
    }

    ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath))
    ZipEntry zipEntry = zis.getNextEntry()
    while (zipEntry != null) {
        File newFile = newFile(destDirectory, zipEntry)
        if (zipEntry.isDirectory()) {
            if (!newFile.isDirectory() && !newFile.mkdirs()) {
                throw new IOException("无法创建目录: ${newFile.getAbsolutePath()}")
            }
        } else {
            // 创建父目录
            File parent = newFile.getParentFile()
            if (!parent.isDirectory() && !parent.mkdirs()) {
                throw new IOException("无法创建父目录: ${parent.getAbsolutePath()}")
            }

            // 写入文件内容
            FileOutputStream fos = new FileOutputStream(newFile)
            byte[] buffer = new byte[4096]
            int len
            while ((len = zis.read(buffer)) > 0) {
                fos.write(buffer, 0, len)
            }
            fos.close()
        }
        zipEntry = zis.getNextEntry()
    }
    zis.closeEntry()
    zis.close()
}

// 防止ZIP路径遍历攻击
private File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
    File destFile = new File(destinationDir, zipEntry.getName())
    String destDirPath = destinationDir.getCanonicalPath()
    String destFilePath = destFile.getCanonicalPath()

    if (!destFilePath.startsWith(destDirPath + File.separator)) {
        throw new IOException("ZIP条目路径非法: ${zipEntry.getName()}")
    }
    return destFile
}

// 调用示例
unzipFile("downloaded.zip", "./unzip-dir")

解压内存中的ZIP字节数组到指定目录

def unzipBytes(byte[] zipBytes, String destDir) {
    File destDirectory = new File(destDir)
    if (!destDirectory.exists()) {
        destDirectory.mkdirs()
    }

    ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes))
    ZipEntry zipEntry = zis.getNextEntry()
    while (zipEntry != null) {
        File newFile = newFile(destDirectory, zipEntry)
        if (zipEntry.isDirectory()) {
            if (!newFile.isDirectory() && !newFile.mkdirs()) {
                throw new IOException("无法创建目录: ${newFile.getAbsolutePath()}")
            }
        } else {
            File parent = newFile.getParentFile()
            if (!parent.isDirectory() && !parent.mkdirs()) {
                throw new IOException("无法创建父目录: ${parent.getAbsolutePath()}")
            }

            FileOutputStream fos = new FileOutputStream(newFile)
            byte[] buffer = new byte[4096]
            int len
            while ((len = zis.read(buffer)) > 0) {
                fos.write(buffer, 0, len)
            }
            fos.close()
        }
        zipEntry = zis.getNextEntry()
    }
    zis.closeEntry()
    zis.close()
}

// 调用示例(假设zipBytes是之前读取到的字节数组)
unzipBytes(zipBytes, "./unzip-dir")

关键注意事项

  • 始终用字节流处理二进制文件:ZIP、图片、视频等二进制数据必须用InputStream/OutputStream这类字节流操作,不能用字符流(Reader/Writer)。
  • 防范路径遍历风险:解压时必须校验ZIP条目的路径,防止恶意ZIP文件写入到目标目录外的位置(上述代码已通过newFile方法处理该风险)。
  • 及时关闭资源:确保所有流和连接都正确关闭,避免资源泄漏。

内容的提问来源于stack exchange,提问作者n1c9

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 10:55:13