※当サイトの記事には、広告・プロモーションが含まれます。

Javaでjava.io.Fileとファイルシステムの関係とか調べてみた

www.itmedia.co.jp

 今回のサイバー攻撃は発覚後も繰り返し続いたといい、プライベートクラウド内のサーバをシャットダウンした後も、遠隔からサーバを再起動して感染拡大を図る行為が見られたという。対応として、ドワンゴはサーバの電源ケーブルや通信ケーブルを物理的に抜線・遮断し、封鎖。システム間・パブリッククラウド間の接続も断った。結果、現在はデータセンターのサーバが全て使用できない状況という。

ニコニコを襲ったサイバー攻撃の全体像まとめ 動画データは無事か、復旧に1カ月かかる理由は - ITmedia NEWS

⇧ ネットワークを遮断して外部からアクセスできないようにすれば、サーバーを落とす必要は無いような気もしますが、犯行の経路が不明な状態では致し方ないということですかね?

サーバーが落ちた状態で不正なアクセスログとか解析するとなった場合に、物理的なハードディスクとかを取り出して、別マシンにデータ移行して解析するってことなんですかね?

ニコニコのサービスを普段利用していないので影響は無いですし、ニコニコのサービスを運用している方は復旧が大変とは思いますが、無理せず頑張って欲しいですね。

私利私欲でサイバー攻撃する輩が、排除されるのが一番なんですがね。

Javajava.io.Fileとファイルシステムの関係とか調べてみた

JavaでFileを戻り値とするメソッドを検討していたのだけど、ファイルシステム上に作成されたファイルの削除タイミングとかが、結局、呼び出し元に負担させることになるんだろうな、と漠然とした認識だったので、調べてみました。

Java標準のAPIドキュメントによると、

docs.oracle.com

このクラスのインスタンスは、実際のファイル・システム・オブジェクト(ファイルやディレクトリなど)を示す場合も、示さない場合もあります。このようなオブジェクトを示す場合、そのオブジェクトはパーティション内に存在します。

https://docs.oracle.com/javase/jp/8/docs/api/java/io/File.html

パーティションとは、ファイル・システム用の記憶域でオペレーティング・システム固有の部分です。1つの記憶装置(物理ディスク・ドライブ、フラッシュ・メモリー、CD-ROMなど)に、複数のパーティションが含まれることがあります。オブジェクトが存在する場合は、このパス名の絶対形式の上位にあるものによって指定されるパーティションに存在します。

https://docs.oracle.com/javase/jp/8/docs/api/java/io/File.html

ファイル・システムでは、実際のファイル・システム・オブジェクトに対する特定の操作(読み取り、書き込み、実行など)への制約を実装することができます。これらの制約を総称してアクセス権と呼びます。ファイル・システムには、1つのオブジェクトに対して複数の組のアクセス権が設定されていることがあります。たとえば、ある組をオブジェクトの所有者に適用し、別の組をほかのすべてのユーザーに適用することができます。オブジェクトのアクセス権が原因で、このクラスの一部のメソッドが失敗することがあります。

https://docs.oracle.com/javase/jp/8/docs/api/java/io/File.html

Fileクラスのインスタンスは不変です。つまり、一度作成されると、Fileオブジェクトで表される抽象パス名は変更されません。

https://docs.oracle.com/javase/jp/8/docs/api/java/io/File.html

java.io.File のインスタンスは、ファイルシステム上のファイルを操作できると。

stackoverflowによると、

stackoverflow.com

⇧ そもそも、戻り値をFileではなく、byteにすれば、メソッド内でファイルシステム上のファイルを削除してから、ファイルの内容を連携できるっぽい。

のだけど、結局のところ、呼び出し元での用途がブラウザなどでファイルをダウンロードするようなケースだと、byte の内容を読み込む際に、

  1. Media Types (formerly known as MIME types)
  2. ファイル名

などが必要になってくると。

「Media Types」については、

developer.mozilla.org

警告: ブラウザーは URL を処理する方法を決定するために、ファイル拡張子ではなく MIME タイプを使用しますので、ウェブサーバーは正しい MIME タイプをレスポンスの Content-Type ヘッダーで送信することが重要です。 これが正しく構成されていないと、ブラウザーはファイルの中身を誤って解釈し、サイトが正しく動作しなかったり、ダウンロードファイルが誤って扱われたりすることがあります。

https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types

www.iana.org

⇧ サーバーサイド側では、「Content-Type」と呼ばれる部分に「Media Types (formerly known as MIME types)」を設定する必要があると。

ちなみに、

stackoverflow.com

⇧ Zipファイルなども、ファイルシステム上に作成されるファイルであるからして、同様にして

■File(ファイルシステム上はzip file) → byte[]

java.io.File(ファイルシステム上はzip file) → byte[]

※ byte は、プリミティブ型

への変換はできると謳っている。

Javaでダウンロードする際には、byte の形になっていれば良いので、知りたいことは確認できたのですが、脱線して調べてみたところ、

stackoverflow.com

⇧ byte からの変換もできる模様。

■①byte → java.util.zip.ZipInputStream

byte[] → java.util.zip.ZipInputStream

■②byte[] → java.io.File(ファイルシステム上はzip file)

byte[] → java.io.File(ファイルシステム上はzip file)

※ byte は、プリミティブ型

⇧②のパターンの実例がネット上に見当たらないので、本当に実現できるかの動作確認は必要そうですが...

まとめると、メソッドとしては、

  1. ファイルシステム上のファイル削除してjava.io.Fileを戻り値は無理
  2. java.io.Fileをbyteに変換し、ファイルシステム上のファイル削除してbyte[]を戻り値は可能
    →呼び出し元としては、ファイル名も必要

という形ですかね。

ファイルのダウンロード機能が要件としてある場合は、

qiita.com

⇧ ブラウザ側へのレスポンスを設定する部分、ないしは、レスポンスを返却する部分までを1つのメソッドで実現することもできそうではありますと。

以下のように、

  1. ファイルを作成する処理
  2. ダウンロードの内容を設定、返却する処理

メソッドを2つに分けて、2つのメソッドを実行する1つのメソッドにするようなイメージになるんかな?

Zipファイルのダウンロードで検証してみる

前に、

ts0818.hatenablog.com

ts0818.hatenablog.com

⇧ 作成していたプロジェクトを流用。

何やら、

dev.classmethod.jp

⇧ ブラウザ経由でZipダウンロードさせるケースだと、Zip化したファイルを展開できなくなるケースがあるようだったので、ダウンロード用のZip化処理は注意が必要と。

Controllerクラスを追加してます。

ソースコードは以下のような感じ。既存に追加した箇所は背景が灰色。

■/test-zip/src/main/java/com/example/demo/util/ZipStandardUtil.java

package com.example.demo.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * Java標準のAPIのみでZip処理
 */
public class ZipStandardUtil {

	
	/**
	 * Zip圧縮<br>
	 * Zipファイル作成後、byte[] に変換
	 * @param zipName Zipファイル名
	 * @param targetRootPath Zip対象のルートパス
	 * @param option 
	 * @param encoding 文字コード
	 * @return byte[] Zipファイルをバイナリ化したデータ
	 */
	public static byte[] zipAndByte(String zipName, String targetRootPath, OpenOption option, Charset encoding) {
		Path pathZipInput = Paths.get(targetRootPath);
		Path pathZipOutput = Paths.get(zipName);
		//int countZip = 0;
		
		//File zipFile = pathZipOutput.toFile();
		byte[] byteZipFile = null;

		try (OutputStream os = Files.newOutputStream(pathZipOutput, option);
				ZipOutputStream zos = new ZipOutputStream(os, encoding)) {

			//for (String targetPathStr: targetPathList) {

			if (Files.isDirectory(pathZipInput)) {
				directoryZip(pathZipInput.getNameCount(), pathZipInput, zos);

			} else {
				// 新しいファイル名を指定し、zip中に設定
				ZipEntry zipEntry = new ZipEntry(pathZipInput.getFileName().toString());
				zos.putNextEntry(zipEntry);
				byte[] bytes = Files.readAllBytes(pathZipInput);
				zos.write(bytes);
			}
			zos.closeEntry();
			zos.close();
			
			// ファイルシステム上にZipファイルが存在する場合
			if (Files.exists(pathZipOutput)){
				byteZipFile = Files.readAllBytes(pathZipOutput);
				boolean isDeleted = Files.deleteIfExists(pathZipOutput);
				if (isDeleted) {
					System.out.println("Zipファイルを削除しました");
				} else {
					System.out.println("Zipファイルを削除されませんでした");
				}
				
			}

			// TODO: Zipファイルの元になったファイルやフォルダの削除
			// Zipファイルの元になったファイルやフォルダも一時的に作成している場合(残す必要が無い)は、削除処理が必要

			
			//}
		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
		
		return byteZipFile;
	}
	
	/**
	 * Zip圧縮
	 * @param zipName Zipファイル名
	 * @param targetRootPath Zip対象のルートパス
	 * @param option 
	 * @param encoding 文字コード
	 * @return
	 */
	public static boolean zip(String zipName, String targetRootPath, OpenOption option, Charset encoding) {
		Path pathZipInput = Paths.get(targetRootPath);
		Path pathZipOutput = Paths.get(zipName);
		//int countZip = 0;
		
		try (OutputStream os = Files.newOutputStream(pathZipOutput, option);
				ZipOutputStream zos = new ZipOutputStream(os, encoding)) {

			//for (String targetPathStr: targetPathList) {

			if (Files.isDirectory(pathZipInput)) {
				directoryZip(pathZipInput.getNameCount(), pathZipInput, zos);

			} else {
				// 新しいファイル名を指定し、zip中に設定
				ZipEntry zipEntry = new ZipEntry(pathZipInput.getFileName().toString());
				zos.putNextEntry(zipEntry);
				byte[] bytes = Files.readAllBytes(pathZipInput);
				zos.write(bytes);
			}
			zos.closeEntry();

			//}
		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
		
		return true;
	}

	private static void directoryZip(int rootCount, Path path, ZipOutputStream zip) throws IOException {
		Files.list(path).forEach(
				p -> {
					try {
						var pathName = p.subpath(rootCount, p.getNameCount());
						if (Files.isDirectory(p)) {
							zip.putNextEntry(new ZipEntry(pathName + "/"));
							directoryZip(rootCount, p, zip);
						} else {
							zip.putNextEntry(new ZipEntry(pathName.toString()));
							zip.write(Files.readAllBytes(p));
						}
					} catch (IOException e) {
						throw new RuntimeException("ファイル操作で問題が発生しました。", e);
					}
				});
	}

	/**
	 * Zip展開(解凍)
	 * @param zipFilePath
	 */
	public static void unzip(Path zipFilePath, String decompressDir) {

		try {
			File zipFile = new File(zipFilePath.toString());

			ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
			ZipEntry entry = null;
			while ((entry = zis.getNextEntry()) != null) {
				if (entry.isDirectory()) {
					new File(entry.getName()).mkdirs();
				} else {
					File parent = new File(entry.getName()).getParentFile();
					if (parent != null) {
						parent.mkdirs();
					}
					FileOutputStream out = new FileOutputStream(entry.getName());
					byte[] buf = new byte[1024];
					int size = 0;
					while ((size = zis.read(buf)) != -1) {
						out.write(buf, 0, size);
					}
					out.close();
				}
				zis.closeEntry();
			}
			zis.close();
		} catch (FileNotFoundException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
	}

	public static boolean unzip(String zipFileFullPath, String unzipPath) {

		File baseFile = new File(zipFileFullPath);
		File baseDir = new File(baseFile.getParent(),
				baseFile.getName().substring(0, baseFile.getName().lastIndexOf(".")));
		if (!baseDir.mkdir())
			System.out.println("Couldn't create directory because directory with the same name exists.: " + baseDir);

		ZipFile zipFile = null;
		try {
			// ZIPファイルオブジェクト作成
			zipFile = new ZipFile(zipFileFullPath);

			// ZIPファイル内のファイルを列挙
			Enumeration<? extends ZipEntry> enumZip = zipFile.entries();

			// ZIPファイル内の全てのファイルを展開
			while (enumZip.hasMoreElements()) {

				// ZIP内のエントリを取得
				ZipEntry zipEntry = (java.util.zip.ZipEntry) enumZip.nextElement();

				//出力ファイル取得
				File unzipFile = new File(unzipPath);
				File outFile = new File(unzipFile.getAbsolutePath() + "/" + baseDir.getName(), zipEntry.getName());

				if (zipEntry.isDirectory())
					outFile.mkdir();
				else {
					// 圧縮ファイル入力ストリーム作成
					BufferedInputStream in = new BufferedInputStream(zipFile.getInputStream(zipEntry));

					// 親ディレクトリがない場合、ディレクトリ作成
					if (!outFile.getParentFile().exists())
						outFile.getParentFile().mkdirs();

					// 出力オブジェクト取得
					BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outFile));

					// 読み込みバッファ作成
					byte[] buffer = new byte[1024];

					// 解凍ファイル出力
					int readSize = 0;
					while ((readSize = in.read(buffer)) != -1) {
						out.write(buffer, 0, readSize);
					}
					// クローズ
					try {
						out.close();
					} catch (Exception e) {
					}
					try {
						in.close();
					} catch (Exception e) {
					}
				}
			}
			// 解凍処理成功
			return true;
		} catch (Exception e) {
			// エラーログ出力
			System.out.println(e.toString());
			// 解凍処理失敗
			return false;
		} finally {
			if (zipFile != null)
				try {
					zipFile.close();
				} catch (Exception e) {
				}
		}
	}

}
    

■/test-zip/src/main/java/com/example/demo/service/DownloadService.java

package com.example.demo.service;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.OpenOption;

import org.springframework.stereotype.Service;

import com.example.demo.util.ZipStandardUtil;

@Service
public class DownloadService {

	public byte[] downloadZipFile(String zipName, String targetRootPath, OpenOption option, Charset encoding) {
		byte[] zipFileByte = ZipStandardUtil.zipAndByte(zipName, targetRootPath, option, encoding);
		return zipFileByte;
	}
	
}
    

■/test-zip/src/main/java/com/example/demo/controller/DownloadController.java

package com.example.demo.controller;

import java.io.BufferedOutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.service.DownloadService;

import jakarta.servlet.http.HttpServletResponse;

@RestController
public class DownloadController {

	@Autowired
	DownloadService downloadService;
	
    @GetMapping("/downloadFile")
    public void downloadFile(HttpServletResponse response) throws Exception {

    	String zipName = "office.zip";
    	String targetRootPath = "C:\\Eclipse-2023-06\\workspace\\test-zip\\target";
    	OpenOption option = StandardOpenOption.CREATE;
    	Charset encoding = StandardCharsets.UTF_8;
    	
    	
    	byte[] zipFileByte = downloadService.downloadZipFile(zipName, targetRootPath, option, encoding);
    	
    	
        response.setContentType("application/zip");
        response.setContentLengthLong(zipFileByte.length);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + zipName + "\"");
        try (
            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        ) {
            out.write(zipFileByte);
        }
 	
    }
	
}    

⇧で、Spring Boot アプリケーションを起動する。

ブラウザのURL欄に、「http://localhost:8080/downloadFile」と入力して、Controllerクラスのエンドポイントにアクセスすると、

どこにダウンロードするかを促されるので、適当な場所にダウンロード。

⇧ ダウンロードできているようです。

一旦、参考サイト様のように、Controllerクラス側で、「1. ダウンロードの内容を設定、返却する処理」を記載してますが、Serviceクラス側で処理させても良いかもしれない。

処理の分割の粒度とか、悩ましいところですな...

ちなみに、

www.zunouissiki.com

本記事ではFiles.readAllBytes(filePath);によりinputファイルをバイトで読み込んでいます。
そのため、圧縮したいファイルがUTF-8のテキストやSJISのテキスト、jpgやExcelファイルであっても文字化けすることなく圧縮することができます。

【Java入門】zipファイルを生成・出力する方法 | 頭脳一式

再帰処理の部分をJava標準のAPIに委譲する方法でも、Zipファイル化してダウンロードはできる模様。

こちらの方法だと、ZipOutputStreamを明示的にcloseしなくても、Zip化したファイルが展開(解凍)できたので、謎ですな...

■/test-zip/src/main/java/com/example/demo/util/ZipStandardUtil.java

package com.example.demo.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

//import org.apache.tika.parser.txt.CharsetDetector;
//import org.apache.tika.parser.txt.CharsetMatch;


/**
 * Java標準のAPIのみでZip処理
 */
public class ZipStandardUtil {

	public static byte[] zipAndByteArray(String zipName, String targetRootPath, OpenOption option, Charset encoding) throws IOException {
		Path pathZipInput = Paths.get(targetRootPath);
		Path pathZipOutput = Paths.get(zipName);
		byte[] byteZipFile = null;
		
        List<Path> pathList = Files.walk(pathZipInput)
                .filter(filePath -> !filePath.toFile().isDirectory())
                .collect(Collectors.toList());
        
        //zipファイルを作成
        try(FileOutputStream fos = new FileOutputStream(pathZipOutput.toString());
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            ZipOutputStream zos = new ZipOutputStream(bos)){

            //ファイルの数分、ループする。
            for (Path filePath : pathList){

                //サブパスを取得
                String subPath = filePath.subpath(pathZipInput.getNameCount(), filePath.getNameCount()).toString();
                ZipEntry entry = new ZipEntry(subPath);
                zos.putNextEntry(entry);

                //ファイルの中身をbyte配列で取得し、書き込み。
                byte[] data = Files.readAllBytes(filePath);
                zos.write(data);
            }
        }
        
		// ファイルシステム上にZipファイルが存在する場合
		if (Files.exists(pathZipOutput)){
			byteZipFile = Files.readAllBytes(pathZipOutput);
			boolean isDeleted = Files.deleteIfExists(pathZipOutput);
			if (isDeleted) {
				System.out.println("Zipファイルを削除しました");
			} else {
				System.out.println("Zipファイルを削除されませんでした");
			}
			
		}
        return byteZipFile;
        
    }	
		
	/**
	 * Zip圧縮<br>
	 * Zipファイル作成後、byte[] に変換
	 * @param zipName Zipファイル名
	 * @param targetRootPath Zip対象のルートパス
	 * @param option 
	 * @param encoding 文字コード
	 * @return byte[] Zipファイルをバイナリ化したデータ
	 */
	public static byte[] zipAndByte(String zipName, String targetRootPath, OpenOption option, Charset encoding) {
		Path pathZipInput = Paths.get(targetRootPath);
		Path pathZipOutput = Paths.get(zipName);
		//int countZip = 0;
		
		//File zipFile = pathZipOutput.toFile();
		byte[] byteZipFile = null;

		try (OutputStream os = Files.newOutputStream(pathZipOutput, option);
				ZipOutputStream zos = new ZipOutputStream(os, encoding)) {

			//for (String targetPathStr: targetPathList) {

			if (Files.isDirectory(pathZipInput)) {
				directoryZip(pathZipInput.getNameCount(), pathZipInput, zos);

			} else {
				// 新しいファイル名を指定し、zip中に設定
				ZipEntry zipEntry = new ZipEntry(pathZipInput.getFileName().toString());
				zos.putNextEntry(zipEntry);
				byte[] bytes = Files.readAllBytes(pathZipInput);

				zos.write(bytes);

			}
			zos.closeEntry();
			zos.close();
			
			// ファイルシステム上にZipファイルが存在する場合
			if (Files.exists(pathZipOutput)){
				byteZipFile = Files.readAllBytes(pathZipOutput);
				boolean isDeleted = Files.deleteIfExists(pathZipOutput);
				if (isDeleted) {
					System.out.println("Zipファイルを削除しました");
				} else {
					System.out.println("Zipファイルを削除されませんでした");
				}
				
			}
			
			// TODO: Zipファイルの元になったファイルやフォルダの削除
			// Zipファイルの元になったファイルやフォルダも一時的に作成している場合(残す必要が無い)は、削除処理が必要
			
			//}
		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
		return byteZipFile;
	}
	
	/**
	 * Zip圧縮
	 * @param zipName Zipファイル名
	 * @param targetRootPath Zip対象のルートパス
	 * @param option 
	 * @param encoding 文字コード
	 * @return
	 */
	public static boolean zip(String zipName, String targetRootPath, OpenOption option, Charset encoding) {
		Path pathZipInput = Paths.get(targetRootPath);
		Path pathZipOutput = Paths.get(zipName);
		//int countZip = 0;
		
		
		try (OutputStream os = Files.newOutputStream(pathZipOutput, option);
				ZipOutputStream zos = new ZipOutputStream(os, encoding)) {

			//for (String targetPathStr: targetPathList) {

			if (Files.isDirectory(pathZipInput)) {
				directoryZip(pathZipInput.getNameCount(), pathZipInput, zos);

			} else {
				// 新しいファイル名を指定し、zip中に設定
				ZipEntry zipEntry = new ZipEntry(pathZipInput.getFileName().toString());
				zos.putNextEntry(zipEntry);
				
				
			}
			zos.closeEntry();

			//}
		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
		return true;
	}

	private static void directoryZip(int rootCount, Path path, ZipOutputStream zip) throws IOException {
		Files.list(path).forEach(
				p -> {
					try {
						var pathName = p.subpath(rootCount, p.getNameCount());
						if (Files.isDirectory(p)) {
							zip.putNextEntry(new ZipEntry(pathName + "/"));
							directoryZip(rootCount, p, zip);
						} else {
							zip.putNextEntry(new ZipEntry(pathName.toString()));
							byte[] bytes = Files.readAllBytes(p);
									zip.write(bytes);
//				            CharsetDetector detector = new CharsetDetector();
//				            detector.setText(bytes);
////				            EncodingDetector encodingDetector = new UniversalEncodingDetector();
//
//				            CharsetMatch match = detector.detect();
//				            System.out.println( "Encoding = " + match.getName());

						}
					} catch (IOException e) {
						throw new RuntimeException("ファイル操作で問題が発生しました。", e);
					}
				});
	}

	/**
	 * Zip展開(解凍)
	 * @param zipFilePath
	 */
	public static void unzip(Path zipFilePath, String decompressDir) {

		try {
			File zipFile = new File(zipFilePath.toString());

			ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
			ZipEntry entry = null;
			while ((entry = zis.getNextEntry()) != null) {
				if (entry.isDirectory()) {
					new File(entry.getName()).mkdirs();
				} else {
					File parent = new File(entry.getName()).getParentFile();
					if (parent != null) {
						parent.mkdirs();
					}
					FileOutputStream out = new FileOutputStream(entry.getName());
					byte[] buf = new byte[1024];
					int size = 0;
					while ((size = zis.read(buf)) != -1) {
						out.write(buf, 0, size);
					}
					out.close();
				}
				zis.closeEntry();
			}
			zis.close();
		} catch (FileNotFoundException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}
	}

	public static boolean unzip(String zipFileFullPath, String unzipPath) {

		File baseFile = new File(zipFileFullPath);
		File baseDir = new File(baseFile.getParent(),
				baseFile.getName().substring(0, baseFile.getName().lastIndexOf(".")));
		if (!baseDir.mkdir())
			System.out.println("Couldn't create directory because directory with the same name exists.: " + baseDir);

		ZipFile zipFile = null;
		try {
			// ZIPファイルオブジェクト作成
			zipFile = new ZipFile(zipFileFullPath);

			// ZIPファイル内のファイルを列挙
			Enumeration<? extends ZipEntry> enumZip = zipFile.entries();

			// ZIPファイル内の全てのファイルを展開
			while (enumZip.hasMoreElements()) {

				// ZIP内のエントリを取得
				ZipEntry zipEntry = (java.util.zip.ZipEntry) enumZip.nextElement();

				//出力ファイル取得
				File unzipFile = new File(unzipPath);
				File outFile = new File(unzipFile.getAbsolutePath() + "/" + baseDir.getName(), zipEntry.getName());

				if (zipEntry.isDirectory())
					outFile.mkdir();
				else {
					// 圧縮ファイル入力ストリーム作成
					BufferedInputStream in = new BufferedInputStream(zipFile.getInputStream(zipEntry));

					// 親ディレクトリがない場合、ディレクトリ作成
					if (!outFile.getParentFile().exists())
						outFile.getParentFile().mkdirs();

					// 出力オブジェクト取得
					BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outFile));

					// 読み込みバッファ作成
					byte[] buffer = new byte[1024];

					// 解凍ファイル出力
					int readSize = 0;
					while ((readSize = in.read(buffer)) != -1) {
						out.write(buffer, 0, readSize);
					}
					// クローズ
					try {
						out.close();
					} catch (Exception e) {
					}
					try {
						in.close();
					} catch (Exception e) {
					}
				}
			}
			// 解凍処理成功
			return true;
		} catch (Exception e) {
			// エラーログ出力
			System.out.println(e.toString());
			// 解凍処理失敗
			return false;
		} finally {
			if (zipFile != null)
				try {
					zipFile.close();
				} catch (Exception e) {
				}
		}
	}

}

■/test-zip/src/main/java/com/example/demo/service/DownloadService.java

package com.example.demo.service;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.OpenOption;

import org.springframework.stereotype.Service;

import com.example.demo.util.ZipStandardUtil;

@Service
public class DownloadService {

	public byte[] downloadZipFile(String zipName, String targetRootPath, OpenOption option, Charset encoding) {
		//byte[] zipFileByte = ZipStandardUtil.zipAndByte(zipName, targetRootPath, option, encoding);
		byte[] zipFileByte = null;
		try {
			zipFileByte = ZipStandardUtil.zipAndByteArray(zipName, targetRootPath, option, encoding);
		} catch (IOException e) {
			// TODO 自動生成された catch ブロック
			e.printStackTrace();
		}

		return zipFileByte;
	}
	
}
    

■/test-zip/src/main/java/com/example/demo/controller/DownloadController.java

package com.example.demo.controller;

import java.io.BufferedOutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.service.DownloadService;

import jakarta.servlet.http.HttpServletResponse;

@RestController
public class DownloadController {

	@Autowired
	DownloadService downloadService;
	
    @GetMapping("/downloadFile")
    public void downloadFile(HttpServletResponse response) throws Exception {

    	String zipName = "office.zip";
    	String targetRootPath = "C:\\Eclipse-2023-06\\workspace\\test-zip\\target";
    	OpenOption option = StandardOpenOption.CREATE;
    	Charset encoding = StandardCharsets.UTF_8;
    	
    	
    	byte[] zipFileByte = downloadService.downloadZipFile(zipName, targetRootPath, option, encoding);
    	
    	
        response.setContentType("application/zip");
        response.setContentLengthLong(zipFileByte.length);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + zipName + "\"");
        try (
            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        ) {
            out.write(zipFileByte);
        }
 	
    }
	
}    

⇧で、Zipファイルがダウンロードできると。

ファイルシステムに関係してくる部分は、厄介ですな...

毎度モヤモヤ感が半端ない…

今回はこのへんで。