【Nodejs】ファイルアップロード

ここに画像の説明を挿入

1. 初期化の準備


1.1 インストールの依存関係

まずexpress-multer-uploadエンジニアリング プロジェクトを作成し、次にプロジェクト内のさまざまな依存パッケージをダウンロードします。

multerミドルウェア
Multerは、multipart/form-data型のフォームデータを処理するためのnode.jsミドルウェアで、主にファイルのアップロードに使用されます。

注: Multer は、非 multipart/form-data タイプのフォーム データを処理しません。

以下は私がダウンロードした依存関係とバージョンです。
画像-20221231143824464

1.2 プロジェクト構成区分

このプロジェクトをより標準的な形で完成させるためには、合理的なプロジェクト構造分割を行う必要があります。次のように:
画像-20221231144220130

2.multerアップロードロジック


2.1 マルチター設定

multer ディレクトリに multerConfig.js を作成し、次のコードを記述します。

  • 依存関係を導入する
  • 処理パス関数をカプセル化します。
  • マルチターの構成オブジェクトを設定する
  • マルチターの設定を追加する
// 1. 引入依赖
const multer = require('multer')
const path = require('path') 

// 2. 封装处理路径函数
const handlePath = (dir) => {
    
    
  return path.join(__dirname, './', dir)
}

// 3. 设置 multer 的配置对象
const storage = multer.diskStorage({
    
    
  // 3.1 存储路径
  destination: function(req, file, cb) {
    
    
    if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png' || file.mimetype==='image/gif') {
    
    
      cb(null, handlePath('../../public'))
    } else {
    
    
      cb({
    
     error: '仅支持 jpg/png/gif 格式的图片!' })
    }
  },
  //  3.2 存储名称
  filename: function (req, file, cb) {
    
    
    // 将图片名称分割伪数组,用于截取图片的后缀
    const fileFormat = file.originalname.split('.')
    // 自定义图片名称
    cb(null, Date.now() + '.' + fileFormat[fileFormat.length - 1])
  }
})

// 4. 为 multer 添加配置
const multerConfig = multer({
    
    
  storage: storage,
  limits: {
    
     fileSize: 2097152 } // 2M
})

module.exports = multerConfig
在该配置中可以设置文件保存的地址、文件名称、限制上传的文件格式、文件大小

2.2 upload上传逻辑
在 multer 目录下创建 upload.js,编写如下代码:

// 引入配置好的 multerConfig
const multerConfig = require('./multerConfig')

// 上传到服务器地址
const BaseURL = 'http://localhost:3001' 
// 上传到服务器的目录
const imgPath = '/public/'

// 封装上传图片的接口
function uploadAvatar(req, res) {
    
    
  return new Promise((resolve, reject) => {
    
    
    multerConfig.single('file')(req, res, function (err) {
    
    
      if (err) {
    
    
        // 传递的图片格式错误或者超出文件限制大小,就会reject出去
        reject(err)
      } else {
    
    
        // 拼接成完整的服务器静态资源图片路径
        resolve(BaseURL + imgPath + req.file.filename)
      }
    })
  })
}

module.exports = uploadAvatar

上記のコードは主にファイルをアップロードするためのメソッドをカプセル化しており、画像のアップロードが成功すると、結合された画像のリンクが解決されます。このメソッドはコントローラーで呼び出されます。

注: 上記の multerConfig.single('file') は単一ファイルのアップロードを示し、フィールド名は「file」です。後で画像をアップロードするためのフィールドは一貫している必要があります。

3. コントローラーを作成し、ルートを定義します

3.1 コントローラーを作成する

controllersディレクトリの下に作成しUserController.js、次のコードを記述します。

const uploadAvatar = require('../multer/upload')

// 用户的逻辑控制器
const UserController = {
    
    
  // 头像图片上传
  async upload(req, res) {
    
    
    try {
    
    
      const uploadRes = await uploadAvatar(req, res)
      res.send({
    
    
        meta: {
    
     code: 200, msg: '上传成功!' },
        data: {
    
     img_url: uploadRes}
      })
    } catch (error) {
    
    
      res.send(error)
    }
  }
}

module.exports = UserController

上記のコードは主に、ユーザー コントローラー クラスUserControllerと写真をアップロードするためのメソッドを記述しますuploaduploadで写真をアップロードするためのインターフェースが呼び出されuploadAvatar成功または失敗の結果が取得され、応答がクライアントに送信されます。

3.2 ルートの定義

① router ディレクトリにindex.jsを作成し、以下のコードを記述します。

const express = require('express')

// 导入用户逻辑
const userController = require('../controllers/UserController')

// 创建路由对象
const router = express.Router()

// 设置路由
router.post('/upload/avatar', userController.upload)

// 导入路由对象
module.exports = router

② ルートを定義したら、app.js にルートを登録し、次のコードを追加する必要があります。

// 导入定义的路由
const router = require('./src/routers/index')

// 注册路由
app.use('/user', router)
在 app.js 中,增加上面两行代码即可完成路由注册

4.写真をアップロードする

次に、テスト リンクを入力し、ポストマン ツールを使用してテストします
画像-20221231144808803
。応答データが正常に取得されたことがわかります。このデータには、画像のリンク アドレスも含まれています

  • フォームはフォームデータ形式である必要があります
  • ファイルのフィールドはバックエンドと一致している必要があります

5. 画像名の最適化


これはユーザーがアバター写真をアップロードする機能であるため、2回目にアバターをアップロードする際には元の写真を削除する必要があり、そうしないと常に古い写真がサーバーに保存されてしまいます。当初のアイデアは、ユーザー ID を画像名として使用し、画像がアップロードされるたびに元の画像が上書きされるようにすることでした。しかし、これには 2 つの問題があります。

  • 異なる形式の画像 (jpg、png、gif) は上書きされずに残ります。
  • 上書きできる場合、画像リンク アドレスは変更されず、データベースに保存されるときの最後の画像アドレスと同じになります。これにより、フロントエンド ページが変更に応じて変更されなくなります。静的リソース内のアバター画像

したがって、ここで使用される方法は、最初に画像の名前を結合して最適化し、次の形式に変更することです: ;时间戳.用户id.jpgこれにより、各画像が繰り返されないことが保証されるだけでなく、ユーザーの ID も含まれます。

注: タイムスタンプは、一意性を確保するために md5 を使用して暗号化できます。ここでは便宜上タイムスタンプを直接使用しています。

6. イメージ名の最適化の実装


この処理は実際には古い画像を削除し、新しい画像の名前を指定された形式に変更するものであり、関数を記述することで実現できます。

6.1 イメージの重複排除、削除、名前変更

  • 指定されたパスの下にあるすべての画像ファイルを検索し、走査します。
  • まず、ID で指定されたファイルが存在するかどうかを確認し、存在する場合は削除します。
  • 新しく保存したファイル名 (timestamp.jpg) に従って、対応するファイルを見つけて、その名前を timestamp.id.jpg に変更します
    。upload.js に次のコードを記述します。
const fs = require('fs')

// 对图片进行去重删除和重命名
const hanldeImgDelAndRename = (id, filename, dirPath) => {
    
    
  // TODO 查找该路径下的所有图片文件
  fs.readdir(dirPath, (err, files) => {
    
    
    for (let i in files) {
    
    
      // 当前图片的名称
      const currentImgName = path.basename(files[i])
      // 图片的名称数组:[时间戳, id, 后缀]
      const imgNameArr = currentImgName.split('.')

      // TODO 先查询该id命名的文件是否存在,有则删除
      if (imgNameArr[1] === id) {
    
    
        const currentImgPath = dirPath + '/' + currentImgName
        fs.unlink(currentImgPath, (err) => {
    
     })
      }

      // TODO 根据新存入的文件名(时间戳.jpg),找到对应文件,然后重命名为: 时间戳.id.jpg
      if (currentImgName === filename) {
    
    
        const old_path = dirPath + '/' + currentImgName
        const new_path = dirPath + '/' + imgNameArr[0] + '.' + id  + path.extname(files[i])
        // 重命名该文件
        fs.rename(old_path, new_path, (err) => {
    
     })
      }
    }
  })
}

関数実行プロセスの分析:

  • この関数は主に fs 組み込みモジュールの readdir を呼び出して、指定されたパスでファイルをクエリし、走査します。
  • ピクチャ名を配列に分割し、idと受信idを取り出して判定し、fsの組み込みモジュールのfs.unlink()メソッドを呼び出して条件を満たしていればファイルを削除する
  • 新しく保存したファイル名 (timestamp.jpg) に従って、対応するファイルを見つけて、その名前を timestamp.id.jpg に変更します。次に、fs 組み込みモジュールの fs.rename() メソッドを呼び出して、ファイルの名前を変更します。

6.2 UploadAvatarインターフェースの変更

hanldeImgDelAndRename メソッドの重複排除、削除、名前変更が完了したら、upload.js の元のアップロード インターフェイス メソッド UploadAvatar でこのメソッドを呼び出し、次のコードに変更する必要があります。

const path = require('path')
// 封装处理路径函数
const handlePath = (dir) => {
    
    
  return path.join(__dirname, './', dir)
}

// 上传接口的 请求参数req  响应参数res
function uploadAvatar(req, res) {
    
    
  return new Promise((resolve, reject) => {
    
    
    multerConfig.single('file')(req, res, function (err) {
    
    
      if (err) {
    
    
        reject(err)
      } else {
    
    
        // 对图片进行去重删除和重命名
        hanldeImgDelAndRename(req.body.id, req.file.filename, handlePath('../../public'))
        const img = req.file.filename.split('.')
        resolve({
    
    
          id: req.body.id,
          // 重新返回符合规定的图片链接地址
          img_url: BaseURL + imgPath + img[0] + '.' + req.body.id + '.' + img[1]
        })
      }
    })
  })
}

注: ファイルをアップロードするときは、req.body.id が受信 ID を取得できるように、id フィールドを指定する必要があります。

7. 最終テスト


7.1 最初のアップロード

画像-20221231145520966

画像が正常にアップロードされ、画像の名前が当社の規定に従って結合され、バックエンド サーバーもアップロードされた画像を正常に保存していることがわかります。
画像-20221231145533537

7.2 2 番目のアップロード

画像-20221231145555139

2 回目のアップロードでは、同じ ID を持つ古い画像が正常に削除され、画像名が変更されました。

7.3 3回目のアップロード

ここでは、次のように、さまざまな ID をアップロードして、さまざまなユーザーがテスト用にアバターをアップロードすることを示すこともできます。

画像-20221231145616872
異なる ID 間でアップロードされた写真は相互に干渉せず、ID が一致した場合にのみ置き換えられ、名前が変更されることがわかります。最後に、取得した画像リンク アドレスをコントローラ内のデータベースに保存するだけでよく、ユーザー ID に従って保存できます。

8. ajaxアップロード

<div class="ajax">
  <p>ajax上传</p>
  <form>
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="file" name="avatar" />
    <button type="button">上传</button>
  </form>
  <img />
</div>

<script>
  let btn = document.querySelector('.ajax [type=button]');
  var username = document.querySelector('.ajax [name=username]');
  var password = document.querySelector('.ajax [name=password]');
  var avatar = document.querySelector('.ajax [name=avatar');

  avatar.addEventListener('change', () => {
    
    
    // 创建预览地址
    let httpUrl = window.webkitURL.createObjectURL(new Blob(avatar.files));
    document.querySelector('img').src = httpUrl;
  });

  btn.addEventListener('click', () => {
    
    
    // 要处理成表单对象上传
    const formsdata = new FormData();
    formsdata.append('username', username.value);
    formsdata.append('password', password.value);
    // 追加name值,和文件对象
    formsdata.append('avatar', avatar.files[0]);

    axios
      .post('/user/upload/avatar', formsdata, {
    
    
        headers: {
    
    
          'Content-Type': 'multipart/form-data',
        },
    })
      .then(res => {
    
    
      document.querySelector('img').src = res.data.imgPath;
    });
  });
</script>

おすすめ

転載: blog.csdn.net/weixin_43094619/article/details/131938589