お問い合わせ
パスワード付の7zipファイルをdocker上のNestjsで解凍する
投稿日:2023-09-06
@prompta
(株)ブロックセブンスソフトウェア
NestJs
7zip
Docker

# 概要

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 3000

p7zip-full7zaコマンドを提供するパッケージです。
日本語ファイル名が化けるため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/config

app.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'

テスト(テキストファイルの中身)と表示されれば成功です。