# 概要
7zipファイルのパス付ファイルの解凍処理をNestJsで行います。
dockerおよびdocker-composeを使用しますので別途必要に応じてインストールします。
ルートディレクトリにbackendフォルダを作っておきます。
mkdir backend# dockerファイル
docker/backend/Dockerfile
FROM node:18.16.0-slim
RUN apt-get update && apt-get install -y curl \
    procps \
    p7zip-full \
    task-japanese \
    && apt-get -y autoremove \
    && apt-get clean
ENV LANG C.UTF-8
RUN npm i -g @nestjs/cli
WORKDIR /workspace
EXPOSE 3000p7zip-fullは7zaコマンドを提供するパッケージです。
日本語ファイル名が化けるためlanguage packも同時にインストールtask-japanese
# docker-compose.yml
docker-compose.ymlを作成します。
version: '3.9'
services:
  backend:
    build:
      context: .
      dockerfile: ./docker/backend/Dockerfile
    ports:
      - "8080:3000"
    volumes:
      - type: bind
        source: ./backend
        target: /workspace
    command: yarn run start:dev# build & install
nest プロジェクトを作成
docker-compose run backend nest new .? Which package manager would you ❤️  to use? (Use arrow keys)
❯ npm 
  yarn 
  pnpmと聞かれるので今回はyarnを選んでいます。
# yarn install
moduleをインストールします。
docker-compose run backend yarn install解凍パスワードの取得を環境変数から行うので。
必要なモジュールを追加インストールします。
docker-compose run backend yarn add @nestjs/configapp.moduleに追加追加します。
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
  imports: [ConfigModule.forRoot()],
})
export class AppModule {}# サービスに追記
backend/src/app.service.ts
import { HttpException, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { execSync } from 'child_process';
import * as fs from 'fs';
@Injectable()
export class AppService {
  constructor(private config: ConfigService) {}
  getHello(): string {
    return 'Hello World!';
  }
  async unzipUpload(file: Express.Multer.File) {
    try {
      const command = `7za  x -o./uploads ./uploads/${
        file.filename
      } -p${this.config.get<string>('ZIP_PASS')}`;
      const res = execSync(command);
      //const fileName = file.originalname.replace('7z', 'txt');
      // 日本語ファイル名が文字化けする場合は以下の処理を行う
      const fileName = Buffer.from(file.originalname, 'binary')
        .toString('utf-8')
        .replace('7z', 'txt');
      const fileText = fs.readFileSync(`./uploads/${fileName}`);
      return fileText.toString();
    } catch (error) {
      const mask = `${error}`.replace(
        `-p${this.config.get<string>('ZIP_PASS')}`,
        '-p********',
      );
      console.error(mask);
      throw new HttpException(mask, 500);
    }
  }
}execSyncにて7zaコマンドを実行しパス付の7zipフォルダを解凍しています。
-o オプションで解凍先のフォルダを指定できます。
resには7zaコマンドの実行結果が出力されます。
# コントローラに追記
FileInterceptor({dest: 'uploads/'})でアップロードファイル保存先のフォルダを指定しています。
import {
  Controller,
  Post,
  UploadedFile,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { AppService } from './app.service';
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}
  @Post()
  @UseInterceptors(FileInterceptor('file', { dest: 'uploads/' }))
  async uploadText(@UploadedFile() file: Express.Multer.File) {
    return await this.appService.unzipUpload(file);
  }
}type
@types/multerが無い場合はインストールしておきます。
yarn add -D @types/multer# dockerを立ち上げて
docker-compose up# 動作確認
テキストファイルを用意して7zipでパスワード付で圧縮します。
テスト.txt
テスト.envに圧縮する際に使用したパスワードを追記します。
ZIP_PASS=<password for 7zip>curlやPostmanなどで動作確認します。
curl -X 'POST' \
  'http://localhost:8080/' \
  -H 'accept: */*' \
  -H 'Accept-Encoding: gzip, deflate, br' \
  -H 'Content-Type: multipart/form-data' \
  -F 'file=@テスト.7z'テスト(テキストファイルの中身)と表示されれば成功です。